C++网络编程学习:源码的封装

网络编程学习记录

  • 使用的语言为C/C++
  • 源码支持的平台为:Windows / Linux

笔记一:建立基础TCP服务端/客户端  点我跳转
笔记二:网络数据报文的收发  点我跳转
笔记三:升级为select网络模型  点我跳转
笔记四:跨平台支持Windows、Linux系统  点我跳转
笔记五:源码的封装  点我跳转
笔记六:缓冲区溢出与粘包分包  点我跳转
笔记七:服务端多线程分离业务处理高负载  点我跳转


一、为何要进行封装操作

  C++为面向对象编程语言,我们要以面向对象的思路进行源码的编写。
  在对主要源码进行封装后,客户端与服务端的代码编写更加清晰明了,逻辑性更强,便于开发维护。且在今后的服务端高并发测试中,便于新建多个连接进行测试。
  在本篇笔记中,我会基于笔记四的源码进行封装,并将记录我对客户端与服务端源码进行封装时的思路与步骤。最终源码为客户端封装类文件TcpClient.hpp与服务端封装类文件TcpServer.hpp,以及客户端源码client_test.cpp与服务端源码server_test.cpp

二、封装的思路与相关

1.封装的头文件选择

  封装类首先要在头文件中以体现封装性。在本次的封装中,为了能更方便的储存,我选择了hpp头文件。即类声明与类定义都在此文件中。

2.客户端类的封装

  首先,客户端的大致流程如下:

1.建立socket
2.连接服务器
3.建立新线程 用于发送命令
while(true)
{
    
    
	4.使用select函数获取服务器端是否有待处理事件
	5.如果有,就处理它(接收/发送)
}
6.关闭socket

新线程:
while(1)
{
    
    
	1.键入数据
	2.发送数据
}

  所以,我们需要封装的方法如下:

	//初始化socket
	int InitSocket();
	//连接服务器 
	int Connect(const char *ip,unsigned short port);
	//关闭socket
	void CloseSocket();
	//查询是否有待处理消息 
	bool OnRun();
	//判断是否工作中 
	bool IsRun();
	//发送数据 
	int SendData(DataHeader *_head);
	//接收数据
	int RecvData(SOCKET _temp_socket);
	//响应数据
	virtual void NetMsg(DataHeader *_head);

  按照此思路,客户端的源码思路为:

1.InitSocket();//建立socket
2.Connect(const char *ip,unsigned short port);//连接服务器 传入IP与端口
3.建立新线程 用于发送命令
while(4.IsRun())//检测是否工作中
{
    
    
	5.OnRun();//查询是否有待处理消息
}
6.CloseSocket();//关闭socket

