網路編程學習記錄
- 使用的語言為C/C++
- 原始碼支持的平臺為:Windows / Linux
筆記一:建立基礎TCP服務端/客戶端 ?點我跳轉
筆記二:網路資料報文的收發 ?點我跳轉
筆記三:升級為select網路模型 ?點我跳轉
筆記四:跨平臺支持Windows、Linux系統 ?點我跳轉
筆記五:原始碼的封裝 ?點我跳轉
筆記六:緩沖區溢位與粘包分包 ?點我跳轉
筆記七:服務端多執行緒分離業務處理高負載 ?點我跳轉
筆記七
- 網路編程學習記錄
- 一、思路與準備
- 二、代碼的改進
- 1.新建子執行緒類
- 2.客戶端主執行緒類的更改
- 3.引入介面,實作子執行緒向主執行緒通信
- 三、詳細代碼實作
- 1.計時器頭檔案 `mytimer.hpp`
- 2.命令頭檔案 `CMD.h`
- 3.服務端頭檔案 `TcpServer.hpp`
- 4.服務端樣例代碼 `server.cpp`
一、思路與準備
之前的服務端思路大概是如下的:
1.建立socket
2.系結埠IP
3.監聽埠
while(true)
{
4.使用select函式獲取存在待監聽事件的socket
5.如果有新的連接則與新的客戶端連接
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 : INetEvent
2.隨后實作介面中的虛方法:
//客戶端退出
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
//關閉客戶端socket
for(int n=0; n<_clients.size(); ++n)
{
closesocket(_clients[n]->GetSockfd());
delete _clients[n];
}
//關閉socket
closesocket(_sock);
//清除windows socket 環境
WSACleanup();
#else
//關閉客戶端socket
for(int n=0; n<_clients.size(); ++n)
{
close(_clients[n]->GetSockfd());
delete _clients[n];
}
//關閉socket/LINUX
close(_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 _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("新客戶端加入 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
//關閉客戶端socket
for(int n=0; n<_clients.size(); ++n)
{
closesocket(_clients[n]->GetSockfd());
delete _clients[n];
}
//關閉socket
closesocket(_sock);
//清除windows socket 環境
WSACleanup();
#else
//關閉客戶端socket
for(int n=0; n<_clients.size(); ++n)
{
close(_clients[n]->GetSockfd());
delete _clients[n];
}
//關閉socket/LINUX
close(_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();
//建立一個socket
tcp1->InitSocket();
//系結埠和IP
tcp1->Bind(NULL,8888);
//監聽
tcp1->Listen(5);
//執行緒啟動
tcp1->Start();
//回圈
while(tcp1->IsRun())
{
tcp1->OnRun();
}
//關閉
tcp1->CloseSocket();
printf("任務結束,程式已退出");
getchar();
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/257781.html
標籤:其他
上一篇:什么是 geobuf?
