笔记一:建立基础TCP服务端/客户端 点我跳转
笔记二:网络数据报文的收发 点我跳转
笔记三:升级为select网络模型 点我跳转
笔记四:跨平台支持Windows、Linux系统 点我跳转
笔记五:源码的封装 点我跳转
笔记六:缓冲区溢出与粘包分包 点我跳转
笔记七:服务端多线程分离业务处理高负载 点我跳转
C++为面向对象编程语言,我们要以面向对象的思路进行源码的编写。
在对主要源码进行封装后,客户端与服务端的代码编写更加清晰明了,逻辑性更强,便于开发维护。且在今后的服务端高并发测试中,便于新建多个连接进行测试。
在本篇笔记中,我会基于笔记四的源码进行封装,并将记录我对客户端与服务端源码进行封装时的思路与步骤。最终源码为客户端封装类文件TcpClient.hpp与服务端封装类文件TcpServer.hpp,以及客户端源码client_test.cpp与服务端源码server_test.cpp。
封装类首先要在头文件中以体现封装性。在本次的封装中,为了能更方便的储存,我选择了hpp头文件。即类声明与类定义都在此文件中。
首先,客户端的大致流程如下:
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连接操作或是关闭套接字操作、传入数据有误等等,此时就会出现问题。
我解决此类问题的方法是多加判定。例如判定套接字是否已经被建立,或是传入数据是否有误等等,随后根据情况进行处理。详细源码请看下文。
首先,客户端的大致流程如下:
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或是关闭套接字操作、传入数据有误等等,此时就会出现问题。
我解决此类问题的方法是多加判定。例如判定套接字是否已经被建立,或是传入数据是否有误等等,随后根据情况进行处理。详细源码请看下文。
#ifndef _TcpClient_hpp_
#define _TcpClient_hpp_
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include
#include
#pragma comment(lib,"ws2_32.lib")
#else
#include
#include
#include
#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
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("关闭连接\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
#include"TcpClient.hpp"
#include
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;
}
#ifndef _TcpServer_hpp_
#define _TcpServer_hpp_
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include
#include
#pragma comment(lib,"ws2_32.lib")//链接此动态链接库 windows特有
#else
#include //selcet
#include //uni std
#include
#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
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("关闭连接\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\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
#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;
}