新线程:
while(1.IsRun())//检测是否工作中
{
    
    
	2.键入数据
	3.SendData(DataHeader *_head);
}

  其中,OnRun() 方法中使用的是select网络结构,在select筛选出待处理事件后,使用RecvData() 方法进行包头与包体的接收,随后调用NetMsg() 方法,依据包头的报文类型对包体数据进行处理。NetMsg() 方法为虚方法,在之后调用此封装类时,可以进行继承重载操作,便于对数据响应的操作进行变更。

  • 相关源码如下:
	//查询是否有待处理消息 
	bool OnRun()
	{
    
    
		if(IsRun())//如果有连接则监听事件 
		{
    
    
			fd_set _fdRead;//建立集合 
			FD_ZERO(&_fdRead);//清空集合  
			FD_SET(_sock,&_fdRead);//放入集合 
			timeval _t = {
    
    1,0};//select最大响应时间 
			//新建seclect 
			int _ret = select(_sock+1,&_fdRead,NULL,NULL,&_t);
			if(_ret<0)
			{
    
    
				printf("seclect任务结束\n");
				return false;
			}
			if(FD_ISSET(_sock,&_fdRead))//获取是否有可读socket 
			{
    
    
				FD_CLR(_sock,&_fdRead);//清理计数器 
				if(-1 == RecvData(_sock))
				{
    
    
					CloseSocket();
					return false;
				}
			}	
			return true;
		}
		return false;
	} 
	//接收数据
	int RecvData(SOCKET _temp_socket)//处理数据 
	{
    
    
		//缓冲区 
		char buffer[4096] = {
    
    }; 
		//接收客户端发送的数据 
		int _buf_len = recv(_temp_socket,buffer,sizeof(DataHeader),0);
		DataHeader *_head = (DataHeader*)buffer; 
		if(_buf_len<=0)
		{
    
    
			printf("与服务器断开连接,任务结束\n");
			return -1;
		} 
		recv(_temp_socket,buffer+sizeof(DataHeader),_head->date_length-sizeof(DataHeader),0);
		//响应数据 
		NetMsg(_head);
		return 0;	
	}
	//响应数据
	virtual void NetMsg(DataHeader *_head)
	{
    
    
		printf("接收到包头,命令:%d,数据长度:%d\n",_head->cmd,_head->date_length);
		switch(_head->cmd)
		{
    
    
			case CMD_LOGINRESULT://登录结果 接收登录包体 
			{
    
    
				LoginResult *_result = (LoginResult*)_head; 
				printf("登录结果:%d\n",_result->Result);
			}
			break;
			case CMD_LOGOUTRESULT://登出结果 接收登出包体 
			{
    
    
				LogoutResult *_result = (LogoutResult*)_head; 
				printf("登录结果:%d\n",_result->Result);
			}
			break;
			case CMD_NEW_USER_JOIN://新用户登录通知 
			{
    
    
				NewUserJoin *_result = (NewUserJoin*)_head; 
				printf("用户:%s已登录\n",_result->UserName);
			} 
		}
	}

  另外,由于已经被封装,所以在调用方法时,可能会出现步骤错误的情况。例如还没进行新建套接字就进行connect连接操作或是关闭套接字操作、传入数据有误等等,此时就会出现问题。
  我解决此类问题的方法是多加判定。例如判定套接字是否已经被建立,或是传入数据是否有误等等,随后根据情况进行处理。详细源码请看下文。

3.服务端类的封装

  首先,客户端的大致流程如下:

1.建立socket
2.绑定端口IP
3.监听端口
while(true)
{
    
    
	4.使用select函数获取存在待监听事件的socket
	5.如果有新的连接则与新的客户端连接
	6.如果有待监听事件,则对其进行处理(接受与发送)
}
7.关闭socket

  所以,我们需要封装的方法如下:

	//初始化socket 
	int InitSocket();
	//绑定IP/端口
	int Bind(const char* ip,unsigned short port);
	//监听端口
	int Listen(int n);
	//接受连接
	int Accept();
	//关闭socket 
	void CloseSocket();
	//查询是否有待处理消息 
	bool OnRun();
	//判断是否工作中 
	bool IsRun();
	//发送数据 
	int SendData(DataHeader *_head,SOCKET _temp_socket);
	//接收数据
	int RecvData(SOCKET _temp_socket);
	//响应数据
	void NetMsg(DataHeader *_head,SOCKET _temp_socket);

  按照此思路,客户端的源码思路为:

