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();
}

猜你喜欢

转载自blog.csdn.net/ymxyld/article/details/124817592