网络编程学习记录

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

笔记一:建立基础TCP服务端/客户端  点我跳转
笔记二:网络数据报文的收发  点我跳转
笔记三:升级为select网络模型  点我跳转
笔记四:跨平台支持Windows、Linux系统  点我跳转
笔记五:源码的封装  点我跳转
笔记六:缓冲区溢出与粘包分包  点我跳转
笔记七:服务端多线程分离业务处理高负载  点我跳转
笔记八:对socket select网络模型的优化  点我跳转
笔记九:消息接收与发送分离  点我跳转
笔记十:项目化 (加入内存池静态库 / 报文动态库)  更多笔记请点我


笔记七

  • 网络编程学习记录
  • 一、思路与准备
  • 二、代码的改进
    • 1.新建子线程类
    • 2.客户端主线程类的更改
    • 3.引入接口,实现子线程向主线程通信
  • 三、详细代码实现
    • 1.计时器头文件 `mytimer.hpp`
    • 2.命令头文件 `CMD.h`
    • 3.服务端头文件 `TcpServer.hpp`
    • 4.服务端样例代码 `server.cpp`

一、思路与准备

之前的服务端思路大概是如下的:

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

  但是,这样的架构在select处理事件较多时,很容易效率低下。对于这类问题,我们可以引入生产者与消费者模式,来处理此类并发问题。
  主线程为生产者线程,用来处理新客户端加入事件,把新客户端分配至消费者线程中。消费者线程便是我们建立的新线程,专门用来处理客户端发送的报文。这样就实现了事件处理的分离,从而使服务端处理效率更高。当过多客户端同时涌入时,可以更快的建立连接(因为有专门的线程用来处理这一事件);而当客户端发送报文频率很快时,多线程处理报文也会大大提高效率。

  • 大致改进思路如下,红色的为此次需要加入的核心,黑色为原本架构

  所以我们首先需要新建一个线程类,用来封装关于消费者线程的内容,从而建立多线程架构。随后,在本次的改进中,我决定加入计时器用来统计数据以及显示数据,主要需要统计的数据为:当前客户端连接数量、数据包的每秒接收数量。同时,我也对报文类型进行了分离,把报文类型放在单独的头文件里,这样既方便更改也方便引用。

  • 1.计时器相关请点这里    2.多线程相关请点这里

二、代码的改进

1.新建子线程类

  • 首先是新建线程类CellServer,其中包含的基础方法以及相关变量如下:
//线程类
class CellServer
{public://构造 CellServer(SOCKET sock = INVALID_SOCKET);//析构~CellServer();//关闭socket void CloseSocket();//判断是否工作中 bool IsRun();//查询是否有待处理消息 bool OnRun();//接收数据int RecvData(ClientSocket *t_client);//响应数据void NetMsg(DataHeader *head,SOCKET temp_socket);//增加客户端 void addClient(ClientSocket* client);//启动线程void Start(); //获取该线程内客户端数量int GetClientCount()private://缓冲区相关 char *_Recv_buf;//接收缓冲区 //socket相关 SOCKET _sock; //正式客户队列 std::vector<ClientSocket*> _clients;//储存客户端//客户缓冲区队列std::vector<ClientSocket*> _clientsBuf; std::mutex _mutex;//锁//线程 std::thread* _pThread;public:std::atomic_int _recvCount;//接收包的数量
};
  • 大致处理思路如下:
线程外:
Start() 首先调用该方法启动线程新客户端加入:
GetClientCount() 首先主线程使用这个方法获取各个线程内客户端数量
//这个添加客户端的方法内涉及到临界区,需要上锁
addClient(ClientSocket* client) 主线程找到客户端数量最少的线程,使用该线程添加客户端至缓冲队列线程内:
OnRun()//运行线程
{while(IsRun())//判断是否工作中{1.将缓冲队列中的客户数据加入正式队列2.正式客户队列为空的话,continue本次循环3.select选择出待处理事件,错误的话就关闭所有连接CloseSocket()4.对待处理事件进行接收RecvData(),接收包的数量加一,随后处理NetMsg()}
}