1.InitSocket();//建立socket
2.Bind(const char* ip,unsigned short port);//绑定端口IP
3.Listen(int n);//监听端口
while(4.IsRun())//是否工作中
{
    
    
	5.OnRun();//查看是否有待处理消息
}
6.CloseSocket();//关闭socket

  其中,OnRun() 方法中使用的是select网络结构。在select筛选出待处理事件后,如果为新连接,则使用Accept() 方法进行新客户端连接操作;如果为已连接客户端的待接受事件,则使用RecvData() 方法进行包头与包体的接收,随后调用NetMsg() 方法,依据包头的报文类型对包体数据进行处理。NetMsg() 方法为虚方法,在之后调用此封装类时,可以进行继承重载操作,便于对数据响应的操作进行变更。

  • 相关源码如下:
	//查询是否有待处理消息 
	bool OnRun()
	{
    
    
		if(IsRun())
		{
    
    
			fd_set _fdRead;//建立集合 
			fd_set _fdWrite;
			fd_set _fdExcept;
			FD_ZERO(&_fdRead);//清空集合 
			FD_ZERO(&_fdWrite); 
			FD_ZERO(&_fdExcept); 
			FD_SET(_sock,&_fdRead);//放入集合 
			FD_SET(_sock,&_fdWrite); 
			FD_SET(_sock,&_fdExcept);
			timeval _t = {
    
    2,0};//select最大响应时间 
			SOCKET _maxSock = _sock;//最大socket 
			//把连接的客户端 放入read集合 
			for(int n=_clients.size()-1; n>=0; --n)
			{
    
    
				FD_SET(_clients[n],&_fdRead);
				if(_maxSock < _clients[n])
				{
    
    
					_maxSock = _clients[n];
				}
			}
			//select函数筛选select 
			int _ret = select(_maxSock+1,&_fdRead,&_fdWrite,&_fdExcept,&_t); 
			if(_ret<0)
			{
    
    
				printf("select任务结束\n");
				CloseSocket();
				return false;
			}
			if(FD_ISSET(_sock,&_fdRead))//获取是否有新socket连接 
			{
    
    
				FD_CLR(_sock,&_fdRead);//清理
				Accept();//连接 
			}
			//遍历所有socket 查看是否有待处理事件 
			for(int n=0; n<_clients.size(); ++n)
			{
    
    
				if(FD_ISSET(_clients[n],&_fdRead))
				{
    
    
					if(-1 == RecvData(_clients[n]))//处理请求 客户端退出的话 
					{
    
    
						std::vector<SOCKET>::iterator iter = _clients.begin()+n;//找到退出客户端的地址
						if(iter != _clients.end())//如果是合理值
						{
    
    
							_clients.erase(iter);//移除
						}
					}
				}
			}
			//printf("空闲时间处理其他业务\n");
			return true;
		}
		return false;
	} 
	//接收数据
	int RecvData(SOCKET _temp_socket)//处理数据 
	{
    
    
		//缓冲区
		char buffer[4096] = {
    
    }; 
		//接收客户端发送的数据  
		int _buf_len = recv(_temp_socket,buffer,sizeof(DataHeader),0);
		DataHeader *_head = (DataHeader*)buffer;
		if(_buf_len<=0)
		{
    
    
			printf("客户端已退出\n");
			return -1;
		} 
		recv(_temp_socket,buffer+sizeof(DataHeader),_head->date_length-sizeof(DataHeader),0);
		//响应数据
		NetMsg(_head,_temp_socket); 
		return 0;
	}
	//响应数据
	void NetMsg(DataHeader *_head,SOCKET _temp_socket)
	{
    
    
	 	printf("接收到包头,命令:%d,数据长度:%d\n",_head->cmd,_head->date_length);
		switch(_head->cmd)
		{
    
    
			case CMD_LOGIN://登录 接收登录包体 
			{
    
    
				Login *_login = (Login*)_head;
				/*
				进行判断操作 
				*/
				printf("%s已登录\n密码:%s\n",_login->UserName,_login->PassWord); 
				LoginResult *_result = new LoginResult;	
				_result->Result = 1;
				SendData(_result,_temp_socket);
			}
			break;
			case CMD_LOGOUT://登出 接收登出包体 
			{
    
    
				Logout *_logout = (Logout*)_head;
				/*
				进行判断操作 
				*/
				printf("%s已登出\n",_logout->UserName); 
				LogoutResult *_result = new LogoutResult();
				_result->Result = 1;
				SendData(_result,_temp_socket);
			}
			break;
			default://错误 
			{
    
    
				_head->cmd = CMD_ERROR; 
				_head->date_length = 0; 
				SendData(_head,_temp_socket); 
			}
			break;
		}
	}

  另外,由于已经被封装,所以在调用方法时,可能会出现步骤错误的情况。例如还没进行新建套接字就进行bind绑定端口IP或是关闭套接字操作、传入数据有误等等,此时就会出现问题。
  我解决此类问题的方法是多加判定。例如判定套接字是否已经被建立,或是传入数据是否有误等等,随后根据情况进行处理。详细源码请看下文。

三、封装后的详细源码及其注释

1.客户端

TcpClient.hpp

#ifndef _TcpClient_hpp_
#define _TcpClient_hpp_

#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#include<winSock2.h>
	#include<windows.h>
	#pragma comment(lib,"ws2_32.lib") 
#else
	#include<arpa/inet.h>
	#include<unistd.h>
	#include<string.h>
	
	#define SOCKET int
	#define INVALID_SOCKET (SOCKET)(~0)
	#define SOCKET_ERROR (-1)
