C/C++封装socket通信类
不管是socket通信程序的客户端还是服务端,准备工作的代码又长又难看占地方,影响了主程序的结构,必须分离出来。
一、读取、写入数据
1、recvn函数
//从已经准备好的socket中读取数据。
//sockfd:已经准备好的socket连接。
//buffer:接收数据缓冲区的地址。
//n:本次接收数据的字节数。
//返回值:成功接收到n字节的数据后返回true,socket连接不可用返回false。
bool recvn(const int sockfd, char *buffer, const size_t n) {
int remain, len, idx;
remain = n;
idx = 0;
while (remain > 0) {
if ((len = recv(sockfd, buffer + idx, remain,0)) <= 0)
return false;
idx += len;
remain -= len;
}
return true;
}
2、sendn函数
//向已经准备好的socket中写入数据。
//sockfd:已经准备好的socket连接。
//buffer:待发送数据缓冲区的地址。
//n:待发送数据的字节数,缺省值是0时默认为buffer的大小。
//返回值:成功发送完n字节的数据后返回true,socket连接不可用返回false。
bool sendn(const int sockfd, const char *buffer, const size_t n = 0) {
int remain, len, idx;
remain = (n == 0) ? strlen(buffer) : n;
idx = 0;
while (remain > 0) {
if ((len = send(sockfd,buffer + idx, remain,0)) <= 0)
return false;
idx += len;
remain -= len;
}
return true;
}
3、TcpRecv函数
//接收socket的对端发送过来的数据。
//sockfd:可用的socket连接。
//buffer:接收数据缓冲区的地址。
//ibuflen:本次成功接收数据的字节数。解决TCP网络传输「粘包」
//itimeout:接收等待超时的时间,单位:秒,缺省值是0-无限等待。
//返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
bool TcpRecv(const int sockfd,char *buffer,int *ibuflen,const int itimeout = 0) {
if (sockfd == -1) return false;
if (itimeout > 0) {
//延时操作
fd_set tmpfd;
FD_ZERO(&tmpfd);
FD_SET(sockfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout;
timeout.tv_usec = 0;
//如果在itimeout时间内没有可用资源的文件描述符的话就退出
if (select(sockfd+1,&tmpfd,0,0,&timeout) <= 0) return false;
}
//数据包 = 数据大小 + 数据
(*ibuflen) = 0;
if(!recvn(sockfd,(char*)ibuflen,4)) return false;
(*ibuflen)=ntohl(*ibuflen); //把网络字节序转换为主机字节序。
return recvn(sockfd, buffer, *ibuflen);
}
4、TcpSend函数
//向socket的对端发送数据。
//sockfd:可用的socket连接。
//buffer:待发送数据缓冲区的地址。
//ibuflen:待发送数据的字节数,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
//itimeout:接收等待超时的时间,单位:秒,值是0-无限等待。
//返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool TcpSend(const int sockfd,const char *buffer,const int ibuflen = 0,const int itimeout = 5) {
if (sockfd == -1 ) return false;
if (itimeout > 0) {
//延时操作
fd_set tmpfd;
FD_ZERO(&tmpfd);
FD_SET(sockfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout;
timeout.tv_usec = 0;
//如果在itimeout时间内没有可用资源的文件描述符的话就退出
if (select(sockfd+1,&tmpfd,0,0,&timeout) <= 0) return false;
}
//如果长度为0,就采用字符串的长度
int ilen = (ibuflen == 0) ? strlen(buffer) : ibuflen;
int ilenn=htonl(ilen); //转换为网络字节序
//数据包 = 数据大小 + 数据
char strTBuffer[ilen+4]; //前面保存长度
memset (strTBuffer,0,sizeof(strTBuffer));
memcpy (strTBuffer, &ilenn, 4);
memcpy (strTBuffer+4, buffer, ilen);
return sendn(sockfd,strTBuffer, ilen+4);
}
二、C的封装方法
C语言只能把程序代码封装成函数。
1、客户端
把客户端连接服务端的socket操作封装到connecttoserver函数中,主程序的代码更简洁。
// TCP客户端连服务端的函数,serverip-服务端ip,port通信端口
// 返回值:成功返回已连接socket,失败返回-1。
int connecttoserver(const char *serverip,const int port) {
int sockfd = socket(AF_INET,SOCK_STREAM,0); // 创建客户端的socket
struct hostent* h; // ip地址信息的数据结构
if ( (h = gethostbyname(serverip)) == 0 ) {
perror("gethostbyname");
close(sockfd);
return -1;
}
// 把服务器的地址和端口转换为数据结构
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
// 向服务器发起连接请求
if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) {
perror("connect");
close(sockfd);
return -1;
}
return sockfd;
}
2、服务端
把服务端初始化socket操作封装到initserver函数中,主程序的代码更简洁。
// 初始化服务端的socket,port为通信端口
// 返回值:成功返回初始化的socket,失败返回-1。
int initserver(int port) {
int listenfd = socket(AF_INET,SOCK_STREAM,0); // 创建服务端的socket
//设置SO_REUSEADDR选项,解决关闭程序端口还在占用,再运行socket bind不成功的问题
int opt = 1;
unsigned int len = sizeof(opt);
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
// 把服务端用于通信的地址和端口绑定到socket上
struct sockaddr_in servaddr; // 服务端地址信息的数据结构
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本主机的任意ip地址
servaddr.sin_port = htons(port); // 绑定通信端口
if (bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ) {
perror("bind");
close(listenfd);
return -1;
}
// 把socket设置为监听模式
if (listen(listenfd,5) != 0 ) {
perror("listen");
close(listenfd);
return -1;
}
return listenfd;
}
三、C++的封装方法
C++语言可以封装数据和函数,采用的是类。
1、客户端
// socket通信的客户端类
class CTcpClient {
public:
int m_sockfd; // 客户端的socket.
char m_ip[21]; // 服务端的ip地址。
int m_port; // 与服务端通信的端口。
bool m_btimeout; // 调用Recv和Send方法时,失败的原因是否是超时:true-超时,false-未超时。
int m_buflen; // 调用Recv方法后,接收到的报文的大小,单位:字节。
CTcpClient(); // 构造函数。
// 向服务端发起连接请求。
// ip:服务端的ip地址。
// port:服务端监听的端口。
// 返回值:true-成功;false-失败。
bool ConnectToServer(const char *ip,const int port);
// 接收服务端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool Recv(char *buffer,const int itimeout=0);
// 向服务端发送数据。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
//itimeout:接收等待超时的时间,单位:秒,值是0-无限等待。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool Send(const char *buffer,const int ibuflen=0,const int itimeout=5);
// 断开与服务端的连接
void Close();
~CTcpClient(); // 析构函数自动关闭socket,释放资源。
};
CTcpClient::CTcpClient() {
m_sockfd=-1;
memset(m_ip,0,sizeof(m_ip));
m_port=0;
m_btimeout=false;
}
bool CTcpClient::ConnectToServer(const char *ip,const int port) {
if (m_sockfd != -1) {
close(m_sockfd);
m_sockfd = -1;
}
strcpy(m_ip,ip);
m_port=port;
struct hostent* h;
struct sockaddr_in servaddr;
if ( (m_sockfd = socket(AF_INET,SOCK_STREAM,0) ) < 0) return false;
if ( !(h = gethostbyname(m_ip)) ) {
close(m_sockfd);
m_sockfd = -1;
return false;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(m_port); // 指定服务端的通讯端口
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
if (connect(m_sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0) {
close(m_sockfd);
m_sockfd = -1;
return false;
}
return true;
}
bool CTcpClient::Recv(char *buffer,const int itimeout) {
if (m_sockfd == -1) return false;
if (itimeout>0) {
fd_set tmpfd;
FD_ZERO(&tmpfd);
FD_SET(m_sockfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout;
timeout.tv_usec = 0;
m_btimeout = false;
int i;
if ( (i = select(m_sockfd+1,&tmpfd,0,0,&timeout)) <= 0 ) {
if (i==0) m_btimeout = true;
return false;
}
}
m_buflen = 0;
return (TcpRecv(m_sockfd,buffer,&m_buflen));
}
bool CTcpClient::Send(const char *buffer,const int ibuflen,const int itimeout) {
if (m_sockfd == -1) return false;
if (itimeout>0) {
fd_set tmpfd;
FD_ZERO(&tmpfd);
FD_SET(m_sockfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout;
timeout.tv_usec = 0;
m_btimeout = false;
int i;
if ( (i = select(m_sockfd+1,&tmpfd,0,0,&timeout)) <= 0 ) {
if (i==0) m_btimeout = true;
return false;
}
}
int ilen=ibuflen;
if (ibuflen==0) ilen=strlen(buffer);
return(TcpSend(m_sockfd,buffer,ilen));
}
void CTcpClient::Close() {
if (m_sockfd > 0) close(m_sockfd);
m_sockfd=-1;
memset(m_ip,0,sizeof(m_ip));
m_port=0;
m_btimeout=false;
}
CTcpClient::~CTcpClient() {
Close();
}
2、服务端
// socket通信的服务端类
class CTcpServer {
private:
int m_socklen; // 结构体struct sockaddr_in的大小。
struct sockaddr_in m_clientaddr; // 客户端的地址信息。
struct sockaddr_in m_servaddr; // 服务端的地址信息。
public:
int m_listenfd; // 服务端用于监听的socket。
int m_connfd; // 客户端连接上来的socket。
bool m_btimeout; // 调用Recv和Send方法时,失败的原因是否是超时:true-超时,false-未超时。
int m_buflen; // 调用Recv方法后,接收到的报文的大小,单位:字节。
CTcpServer(); // 构造函数。
// 服务端初始化。
// port:指定服务端用于监听的端口。
// 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
bool InitServer(const unsigned int port);
// 阻塞等待客户端的连接请求。
// 返回值:true-有新的客户端已连接上来,false-失败,Accept被中断,如果Accept失败,可以重新Accept。
bool Accept();
// 获取客户端的ip地址。
// 返回值:客户端的ip地址,如"192.168.1.100"。
char *GetIP();
// 接收客户端发送过来的数据。
// buffer:接收数据缓冲区的地址,数据的长度存放在m_buflen成员变量中。
// itimeout:等待数据的超时时间,单位:秒,缺省值是0-无限等待。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时,成员变量m_btimeout的值被设置为true;2)socket连接已不可用。
bool Recv(char *buffer,const int itimeout=0);
// 向客户端发送数据。
// buffer:待发送数据缓冲区的地址。
// ibuflen:待发送数据的大小,单位:字节,缺省值为0,如果发送的是ascii字符串,ibuflen取0,如果是二进制流数据,ibuflen为二进制数据块的大小。
//itimeout:接收等待超时的时间,单位:秒,值是0-无限等待。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool Send(const char *buffer,const int ibuflen=0,const int itimeout=5);
// 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
void CloseListen();
// 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
void CloseClient();
~CTcpServer(); // 析构函数自动关闭socket,释放资源。
};
CTcpServer::CTcpServer() {
m_listenfd=-1;
m_connfd=-1;
m_socklen=0;
m_btimeout=false;
}
bool CTcpServer::InitServer(const unsigned int port) {
CloseListen();
if ( (m_listenfd = socket(AF_INET,SOCK_STREAM,0))<=0) return false;
//设置SO_REUSEADDR选项,解决关闭程序端口还在占用,再运行socket bind不成功的问题
// WINDOWS平台如下
//char b_opt='1';
//setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&b_opt,sizeof(b_opt));
// Linux如下
int opt = 1;
unsigned int len = sizeof(opt);
setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,len);
memset(&m_servaddr,0,sizeof(m_servaddr));
m_servaddr.sin_family = AF_INET;
m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
m_servaddr.sin_port = htons(port);
if (bind(m_listenfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr)) != 0 ) {
CloseListen();
return false;
}
if (listen(m_listenfd,5) != 0 ) {
CloseListen();
return false;
}
m_socklen = sizeof(struct sockaddr_in);
return true;
}
bool CTcpServer::Accept() {
if (m_listenfd == -1) return false;
if ((m_connfd=accept(m_listenfd,(struct sockaddr *)&m_clientaddr,(socklen_t*)&m_socklen)) < 0)
return false;
return true;
}
char *CTcpServer::GetIP() {
return(inet_ntoa(m_clientaddr.sin_addr));
}
bool CTcpServer::Recv(char *buffer,const int itimeout) {
if (m_connfd == -1) return false;
if (itimeout>0) {
fd_set tmpfd;
FD_ZERO(&tmpfd);
FD_SET(m_connfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout;
timeout.tv_usec = 0;
m_btimeout = false;
int i;
if ( (i = select(m_connfd+1,&tmpfd,0,0,&timeout)) <= 0 ) {
if (i==0) m_btimeout = true;
return false;
}
}
m_buflen = 0;
return(TcpRecv(m_connfd,buffer,&m_buflen));
}
bool CTcpServer::Send(const char *buffer,const int ibuflen,const int itimeout) {
if (m_connfd == -1) return false;
if (itimeout>0) {
fd_set tmpfd;
FD_ZERO(&tmpfd);
FD_SET(m_connfd,&tmpfd);
struct timeval timeout;
timeout.tv_sec = itimeout;
timeout.tv_usec = 0;
m_btimeout = false;
int i;
if ( (i = select(m_connfd+1,&tmpfd,0,0,&timeout)) <= 0 ) {
if (i==0) m_btimeout = true;
return false;
}
}
int ilen = ibuflen;
if (ilen==0) ilen=strlen(buffer);
return(TcpSend(m_connfd,buffer,ilen));
}
void CTcpServer::CloseListen() {
if (m_listenfd > 0) {
close(m_listenfd);
m_listenfd=-1;
}
}
void CTcpServer::CloseClient() {
if (m_connfd > 0) {
close(m_connfd);
m_connfd=-1;
}
}
CTcpServer::~CTcpServer() {
CloseListen();
CloseClient();
}