2.客户端主线程类的更改

  由于我们处理事件都改为在子线程中,所以首先主线程中是不需要处理报文消息了,所以类中接收消息和处理消息的方法都可以删除了。同时我们加入Start方法用来启动子线程,加入time4msg方法用来显示子线程中的客户端数量、每秒收包数等数据。

  • 主线程类TcpServer,更改后如下:
class TcpServer : INetEvent
{public://构造 TcpServer();//析构 ~TcpServer();//初始化socket 返回1为正常 int InitSocket();//绑定IP/端口int Bind(const char* ip,unsigned short port);//监听端口int Listen(int n);//接受连接int Accept();//添加客户端至服务端  void AddClientToServer(ClientSocket* pClient);//线程启动 void Start();//关闭socket void CloseSocket();//判断是否工作中 bool IsRun();//查询是否有待处理消息 bool OnRun();//显示各线程数据信息 void time4msg();private://socket相关 SOCKET _sock; std::vector<ClientSocket*> _clients;//储存客户端std::vector<CellServer*> _cellServers;//子线程们 //计时器mytimer _time;
};
  • 大致处理思路如下:计时器相关请点这里
调用TcpServer封装类建立服务端的流程:
1.InitSocket() 建立一个socket
2.Bind(const char* ip,unsigned short port) 绑定端口和IP
3.Listen(int n) 监听
4.Start() 线程启动
while(5.IsRun()) 主线程循环
{6.OnRun() 开始select选择处理事件
}
7.CloseSocket() 关闭socket主线程内:
OnRun()
{time4msg()显示数据 select选择出新客户端加入事件如果有新客户端加入,调用Accept()接受连接Accept()连接成功后,调用AddClientToServer(ClientSocket* pClient)分配客户端到子线程中
}AddClientToServer()内:
首先调用子线程的GetClientCount()方法获取各条子线程中的客户端数量
随后调用子线程的addClient(ClientSocket* client)方法,把新客户端添加至客户端最少的线程中time4msg()内:
首先GetSecond()获取计时器的计时
如果大于一秒,就统计客户端的情况:子线程内_recvCount为收包数,主线程内_clients.size()获取客户端数量
显示后UpDate()重置计时器,并且重置收包数,从而达到统计每秒收包数的作用

3.引入接口,实现子线程向主线程通信

  通过前两步的实现,多线程服务端已经初步完成,接下来需要进行一些完善。
  我们很容易可以发现,子线程对象是在主线程Start()方法被创建的,随后被加入容器_cellServers储存。这就导致主线程中可以调用子线程类中的方法与成员变量,但是子线程中却无法调用主线程的方法与成员变量。从而导致当子线程中有客户端退出时,主线程无法了解。
  对于这种情况,我们可以创建一个接口,让主线程类继承这个接口,子线程即可通过这个接口调用主线程中的特定方法。

  • 接口类INetEvent如下:
class INetEvent
{public://有客户端退出 virtual void OnLeave(ClientSocket* pClient) = 0;
private:
};
  • 主线程类与子线程类中的相关实现:
1.首先是主线程类继承该接口:
class TcpServer : INetEvent2.随后实现接口中的虚方法:
//客户端退出
void OnLeave(ClientSocket* pClient)
{//找到退出的客户端 for(int n=0; n<_clients.size(); n++){if(_clients[n] == pClient){auto iter = _clients.begin() + n;if(iter != _clients.end()){_clients.erase(iter);//移除 }}}
}
即可实现调用该方法,移除客户端容器中指定客户端3.随后在子线程类中添加私有成员变量:
private:INetEvent* _pNetEvent;
创建接口对象4.创建方法,让接口对象指向主线程类
void setEventObj(INetEvent* event)
{_pNetEvent = event;
}
event传进去主线程即可,接口对象即指向主线程5.主线程创建、启动子线程类时,调用该方法,传入自身this
子线程对象->setEventObj(this);6.随后即可通过子线程调用主线程的OnLeave()方法删除客户端
_pNetEvent->OnLeave(要删除的客户端);

三、详细代码实现

1.计时器头文件 mytimer.hpp