#endif
//枚举类型记录命令 
enum cmd 
{
    
    
	CMD_LOGIN,//登录 
	CMD_LOGINRESULT,//登录结果 
	CMD_LOGOUT,//登出 
	CMD_LOGOUTRESULT,//登出结果 
	CMD_NEW_USER_JOIN,//新用户登入 
	CMD_ERROR//错误 
};
//定义数据包头 
struct DataHeader 
{
    
    
	short cmd;//命令
	short date_length;//数据的长短	
};
//包1 登录 传输账号与密码
struct Login : public DataHeader 
{
    
    
	Login()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGIN;
		this->date_length = sizeof(Login); 
	}
	char UserName[32];//用户名 
	char PassWord[32];//密码 
};
//包2 登录结果 传输结果
struct LoginResult : public DataHeader 
{
    
    
	LoginResult()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGINRESULT;
		this->date_length = sizeof(LoginResult); 
	}
	int Result;
};
//包3 登出 传输用户名 
struct Logout : public DataHeader 
{
    
    
	Logout()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGOUT;
		this->date_length = sizeof(Logout); 
	}
	char UserName[32];//用户名 
};
//包4 登出结果 传输结果
struct LogoutResult : public DataHeader 
{
    
    
	LogoutResult()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGOUTRESULT;
		this->date_length = sizeof(LogoutResult); 
	}
	int Result;
};
//包5 新用户登入 传输通告 
struct NewUserJoin : public DataHeader 
{
    
    
	NewUserJoin()//初始化包头 
	{
    
    
		this->cmd = CMD_NEW_USER_JOIN;
		this->date_length = sizeof(NewUserJoin); 
	}
	char UserName[32];//用户名 
};

#include<bits/stdc++.h>

class TcpClient
{
    
    
public:
	//构造 
	TcpClient()
	{
    
    
		_sock = INVALID_SOCKET; 
	}
	
	//析构 
	virtual ~TcpClient()
	{
    
    
		//关闭socket 
		CloseSocket();
	}
	
	//初始化socket 返回1为正常 
	int InitSocket()
	{
    
    
#ifdef _WIN32
		//启动windows socket 2,x环境 
		WORD ver = MAKEWORD(2,2); 
		WSADATA dat;
		if(0 != WSAStartup(ver,&dat))
		{
    
    
			return -1;//-1为环境错误 
		}
#endif 
		//创建socket 
		if(INVALID_SOCKET != _sock)
		{
    
    
			printf("<Socket=%d>关闭连接\n",_sock); 
			CloseSocket();//如果之前有连接 就关闭连接 
		}
		_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 
		if(INVALID_SOCKET == _sock)
		{
    
       

			return 0;//0为socket创建错误 
		} 
		return 1;
	} 
	
	//连接服务器  返回1为成功 
	int Connect(const char *ip,unsigned short port)
	{
    
    
		//如果为无效套接字 则初始化 
		if(INVALID_SOCKET == _sock)
		{
    
    
			InitSocket(); 
		}
		//连接服务器
	   	sockaddr_in _sin = {
    
    };
	   	_sin.sin_family = AF_INET;//IPV4
	   	_sin.sin_port = htons(port);//端口号 
#ifdef _WIN32
		_sin.sin_addr.S_un.S_addr =  inet_addr(ip);//IP 
#else
		_sin.sin_addr.s_addr =  inet_addr(ip);//IP 
#endif
		if(SOCKET_ERROR == connect(_sock,(sockaddr*)&_sin,sizeof(sockaddr_in)))
		{
    
    
			return 0;//连接失败 
		}
		else
		{
    
    
			return 1;//连接成功 
		}
	} 
	
	//关闭socket
	void CloseSocket()
	{
    
    
		if(INVALID_SOCKET != _sock) 
		{
    
    
#ifdef _WIN32
			//关闭socket
			closesocket(_sock); 
			//清除windows socket 环境 
			WSACleanup(); 
#else
			//关闭socket/LINUX
			close(_sock);
#endif
			_sock = INVALID_SOCKET;
		}
	} 
	
