日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

粘包的原因分析及解决

發布時間:2025/4/5 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 粘包的原因分析及解决 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 1 粘包的原因分析
    • 2 客戶端解決粘包的問題
    • 3 服務端解決粘包的問題

1 粘包的原因分析

先看一下數據收發的示意圖:

我們之前每次只處理一幀數據,如果接收端的處理速度力和發送端的發送速度不匹配就會導致接收緩沖區滿的情況,這種情況下就會出現緩沖區溢出的情況,導致網絡阻塞。在實際的測試中,發現程序直接卡死,原因暫時無法得知!

我們這個時候就需要引入第二緩沖區,我們每次不再讀取一幀的數據長度,而是盡可能的將緩沖區的數據都進行讀取。在這種情況下,有可能出現粘包、少包的情況。我們就需要對粘包和少包進行處理。

粘包、少包的處理方式其實很簡單,把每次讀取到的數據都放到第二緩沖區,然后對第二緩沖區的數據進行處理即可。


2 客戶端解決粘包的問題

其實處理十分簡單,核心代碼如下:

//緩沖區最小單元大小 #ifndef RECV_BUFF_SZIE #define RECV_BUFF_SZIE 102400 #endif // !RECV_BUFF_SZIE//第二緩沖區 消息緩沖區char _szMsgBuf[RECV_BUFF_SZIE * 10] = {};//消息緩沖區的數據尾部位置int _lastPos = 0;//接收緩沖區char _szRecv[RECV_BUFF_SZIE] = {};//接收數據 處理粘包 拆分包int RecvData(SOCKET cSock){// 5 接收數據int nLen = (int)recv(cSock, _szRecv, RECV_BUFF_SZIE, 0);//printf("nLen=%d\n", nLen);if (nLen <= 0){printf("<socket=%d>與服務器斷開連接,任務結束。\n", cSock);return -1;}//將收取到的數據拷貝到消息緩沖區memcpy(_szMsgBuf+_lastPos, _szRecv, nLen);//消息緩沖區的數據尾部位置后移_lastPos += nLen;//判斷消息緩沖區的數據長度大于消息頭DataHeader長度while (_lastPos >= sizeof(DataHeader)){//這時就可以知道當前消息的長度DataHeader* header = (DataHeader*)_szMsgBuf;//判斷消息緩沖區的數據長度大于消息長度if (_lastPos >= header->dataLength){//消息緩沖區剩余未處理數據的長度int nSize = _lastPos - header->dataLength;//處理網絡消息OnNetMsg(header);//將消息緩沖區剩余未處理數據前移memcpy(_szMsgBuf, _szMsgBuf + header->dataLength, nSize);//消息緩沖區的數據尾部位置前移_lastPos = nSize;}else {//消息緩沖區剩余數據不夠一條完整消息break;}}return 0;}

注意:這種做法并不高效,每處理一個數據幀就需要移動數據,還有很大的改進空間。


3 服務端解決粘包的問題

思路一致:

EasyTcpServer.hpp:

#ifndef _EasyTcpServer_hpp_ #define _EasyTcpServer_hpp_#ifdef _WIN32#define WIN32_LEAN_AND_MEAN#define _WINSOCK_DEPRECATED_NO_WARNINGS#include<windows.h>#include<WinSock2.h>#pragma comment(lib,"ws2_32.lib") #else#include<unistd.h> //uni std#include<arpa/inet.h>#include<string.h>#define SOCKET int#define INVALID_SOCKET (SOCKET)(~0)#define SOCKET_ERROR (-1) #endif#include<stdio.h> #include<vector> #include"MessageHeader.hpp"//緩沖區最小單元大小 #ifndef RECV_BUFF_SZIE #define RECV_BUFF_SZIE 102400 #endif // !RECV_BUFF_SZIEclass ClientSocket { public:ClientSocket(SOCKET sockfd = INVALID_SOCKET){_sockfd = sockfd;memset(_szMsgBuf, 0, sizeof(_szMsgBuf));_lastPos = 0;}SOCKET sockfd(){return _sockfd;}char* msgBuf(){return _szMsgBuf;}int getLastPos(){return _lastPos;}void setLastPos(int pos){_lastPos = pos;} private:// socket fd_set file desc setSOCKET _sockfd;//第二緩沖區 消息緩沖區char _szMsgBuf[RECV_BUFF_SZIE * 10];//消息緩沖區的數據尾部位置int _lastPos; };class EasyTcpServer { private:SOCKET _sock;std::vector<ClientSocket*> _clients; public:EasyTcpServer(){_sock = INVALID_SOCKET;}virtual ~EasyTcpServer(){Close();}//初始化SocketSOCKET InitSocket(){ #ifdef _WIN32//啟動Windows socket 2.x環境WORD ver = MAKEWORD(2, 2);WSADATA dat;WSAStartup(ver, &dat); #endifif (INVALID_SOCKET != _sock){printf("<socket=%d>關閉舊連接...\n", (int)_sock);Close();}_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (INVALID_SOCKET == _sock){printf("錯誤,建立socket失敗...\n");}else {printf("建立socket=<%d>成功...\n", (int)_sock);}return _sock;}//綁定IP和端口號int Bind(const char* ip, unsigned short port){//if (INVALID_SOCKET == _sock)//{// InitSocket();//}// 2 bind 綁定用于接受客戶端連接的網絡端口sockaddr_in _sin = {};_sin.sin_family = AF_INET;_sin.sin_port = htons(port);//host to net unsigned short#ifdef _WIN32if (ip){_sin.sin_addr.S_un.S_addr = inet_addr(ip);}else {_sin.sin_addr.S_un.S_addr = INADDR_ANY;} #elseif (ip) {_sin.sin_addr.s_addr = inet_addr(ip);}else {_sin.sin_addr.s_addr = INADDR_ANY;} #endifint ret = bind(_sock, (sockaddr*)&_sin, sizeof(_sin));if (SOCKET_ERROR == ret){printf("錯誤,綁定網絡端口<%d>失敗...\n", port);}else {printf("綁定網絡端口<%d>成功...\n", port);}return ret;}//監聽端口號int Listen(int n){// 3 listen 監聽網絡端口int ret = listen(_sock, n);if (SOCKET_ERROR == ret){printf("socket=<%d>錯誤,監聽網絡端口失敗...\n",_sock);}else {printf("socket=<%d>監聽網絡端口成功...\n", _sock);}return ret;}//接受客戶端連接SOCKET Accept(){// 4 accept 等待接受客戶端連接sockaddr_in clientAddr = {};int nAddrLen = sizeof(sockaddr_in);SOCKET cSock = INVALID_SOCKET; #ifdef _WIN32cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen); #elsecSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t *)&nAddrLen); #endifif (INVALID_SOCKET == cSock){printf("socket=<%d>錯誤,接受到無效客戶端SOCKET...\n", (int)_sock);}else{NewUserJoin userJoin;SendDataToAll(&userJoin);_clients.push_back(new ClientSocket(cSock));printf("socket=<%d>新客戶端加入:socket = %d,IP = %s \n", (int)_sock, (int)cSock, inet_ntoa(clientAddr.sin_addr));}return cSock;}//關閉Socketvoid Close(){if (_sock != INVALID_SOCKET){ #ifdef _WIN32for (int n = (int)_clients.size() - 1; n >= 0; n--){closesocket(_clients[n]->sockfd());delete _clients[n];}// 8 關閉套節字closesocketclosesocket(_sock);//------------//清除Windows socket環境WSACleanup(); #elsefor (int n = (int)_clients.size() - 1; n >= 0; n--){close(_clients[n]->sockfd());delete _clients[n];}// 8 關閉套節字closesocketclose(_sock); #endif_clients.clear();}}//處理網絡消息int _nCount = 0;bool OnRun(){if (isRun()){//伯克利套接字 BSD socketfd_set fdRead;//描述符(socket) 集合fd_set fdWrite;fd_set fdExp;//清理集合FD_ZERO(&fdRead);FD_ZERO(&fdWrite);FD_ZERO(&fdExp);//將描述符(socket)加入集合FD_SET(_sock, &fdRead);FD_SET(_sock, &fdWrite);FD_SET(_sock, &fdExp);SOCKET maxSock = _sock;for (int n = (int)_clients.size() - 1; n >= 0; n--){FD_SET(_clients[n]->sockfd(), &fdRead);if (maxSock < _clients[n]->sockfd()){maxSock = _clients[n]->sockfd();}}///nfds 是一個整數值 是指fd_set集合中所有描述符(socket)的范圍,而不是數量///既是所有文件描述符最大值+1 在Windows中這個參數可以寫0timeval t = { 1,0 };int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t); ////printf("select ret=%d count=%d\n", ret, _nCount++);if (ret < 0){printf("select任務結束。\n");Close();return false;}//判斷描述符(socket)是否在集合中if (FD_ISSET(_sock, &fdRead)){FD_CLR(_sock, &fdRead);Accept();}for (int n = (int)_clients.size() - 1; n >= 0; n--){if (FD_ISSET(_clients[n]->sockfd(), &fdRead)){if (-1 == RecvData(_clients[n])){auto iter = _clients.begin() + n;//std::vector<SOCKET>::iteratorif (iter != _clients.end()){delete _clients[n];_clients.erase(iter);}}}}return true;}return false;}//是否工作中bool isRun(){return _sock != INVALID_SOCKET;}//緩沖區char _szRecv[RECV_BUFF_SZIE] = {};//接收數據 處理粘包 拆分包int RecvData(ClientSocket* pClient){// 5 接收客戶端數據int nLen = (int)recv(pClient->sockfd(), _szRecv, RECV_BUFF_SZIE, 0);//printf("nLen=%d\n", nLen);if (nLen <= 0){printf("客戶端<Socket=%d>已退出,任務結束。\n", pClient->sockfd());return -1;}//將收取到的數據拷貝到消息緩沖區memcpy(pClient->msgBuf() + pClient->getLastPos(), _szRecv, nLen);//消息緩沖區的數據尾部位置后移pClient->setLastPos(pClient->getLastPos() + nLen);//判斷消息緩沖區的數據長度大于消息頭DataHeader長度while (pClient->getLastPos() >= sizeof(DataHeader)){//這時就可以知道當前消息的長度DataHeader* header = (DataHeader*)pClient->msgBuf();//判斷消息緩沖區的數據長度大于消息長度if (pClient->getLastPos() >= header->dataLength){//消息緩沖區剩余未處理數據的長度int nSize = pClient->getLastPos() - header->dataLength;//處理網絡消息OnNetMsg(pClient->sockfd(),header);//將消息緩沖區剩余未處理數據前移memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize);//消息緩沖區的數據尾部位置前移pClient->setLastPos(nSize);}else {//消息緩沖區剩余數據不夠一條完整消息break;}}return 0;}//響應網絡消息virtual void OnNetMsg(SOCKET cSock, DataHeader* header){switch (header->cmd){case CMD_LOGIN:{Login* login = (Login*)header;//printf("收到客戶端<Socket=%d>請求:CMD_LOGIN,數據長度:%d,userName=%s PassWord=%s\n", cSock, login->dataLength, login->userName, login->PassWord);//忽略判斷用戶密碼是否正確的過程LoginResult ret;SendData(cSock, &ret);}break;case CMD_LOGOUT:{Logout* logout = (Logout*)header;//printf("收到客戶端<Socket=%d>請求:CMD_LOGOUT,數據長度:%d,userName=%s \n", cSock, logout->dataLength, logout->userName);//忽略判斷用戶密碼是否正確的過程LogoutResult ret;SendData(cSock, &ret);}break;default:{printf("<socket=%d>收到未定義消息,數據長度:%d\n", cSock, header->dataLength);//DataHeader ret;//SendData(cSock, &ret);}break;}}//發送指定Socket數據int SendData(SOCKET cSock, DataHeader* header){if (isRun() && header){return send(cSock, (const char*)header, header->dataLength, 0);}return SOCKET_ERROR;}void SendDataToAll(DataHeader* header){for (int n = (int)_clients.size() - 1; n >= 0; n--){SendData(_clients[n]->sockfd(), header);}}};#endif // !_EasyTcpServer_hpp_