#ifndef MY_TIMER_H_
#define MY_TIMER_H_#include<chrono>class mytimer
{private:std::chrono::steady_clock::time_point _begin;//起始时间std::chrono::steady_clock::time_point _end;//终止时间
public:mytimer(){_begin = std::chrono::steady_clock::time_point();_end = std::chrono::steady_clock::time_point();}virtual ~mytimer(){};  //调用update时,使起始时间等于当前时间void UpDate(){_begin = std::chrono::steady_clock::now();}//调用getsecond方法时,经过的时间为当前时间减去之前统计过的起始时间。double GetSecond(){_end = std::chrono::steady_clock::now();//使用duration类型变量进行时间的储存   duration_cast是类型转换方法std::chrono::duration<double> temp = std::chrono::duration_cast<std::chrono::duration<double>>(_end - _begin);return temp.count();//count() 获取当前时间的计数数量}};#endif

2.命令头文件 CMD.h

//枚举类型记录命令
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];//用户名
};

3.服务端头文件 TcpServer.hpp

#ifndef _TcpServer_hpp_
#define _TcpServer_hpp_#ifdef _WIN32#define FD_SETSIZE 10240 #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#include"CMD.h"//命令
#include"mytimer.hpp"//计时器
#include<stdio.h>
#include<vector>
#include<thread>
#include<mutex>
#include<atomic>//缓冲区大小
#ifndef RECV_BUFFER_SIZE#define RECV_BUFFER_SIZE 4096
#endif //线程数量
#define _THREAD_COUNT 4//客户端类
class ClientSocket
{public://构造 ClientSocket(SOCKET sockfd = INVALID_SOCKET){_sockfd = sockfd;//缓冲区相关 _Msg_buf = new char[RECV_BUFFER_SIZE*10];_Len_buf = 0; }//析构 virtual ~ClientSocket(){delete[] _Msg_buf;}//获取socket SOCKET GetSockfd(){return _sockfd;}//获取缓冲区 char* MsgBuf(){return _Msg_buf;} //获取缓冲区尾部变量 int GetLen(){return _Len_buf;  } //设置缓冲区尾巴变量void SetLen(int len){_Len_buf = len;} private:    SOCKET _sockfd;//缓冲区相关 char *_Msg_buf;//消息缓冲区 int _Len_buf;//缓冲区数据尾部变量
}; //事件接口
class INetEvent
{public://有客户端退出 virtual void OnLeave(ClientSocket* pClient) = 0;
private:
};//线程类
class CellServer
{public://构造 CellServer(SOCKET sock = INVALID_SOCKET){_sock = sock; _pThread = nullptr;_pNetEvent = nullptr;_recvCount = 0; //缓冲区相关 _Recv_buf = new char[RECV_BUFFER_SIZE];}//析构~CellServer(){delete[] _Recv_buf;//关闭socket CloseSocket();_sock = INVALID_SOCKET;} //处理事件 void setEventObj(INetEvent* event){_pNetEvent = event; }//关闭socket void CloseSocket(){if(INVALID_SOCKET != _sock) {#ifdef _WIN32//关闭客户端socketfor(int n=0; n<_clients.size(); ++n){closesocket(_clients[n]->GetSockfd());delete _clients[n];}//关闭socketclosesocket(_sock); //清除windows socket 环境 WSACleanup();
#else//关闭客户端socketfor(int n=0; n<_clients.size(); ++n){close(_clients[n]->GetSockfd());delete _clients[n];}//关闭socket/LINUXclose(_sock);
#endif_sock = INVALID_SOCKET;_clients.clear();}} //判断是否工作中 bool IsRun(){return _sock != INVALID_SOCKET; }//查询是否有待处理消息 bool OnRun(){while(IsRun()){//将缓冲队列中的客户数据加入正式队列 if(_clientsBuf.size() > 0){std::lock_guard<std::mutex> lock(_mutex);//上锁 for(auto client :_clientsBuf){_clients.push_back(client);}_clientsBuf.clear();} //如果没有需要处理的客户端就跳过 if(_clients.empty()){std::chrono::milliseconds t(1);//休眠一毫秒 std::this_thread::sleep_for(t);continue;}fd_set fdRead;//建立集合 FD_ZERO(&fdRead);//清空集合 SOCKET maxSock = _clients[0]->GetSockfd();//最大socket //把连接的客户端 放入read集合 for(int n=_clients.size()-1; n>=0; --n){FD_SET(_clients[n]->GetSockfd(),&fdRead);if(maxSock < _clients[n]->GetSockfd()){maxSock = _clients[n]->GetSockfd();}}//select函数筛选select int ret = select(maxSock+1,&fdRead,0,0,0); if(ret<0){printf("select任务结束\n");CloseSocket();return false;}//遍历所有socket 查看是否有待处理事件 for(int n=0; n<_clients.size(); ++n){if(FD_ISSET(_clients[n]->GetSockfd(),&fdRead)){if(-1 == RecvData(_clients[n]))//处理请求 客户端退出的话 {std::vector<ClientSocket*>::iterator iter = _clients.begin()+n;//找到退出客户端的地址if(iter != _clients.end())//如果是合理值{if(_pNetEvent)//主线程中删除客户端 {_pNetEvent->OnLeave(_clients[n]);}delete _clients[n];_clients.erase(iter);//移除}}}}//printf("空闲时间处理其他业务\n");}} //接收数据int RecvData(ClientSocket *t_client)//处理数据 { _recvCount++;//收包数量加一 //接收客户端发送的数据  int buf_len = recv(t_client->GetSockfd(), _Recv_buf, RECV_BUFFER_SIZE, 0);if(buf_len<=0){printf("客户端已退出\n");return -1;} //将接收缓冲区的数据拷贝到消息缓冲区 memcpy(t_client->MsgBuf() + t_client->GetLen(), _Recv_buf, buf_len); //消息缓冲区的数据末尾后移 t_client->SetLen(t_client->GetLen() + buf_len);//判断消息缓冲区的数据长度是否大于等于包头长度 while(t_client->GetLen() >= sizeof(DataHeader))//处理粘包问题 {//选出包头数据 DataHeader* header = (DataHeader*)t_client->MsgBuf(); //判断消息缓冲区内数据长度是否大于等于报文长度 避免少包问题 if(t_client->GetLen() >= header->date_length){//计算出消息缓冲区内剩余未处理数据的长度int size = t_client->GetLen() - header->date_length; //响应数据 NetMsg(header,t_client->GetSockfd());//将消息缓冲区剩余未处理的数据前移memcpy(t_client->MsgBuf(), t_client->MsgBuf() + header->date_length, size);//消息缓冲区的数据末尾前移t_client->SetLen(size); } else{//消息缓冲区数据不足 break; } }  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;}}//增加客户端 void addClient(ClientSocket* client){std::lock_guard<std::mutex> lock(_mutex);//_mutex.lock();_clientsBuf.push_back(client);   //_mutex.unlock();} //启动线程void Start(){_pThread = new std::thread(std::mem_fun(&CellServer::OnRun),this);} //获取该线程内客户端数量int GetClientCount(){return _clients.size() + _clientsBuf.size(); } private://缓冲区相关 char *_Recv_buf;//接收缓冲区 //socket相关 SOCKET _sock; //正式客户队列 std::vector<ClientSocket*> _clients;//储存客户端//客户缓冲区std::vector<ClientSocket*> _clientsBuf; std::mutex _mutex;//锁//线程 std::thread* _pThread;//退出事件接口 INetEvent* _pNetEvent; public:std::atomic_int _recvCount;//接收包的数量
};//服务端类
class TcpServer : INetEvent
{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 _WIN32if(ip)//ip为空则监听所有网卡 {_myaddr.sin_addr.S_un.S_addr = inet_addr(ip);//IP} else{_myaddr.sin_addr.S_un.S_addr = INADDR_ANY;//IP}
#elseif(ip)//ip为空则监听所有网卡{_myaddr.sin_addr.s_addr = inet_addr(ip);//IP }else{_myaddr.sin_addr.s_addr = INADDR_ANY;//IP }
#endifif(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);//自身套接字 客户端结构体 结构体大小
#elsetemp_socket = accept(_sock,(sockaddr*)&clientAddr,(socklen_t*)&addr_len);//自身套接字 客户端结构体 结构体大小
#endifif(INVALID_SOCKET == temp_socket)//接收失败 {printf("<Socket=%d>错误,接受到无效客户端SOCKET\n",temp_socket);return 0;}else{ //printf("新客户端加入 count: %d\nIP地址为:%s \n", _clients.size(), inet_ntoa(clientAddr.sin_addr));  //群发所有客户端 通知新用户登录 //NewUserJoin *user_join = new NewUserJoin(); //strcpy(user_join->UserName,inet_ntoa(clientAddr.sin_addr));//SendDataToAll(user_join);//将新的客户端加入动态数组AddClientToServer(new ClientSocket(temp_socket));return 1;} } //添加客户端至服务端  void AddClientToServer(ClientSocket* pClient){_clients.push_back(pClient);//找出客户端最少的线程 然后加入 auto pMinServer = _cellServers[0];for(auto pCellServer : _cellServers){if(pMinServer->GetClientCount() > pCellServer->GetClientCount()){pMinServer = pCellServer;} }pMinServer->addClient(pClient);}//线程启动 void Start(){for(int n=0; n<_THREAD_COUNT; n++){//线程加入容器 auto ser = new CellServer(_sock); _cellServers.push_back(ser);ser->setEventObj(this);ser->Start(); }}//关闭socket void CloseSocket(){if(INVALID_SOCKET != _sock) {#ifdef _WIN32//关闭客户端socketfor(int n=0; n<_clients.size(); ++n){closesocket(_clients[n]->GetSockfd());delete _clients[n];}//关闭socketclosesocket(_sock); //清除windows socket 环境 WSACleanup();
#else//关闭客户端socketfor(int n=0; n<_clients.size(); ++n){close(_clients[n]->GetSockfd());delete _clients[n];}//关闭socket/LINUXclose(_sock);
#endif_sock = INVALID_SOCKET;_clients.clear();}} //判断是否工作中 bool IsRun(){return _sock != INVALID_SOCKET; }//查询是否有待处理消息 bool OnRun(){if(IsRun()){time4msg();//查看各线程数据信息 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 s_t = {0,0};//select最大响应时间 //select函数筛选select int ret = select(_sock+1,&fdRead,0,0,&s_t); if(ret<0){printf("select任务结束\n");CloseSocket();return false;}if(FD_ISSET(_sock,&fdRead))//获取是否有新socket连接 {FD_CLR(_sock,&fdRead);//清理Accept();//连接 }return true;}return false;} //显示各线程数据信息 void time4msg(){auto t1 = _time.GetSecond();if(1.0 <= t1){int recvCount = 0;for(auto ser: _cellServers){recvCount += ser->_recvCount;ser->_recvCount = 0; }//时间间隔  本机socket连接序号  客户端数量  每秒收包数 printf("time<%lf>,socket<%d>,clients<%d>,recvCount<%d>\n", t1, _sock, _clients.size(),(int)(recvCount/t1));_time.UpDate();}} //发送数据 int SendData(DataHeader *head,SOCKET temp_socket){if(IsRun() && head){send(temp_socket,(const char*)head,head->date_length,0);return 1;}return 0;}//向所有人发送数据void SendDataToAll(DataHeader *head){for(int n=0;n<_clients.size();++n){SendData(head, _clients[n]->GetSockfd());   } } //客户端退出void OnLeave(ClientSocket* pClient){//找到退出的客户端 for(int n=0; n<_clients.size(); n++){if(_clients[n] == pClient){auto iter = _clients.begin() + n;if(iter != _clients.end()){_clients.erase(iter);//移除 }}} } private://socket相关 SOCKET _sock; std::vector<ClientSocket*> _clients;//储存客户端std::vector<CellServer*> _cellServers;//线程处理 //计时器mytimer _time;
};#endif