	//查询是否有待处理消息 
	bool OnRun()
	{
    
    
		if(IsRun())//如果有连接则监听事件 
		{
    
    
			fd_set _fdRead;//建立集合 
			FD_ZERO(&_fdRead);//清空集合  
			FD_SET(_sock,&_fdRead);//放入集合 
			timeval _t = {
    
    1,0};//select最大响应时间 
			//新建seclect 
			int _ret = select(_sock+1,&_fdRead,NULL,NULL,&_t);
			if(_ret<0)
			{
    
    
				printf("seclect任务结束\n");
				return false;
			}
			if(FD_ISSET(_sock,&_fdRead))//获取是否有可读socket 
			{
    
    
				FD_CLR(_sock,&_fdRead);//清理计数器 
				if(-1 == RecvData(_sock))
				{
    
    
					CloseSocket();
					return false;
				}
			}	
			return true;
		}
		return false;
	} 
	
	//判断是否工作中 
	bool IsRun()
	{
    
    
		return _sock != INVALID_SOCKET; 
	}
	
	//发送数据 
	int SendData(DataHeader *_head)
	{
    
    
		if(IsRun() && _head)
		{
    
    
			send(_sock,(const char*)_head,_head->date_length,0);
			return 1;
		}
		return 0;
	}
	
	//接收数据
	int RecvData(SOCKET _temp_socket)//处理数据 
	{
    
    
		//缓冲区 
		char buffer[4096] = {
    
    }; 
		//接收客户端发送的数据 
		int _buf_len = recv(_temp_socket,buffer,sizeof(DataHeader),0);
		DataHeader *_head = (DataHeader*)buffer; 
		if(_buf_len<=0)
		{
    
    
			printf("与服务器断开连接,任务结束\n");
			return -1;
		} 
		recv(_temp_socket,buffer+sizeof(DataHeader),_head->date_length-sizeof(DataHeader),0);
		//响应数据 
		NetMsg(_head);
		return 0;	
	}
	
	//响应数据
	virtual void NetMsg(DataHeader *_head)
	{
    
    
		printf("接收到包头,命令:%d,数据长度:%d\n",_head->cmd,_head->date_length);
		switch(_head->cmd)
		{
    
    
			case CMD_LOGINRESULT://登录结果 接收登录包体 
			{
    
    
				LoginResult *_result = (LoginResult*)_head; 
				printf("登录结果:%d\n",_result->Result);
			}
			break;
			case CMD_LOGOUTRESULT://登出结果 接收登出包体 
			{
    
    
				LogoutResult *_result = (LogoutResult*)_head; 
				printf("登录结果:%d\n",_result->Result);
			}
			break;
			case CMD_NEW_USER_JOIN://新用户登录通知 
			{
    
    
				NewUserJoin *_result = (NewUserJoin*)_head; 
				printf("用户:%s已登录\n",_result->UserName);
			} 
		}
	}
	
private:
	SOCKET _sock;
};

#endif

client_test.cpp

#include"TcpClient.hpp"
#include<thread>

void _cmdThread(TcpClient* tcp)//命令线程 
{
    
    
	while(tcp->IsRun())
	{
    
    
		//输入请求 
		char _msg[256] = {
    
    };
		scanf("%s",_msg);
		//处理请求 
		if(0 == strcmp(_msg,"exit"))
		{
    
    
			tcp->CloseSocket();
			printf("程序退出\n"); 
			break;
		}
		else if(0 == strcmp(_msg,"login"))
		{
    
    
			//发送 
			Login _login;
			strcpy(_login.UserName,"hbxxy");
			strcpy(_login.PassWord,"123456");
			tcp->SendData(&_login);
		}
		else if(0 == strcmp(_msg,"logout"))
		{
    
    
			//发送 
			Logout _logout;
			strcpy(_logout.UserName,"hbxxy");
			tcp->SendData(&_logout);
		}
		else
		{
    
    
			printf("不存在的命令\n");
		} 
	}
}

int main()
{
    
    
	printf("Welcome\n");
	
	//建立tcp对象 
	TcpClient *tcp1 = new TcpClient();
	//建立一个socket 
	tcp1->InitSocket();
   	//连接服务器
   	tcp1->Connect("127.0.0.1",8888);
	//创建UI线程
	std::thread t1(_cmdThread,tcp1);
	t1.detach();//线程分离 
	//循环 
	while(tcp1->IsRun())
	{
    
    
		tcp1->OnRun();
	}
	//关闭 
	tcp1->CloseSocket();
	
	return 0;
}  