參考資料:

  • C++ 百萬并發網絡通信引擎架構與實現 (服務端、客戶端、跨平臺) Version 1.0
  • 總結

    以上是生活随笔為你收集整理的粘包的原因分析及解决的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 欧美在线 | 黄色三级带 | 97神马影院 | 午夜毛片电影 | 乱子伦视频在线看 | 8x8ⅹ成人永久免费视频 | 亚洲图片自拍偷拍区 | 国产精品一区二区三区久久 | 久草五月天| 国产熟妇搡bbbb搡bbbb | 日韩精品1区2区3区 欧美一本 | 极度诱惑香港电影完整 | 国产黄色片在线 | 日韩免费| 免费成人在线观看 | 欧美在线视频你懂的 | 日本a视频 | 亚洲乱码少妇 | 日本一区二区不卡视频 | 日韩性高潮 | 天天干夜夜 | 91免费网站在线观看 | 国产精品不卡一区二区三区 | 女警白嫩翘臀呻吟迎合 | 国产视频手机在线 | 欧美日韩一区二区区别是什么 | 男女黄床上色视频 | 国产精品一二区在线观看 | 亚洲欧美视频一区 | 国产乱妇4p交换乱免费视频 | 久草免费新视频 | 男人的天堂手机在线 | 国产第一页精品 | 婷婷天堂网 | 日本精品视频一区 | 在线观看亚洲av每日更新 | 干爹你真棒插曲mv在线观看 | 亚洲欧洲日本在线 | 国产精品91在线 | 日韩视频一区二区三区四区 | 久久香蕉网| 91精品久久人妻一区二区夜夜夜 | 三上悠亚人妻中文字幕在线 | 国产精品不卡在线观看 | 久久久久久免费观看 | 亚洲精品三级 | 中文字字幕在线中文乱码电影 | 成人在线免费观看网站 | 午夜精品久久 | 精品国产黄色 | av天天堂 | 久久国产精品亚洲 | 欧美日韩视频在线播放 | 亚洲草逼视频 | 女人囗交吞精囗述 | 91久久精品国产91久久 | 国产日韩av一区二区 | 无码精品久久久久久久 | 免费在线a | 蝌蚪网在线视频 | 日韩精品在线电影 | 色人阁婷婷| 老司机精品视频在线播放 | 日韩欧美网站 | 成人久久影院 | 久久久久激情 | 亚洲午夜久久久久久久久 | 日日碰碰 | 国产精品99精品无码视亚 | 五月婷婷狠狠干 | www色网站 | 男生插女生视频 | 台湾swag在线播放 | 亚洲精品乱码久久久久久久久久久久 | 外国一级片 | 手机在线看黄色 | 一区二区三区美女 | 日韩精品视频在线观看免费 | 激情小说欧美色图 | 激情黄色小说视频 | 欧美性插动态图 | 国产性av| 色老头一区 | 精品免费在线 | 午夜h | 欧美在线视频不卡 | 中文字幕第页 | 亚洲一区二区三区综合 | 无码人妻精品一区二区 | 99re热这里只有精品视频 | 久久一本综合 | 亚洲一级Av无码毛片久久精品 | 电影《走路上学》免费 | 一级黄色在线视频 | 欧美极品少妇 | 波多野结衣一区二区三区高清av | 国产露脸150部国语对白 | 高级家教课程在线观看 | 天天干天天弄 |