4.服务端样例代码 server.cpp

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

C++网络编程学习:服务端多线程分离业务处理高负载相关推荐

  1. Qt:Qt实现Winsock网络编程—TCP服务端和客户端通信(多线程)

    Qt实现Winsock网络编程-TCP服务端和客户端通信(多线程) 前言 感觉Winsock网络编程的api其实和Linux下网络编程的api非常像,其实和其他编程语言的网络编程都差不太多.博主用Qt ...

  2. QT网络编程开发服务端

    下一篇: QT网络编程开发客户端 文章目录 基于Qt的网络编程服务端 QTcpServer 配置 listen() close() newConnection() SINGL readyRead() ...

  3. C++网络编程学习:网络数据报文的收发

    网络编程学习记录 使用的语言为C/C++ 源码支持的平台为:Windows 笔记一:建立基础TCP服务端/客户端  点我跳转 笔记二:网络数据报文的收发  点我跳转 笔记三:升级为select网络模型 ...

  4. 谈一谈网络编程学习经验(陈硕)

    作者:陈硕  原文地址:http://blog.csdn.net/solstice/article/details/6527585 本文谈一谈我在学习网络编程方面的一些个人经验."网络编程& ...

  5. 网络协议和Netty——第二章 Java原生网络编程学习笔记

    编程中的Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说, ...

  6. 华清远见网络编程学习总结

    这周进行了两天半的网络编程学习和两天来做自己的项目. 首先是学习了UDP(UDP:不保证可靠的无连接协议,在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输)的服务端和客户端.其适用的情况 ...

  7. Java网络编程学习——简单模拟在线聊天

    Java网络编程学习--简单模拟在线聊天 学了java网络,也是该做个小案例来巩固一下了. 本次案例将使用UDP和多线程模拟即时聊天,简单练练手. 1.前提知识 需要知道简单的IO流操作,以及简单的U ...

  8. 谈一谈网络编程学习经验

    转自  陈硕 giantchen@gmail.com blog.csdn.net/Solstice 2011-06-06 PDF 版下载:https://github.com/downloads/ch ...

  9. 谈一谈网络编程学习经验(06-08更新)

    谈一谈网络编程学习经验 陈硕 giantchen@gmail.com blog.csdn.net/Solstice 2011-06-08 PDF 版下载:https://github.com/down ...