2.服务端

TcpServer.hpp

#ifndef _TcpServer_hpp_
#define _TcpServer_hpp_

#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#include<winSock2.h>
	#include<windows.h>
	#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有 
#else
	#include<arpa/inet.h>//selcet
	#include<unistd.h>//uni std
	#include<string.h>
	
	#define SOCKET int
	#define INVALID_SOCKET (SOCKET)(~0)
	#define SOCKET_ERROR (-1)
#endif
//枚举类型记录命令 
enum cmd 
{
    
    
	CMD_LOGIN,//登录 
	CMD_LOGINRESULT,//登录结果 
	CMD_LOGOUT,//登出 
	CMD_LOGOUTRESULT,//登出结果 
	CMD_NEW_USER_JOIN,//新用户登入 
	CMD_ERROR//错误 
};
//定义数据包头 
struct DataHeader 
{
    
    
	short cmd;//命令
	short date_length;//数据的长短	
};
//包1 登录 传输账号与密码
struct Login : public DataHeader 
{
    
    
	Login()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGIN;
		this->date_length = sizeof(Login); 
	}
	char UserName[32];//用户名 
	char PassWord[32];//密码 
};
//包2 登录结果 传输结果
struct LoginResult : public DataHeader 
{
    
    
	LoginResult()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGINRESULT;
		this->date_length = sizeof(LoginResult); 
	}
	int Result;
};
//包3 登出 传输用户名 
struct Logout : public DataHeader 
{
    
    
	Logout()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGOUT;
		this->date_length = sizeof(Logout); 
	}
	char UserName[32];//用户名 
};
//包4 登出结果 传输结果
struct LogoutResult : public DataHeader 
{
    
    
	LogoutResult()//初始化包头 
	{
    
    
		this->cmd = CMD_LOGOUTRESULT;
		this->date_length = sizeof(LogoutResult); 
	}
	int Result;
};
//包5 新用户登入 传输通告 
struct NewUserJoin : public DataHeader 
{
    
    
	NewUserJoin()//初始化包头 
	{
    
    
		this->cmd = CMD_NEW_USER_JOIN;
		this->date_length = sizeof(NewUserJoin); 
	}
	char UserName[32];//用户名 
};

#include<bits/stdc++.h>

class TcpServer
{
    
    
public:
	//构造 
	TcpServer()
	{
    
    
		_sock = INVALID_SOCKET; 
	}
	
	//析构 
	virtual ~TcpServer()
	{
    
    
		//关闭socket 
		CloseSocket();
	}
	
	//初始化socket 返回1为正常 
	int InitSocket()
	{
    
    
#ifdef _WIN32
		//启动windows socket 2,x环境 
		WORD ver = MAKEWORD(2,2); 
		WSADATA dat;
		if(0 != WSAStartup(ver,&dat))
		{
    
    
			return -1;//-1为环境错误 
		}
#endif 
		//创建socket 
		if(INVALID_SOCKET != _sock)
		{
    
    
			printf("<Socket=%d>关闭连接\n",_sock); 
			CloseSocket();//如果之前有连接 就关闭连接 
		}
		_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 
		if(INVALID_SOCKET == _sock)
		{
    
       
			return 0;//0为socket创建错误 
		} 
		return 1;
	} 
	
	//绑定IP/端口
	int Bind(const char* ip,unsigned short port)
	{
    
    
		//如果为无效套接字 则初始化 
		if(INVALID_SOCKET == _sock)
		{
    
    
			InitSocket(); 
		}
		//绑定网络端口和IP地址 
		sockaddr_in _myaddr = {
    
    }; 
		_myaddr.sin_family = AF_INET;//IPV4
		_myaddr.sin_port = htons(port);//端口
#ifdef _WIN32
		if(ip)//ip为空则监听所有网卡 
		{
    
    
			_myaddr.sin_addr.S_un.S_addr = inet_addr(ip);//IP
		} 
		else
		{
    
    
			_myaddr.sin_addr.S_un.S_addr = INADDR_ANY;//IP
		}
#else
		if(ip)//ip为空则监听所有网卡
		{
    
    
			_myaddr.sin_addr.s_addr = inet_addr(ip);//IP 
		}
		else
		{
    
    
			_myaddr.sin_addr.s_addr = INADDR_ANY;//IP 
		}
#endif
		if(SOCKET_ERROR == bind(_sock,(sockaddr*)&_myaddr,sizeof(sockaddr_in)))//socket (强制转换)sockaddr结构体 结构体大小 
		{
    
    
			printf("绑定失败\n");
			return 0;
		}
		else
		{
    
    
			printf("绑定成功\n绑定端口为%d\n",port);
			return 1;
		}
	}
	
	//监听端口
	int Listen(int n)
	{
    
    
		//如果为无效套接字 则提示 
		if(INVALID_SOCKET == _sock)
		{
    
    
			printf("请先初始化套接字并绑定IP端口\n");
			return 0;	
		}
		//监听网络端口
		if(SOCKET_ERROR == listen(_sock,n))//最大连接队列 
		{
    
    
			printf("监听失败\n");
			return 0;
		}
		else
		{
    
    
			printf("监听成功\n");
			return 1; 
		}
	}
	
	//接受连接
	int Accept()
	{
    
    
		//等待接收客户端连接
		sockaddr_in _clientAddr = {
    
    };//新建sockadd结构体接收客户端数据 
		int _addr_len = sizeof(sockaddr_in);//获取sockadd结构体长度 
		SOCKET _temp_socket = INVALID_SOCKET;//声明客户端套接字 
#ifdef _WIN32	
		_temp_socket = accept(_sock,(sockaddr*)&_clientAddr,&_addr_len);//自身套接字 客户端结构体 结构体大小 	
#else
		_temp_socket = accept(_sock,(sockaddr*)&_clientAddr,(socklen_t*)&_addr_len);//自身套接字 客户端结构体 结构体大小 
#endif
		if(INVALID_SOCKET == _temp_socket)//接收失败 
		{
    
    
			printf("<Socket=%d>错误,接受到无效客户端SOCKET\n",_temp_socket);
			return 0;
		}
		else
		{
    
     
			printf("新客户端加入\nIP地址为:%s \n", inet_ntoa(_clientAddr.sin_addr));  
			//群发所有客户端 通知新用户登录 
			NewUserJoin _user_join; 
			strcpy(_user_join.UserName,inet_ntoa(_clientAddr.sin_addr));
			for(int n=0;n<_clients.size();++n)
			{
    
    
				send(_clients[n],(const char*)&_user_join,sizeof(NewUserJoin),0);	
			} 
			//将新的客户端加入动态数组
			_clients.push_back(_temp_socket); 
			return 1;
		} 
	} 
	
	//关闭socket 
	void CloseSocket()
	{
    
    
		if(INVALID_SOCKET != _sock) 
		{
    
    
#ifdef _WIN32
			//关闭客户端socket
			for(int n=0; n<_clients.size(); ++n)
			{
    
    
				closesocket(_clients[n]);
			}
			//关闭socket
			closesocket(_sock); 
			//清除windows socket 环境 
			WSACleanup(); 
#else
			//关闭客户端socket
			for(int n=0; n<_clients.size(); ++n)
			{
    
    
				close(_clients[n]);
			}
			//关闭socket/LINUX
			close(_sock);
#endif
			_sock = INVALID_SOCKET;
		}
	} 
	