最新文章

  1. 线性表List的基本创建
  2. 为什么pytorch mode = sequential() 为何model(input)这样调用就直接执行了forward
  3. Python Web 框架:Django MVC搭建
  4. 乾坤 微前端_前端优秀资源整理(持续更新~)
  5. 在AIX上空闲卷上重建文件系统
  6. 电脑越来越慢怎么办_电脑维修|你的电脑肯定遇到过这些故障
  7. Spring Boot 之路(一):一个简单的Spring Boot应用
  8. 转 Cocos2d-x3.0模版容器详解之三:cocos2d::Value
  9. AttributeError: partially initialized module ‘aiohttp‘ has no attribute ‘ClientSession‘ (most...)
  10. java小球碰撞实验报告_20155317 《Java程序设计》实验五网络编程与安全实验报告...
  11. www.cnblog.org无法访问了
  12. 华为估值知多少?倪光南:位居世界第一应该没问题
  13. java中d怎样转换D,如何将ZonedDateTime转换为date?
  14. Golang的context理解
  15. php-java-bridge 作用_PHP-Java-Bridge的使用(平安银行支付功能专版)
  16. windows 任务管理中各个内存项的含义
  17. [转]QNX系统开发-镜像制作及烧录分析
  18. 王垠面阿里P9,面跪后与P10赵海平互怼:人性最大的愚蠢,是互相为难
  19. lighttpd和php关系,Lighttpd是什么
  20. 妄想性仮想人格障害 汉化补丁(BUG修正)

热门文章

  1. SyntaxHighlighter代码高亮插件
  2. 如何让电脑文件与手机同步?
  3. iOS 交易支付密码(多种样式选择)~ 封装demo
  4. 删除文件夹及文件夹里的文件
  5. 在word2010中启用文本朗读功能
  6. python最强书籍_手机下载了那么多 Python 书却从不看?最强阅读器推荐给你
  7. 计算机中硬盘上删了的东西为什么还可以恢复sd卡可以吗,怎么从SD卡中恢复误删文件...
  8. hashmap源码分析及常用方法测试_一点课堂(多岸学院)
  9. 【GitHub】在Github主页显示你的个人简历
  10. 谷歌浏览器如何清除当前页面的缓存