	//查询是否有待处理消息 
	bool OnRun()
	{
    
    
		if(IsRun())
		{
    
    
			fd_set _fdRead;//建立集合 
			fd_set _fdWrite;
			fd_set _fdExcept;
			FD_ZERO(&_fdRead);//清空集合 
			FD_ZERO(&_fdWrite); 
			FD_ZERO(&_fdExcept); 
			FD_SET(_sock,&_fdRead);//放入集合 
			FD_SET(_sock,&_fdWrite); 
			FD_SET(_sock,&_fdExcept);
			timeval _t = {
    
    2,0};//select最大响应时间 
			SOCKET _maxSock = _sock;//最大socket 
			//把连接的客户端 放入read集合 
			for(int n=_clients.size()-1; n>=0; --n)
			{
    
    
				FD_SET(_clients[n],&_fdRead);
				if(_maxSock < _clients[n])
				{
    
    
					_maxSock = _clients[n];
				}
			}
			//select函数筛选select 
			int _ret = select(_maxSock+1,&_fdRead,&_fdWrite,&_fdExcept,&_t); 
			if(_ret<0)
			{
    
    
				printf("select任务结束\n");
				CloseSocket();
				return false;
			}
			if(FD_ISSET(_sock,&_fdRead))//获取是否有新socket连接 
			{
    
    
				FD_CLR(_sock,&_fdRead);//清理
				Accept();//连接 
			}
			//遍历所有socket 查看是否有待处理事件 
			for(int n=0; n<_clients.size(); ++n)
			{
    
    
				if(FD_ISSET(_clients[n],&_fdRead))
				{
    
    
					if(-1 == RecvData(_clients[n]))//处理请求 客户端退出的话 
					{
    
    
						std::vector<SOCKET>::iterator iter = _clients.begin()+n;//找到退出客户端的地址
						if(iter != _clients.end())//如果是合理值
						{
    
    
							_clients.erase(iter);//移除
						}
					}
				}
			}
			//printf("空闲时间处理其他业务\n");
			return true;
		}
		return false;
	} 
	
	//判断是否工作中 
	bool IsRun()
	{
    
    
		return _sock != INVALID_SOCKET; 
	}
	
	//发送数据 
	int SendData(DataHeader *_head,SOCKET _temp_socket)
	{
    
    
		if(IsRun() && _head)
		{
    
    
			send(_temp_socket,(const char*)_head,_head->date_length,0);
			return 1;
		}
		return 0;
	}
	
	//接收数据
	int RecvData(SOCKET _temp_socket)//处理数据 
	{
    
    
		//缓冲区
		char buffer[4096] = {
    
    }; 
		//接收客户端发送的数据  
		int _buf_len = recv(_temp_socket,buffer,sizeof(DataHeader),0);
		DataHeader *_head = (DataHeader*)buffer;
		if(_buf_len<=0)
		{
    
    
			printf("客户端已退出\n");
			return -1;
		} 
		recv(_temp_socket,buffer+sizeof(DataHeader),_head->date_length-sizeof(DataHeader),0);
		//响应数据
		NetMsg(_head,_temp_socket); 
		return 0;
	}
	
	//响应数据
	void NetMsg(DataHeader *_head,SOCKET _temp_socket)
	{
    
    
	 	printf("接收到包头,命令:%d,数据长度:%d\n",_head->cmd,_head->date_length);
		switch(_head->cmd)
		{
    
    
			case CMD_LOGIN://登录 接收登录包体 
			{
    
    
				Login *_login = (Login*)_head;
				/*
				进行判断操作 
				*/
				printf("%s已登录\n密码:%s\n",_login->UserName,_login->PassWord); 
				LoginResult *_result = new LoginResult;	
				_result->Result = 1;
				SendData(_result,_temp_socket);
			}
			break;
			case CMD_LOGOUT://登出 接收登出包体 
			{
    
    
				Logout *_logout = (Logout*)_head;
				/*
				进行判断操作 
				*/
				printf("%s已登出\n",_logout->UserName); 
				LogoutResult *_result = new LogoutResult();
				_result->Result = 1;
				SendData(_result,_temp_socket);
			}
			break;
			default://错误 
			{
    
    
				_head->cmd = CMD_ERROR; 
				_head->date_length = 0; 
				SendData(_head,_temp_socket); 
			}
			break;
		}
	}
	 
private:
	SOCKET _sock; 
	std::vector<SOCKET> _clients;//储存客户端socket
};

#endif

server_test.cpp

#include"TcpServer.hpp"
 
int main() 
{
    
    
	printf("Welcome\n");
	
	//建立tcp对象 
	TcpServer *tcp1 = new TcpServer();
	//建立一个socket
	tcp1->InitSocket();
	//绑定端口和IP
	tcp1->Bind(NULL,8888);
	//监听
	tcp1->Listen(5);
	//循环 
	while(tcp1->IsRun())
	{
    
    
		tcp1->OnRun();
	}
	//关闭
	tcp1->CloseSocket();
	 
	printf("任务结束,程序已退出"); 
	getchar(); 
	
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_45698148/article/details/113191673