zoukankan      html  css  js  c++  java
  • 跨平台的游戏客户端Socket封装

    依照惯例,先上代码:

    1. #pragma once 
    2.  
    3. #ifdef WIN32 
    4. #include <windows.h> 
    5. #include <WinSock.h> 
    6. #else 
    7. #include <sys/socket.h> 
    8. #include <fcntl.h> 
    9. #include <errno.h> 
    10. #include <netinet/in.h> 
    11. #include <arpa/inet.h> 
    12.  
    13. #define SOCKET int 
    14. #define SOCKET_ERROR -1 
    15. #define INVALID_SOCKET -1 
    16.  
    17. #endif 
    18.  
    19. #ifndef CHECKF 
    20. #define CHECKF(x) \ 
    21.     do
    22. { \ 
    23.     if (!(x)) { \ 
    24.     log_msg("CHECKF", #x, __FILE__, __LINE__); \ 
    25.     return 0; \ 
    26.     } \ 
    27. } while (0) 
    28. #endif 
    29.  
    30. #define _MAX_MSGSIZE 16 * 1024      // 暂定一个消息最大为16k 
    31. #define BLOCKSECONDS    30          // INIT函数阻塞时间 
    32. #define INBUFSIZE   (64*1024)       //? 具体尺寸根据剖面报告调整  接收数据的缓存 
    33. #define OUTBUFSIZE  (8*1024)        //? 具体尺寸根据剖面报告调整。 发送数据的缓存,当不超过8K时,FLUSH只需要SEND一次 
    34.  
    35. class CGameSocket { 
    36. public
    37.     CGameSocket(void); 
    38.     bool    Create(constchar* pszServerIP, int nServerPort, int nBlockSec = BLOCKSECONDS, bool bKeepAlive = false); 
    39.     bool    SendMsg(void* pBuf, int nSize); 
    40.     bool    ReceiveMsg(void* pBuf, int& nSize); 
    41.     bool    Flush(void); 
    42.     bool    Check(void); 
    43.     void    Destroy(void); 
    44.     SOCKET  GetSocket(void) const { return m_sockClient; } 
    45. private
    46.     bool    recvFromSock(void);     // 从网络中读取尽可能多的数据 
    47.     bool    hasError();         // 是否发生错误,注意,异步模式未完成非错误 
    48.     void    closeSocket(); 
    49.  
    50.     SOCKET  m_sockClient; 
    51.  
    52.     // 发送数据缓冲 
    53.     char    m_bufOutput[OUTBUFSIZE];    //? 可优化为指针数组 
    54.     int     m_nOutbufLen; 
    55.  
    56.     // 环形缓冲区 
    57.     char    m_bufInput[INBUFSIZE]; 
    58.     int     m_nInbufLen; 
    59.     int     m_nInbufStart;              // INBUF使用循环式队列,该变量为队列起点,0 - (SIZE-1) 
    60. }; 
    #pragma once
    
    #ifdef WIN32
    #include <windows.h>
    #include <WinSock.h>
    #else
    #include <sys/socket.h>
    #include <fcntl.h>
    #include <errno.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    #define SOCKET int
    #define SOCKET_ERROR -1
    #define INVALID_SOCKET -1
    
    #endif
    
    #ifndef CHECKF
    #define CHECKF(x) \
    	do \
    { \
    	if (!(x)) { \
    	log_msg("CHECKF", #x, __FILE__, __LINE__); \
    	return 0; \
    	} \
    } while (0)
    #endif
    
    #define _MAX_MSGSIZE 16 * 1024		// 暂定一个消息最大为16k
    #define BLOCKSECONDS	30			// INIT函数阻塞时间
    #define INBUFSIZE	(64*1024)		//?	具体尺寸根据剖面报告调整  接收数据的缓存
    #define OUTBUFSIZE	(8*1024)		//? 具体尺寸根据剖面报告调整。 发送数据的缓存,当不超过8K时,FLUSH只需要SEND一次
    
    class CGameSocket {
    public:
    	CGameSocket(void);
    	bool	Create(const char* pszServerIP, int nServerPort, int nBlockSec = BLOCKSECONDS, bool bKeepAlive = false);
    	bool	SendMsg(void* pBuf, int nSize);
    	bool	ReceiveMsg(void* pBuf, int& nSize);
    	bool	Flush(void);
    	bool	Check(void);
    	void	Destroy(void);
    	SOCKET	GetSocket(void) const { return m_sockClient; }
    private:
    	bool	recvFromSock(void);		// 从网络中读取尽可能多的数据
    	bool    hasError();			// 是否发生错误,注意,异步模式未完成非错误
    	void    closeSocket();
    
    	SOCKET	m_sockClient;
    
    	// 发送数据缓冲
    	char	m_bufOutput[OUTBUFSIZE];	//? 可优化为指针数组
    	int		m_nOutbufLen;
    
    	// 环形缓冲区
    	char	m_bufInput[INBUFSIZE];
    	int		m_nInbufLen;
    	int		m_nInbufStart;				// INBUF使用循环式队列,该变量为队列起点,0 - (SIZE-1)
    };
    
    1. #include "stdafx.h" 
    2. #include "Socket.h" 
    3.  
    4. CGameSocket::CGameSocket() 
    5. {  
    6.     // 初始化 
    7.     memset(m_bufOutput, 0, sizeof(m_bufOutput)); 
    8.     memset(m_bufInput, 0, sizeof(m_bufInput)); 
    9.  
    10. void CGameSocket::closeSocket() 
    11. #ifdef WIN32 
    12.     closesocket(m_sockClient); 
    13.     WSACleanup(); 
    14. #else 
    15.     close(m_sockClient); 
    16. #endif 
    17.  
    18. bool CGameSocket::Create(constchar* pszServerIP, int nServerPort, int nBlockSec, bool bKeepAlive /*= FALSE*/
    19.     // 检查参数 
    20.     if(pszServerIP == 0 || strlen(pszServerIP) > 15) { 
    21.         returnfalse
    22.     } 
    23.  
    24. #ifdef WIN32 
    25.     WSADATA wsaData; 
    26.     WORD version = MAKEWORD(2, 0); 
    27.     int ret = WSAStartup(version, &wsaData);//win sock start up 
    28.     if (ret != 0) { 
    29.         returnfalse
    30.     } 
    31. #endif 
    32.  
    33.     // 创建主套接字 
    34.     m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    35.     if(m_sockClient == INVALID_SOCKET) { 
    36.         closeSocket(); 
    37.         returnfalse
    38.     } 
    39.  
    40.     // 设置SOCKET为KEEPALIVE 
    41.     if(bKeepAlive) 
    42.     { 
    43.         int     optval=1; 
    44.         if(setsockopt(m_sockClient, SOL_SOCKET, SO_KEEPALIVE, (char *) &optval, sizeof(optval))) 
    45.         { 
    46.             closeSocket(); 
    47.             returnfalse
    48.         } 
    49.     } 
    50.  
    51. #ifdef WIN32 
    52.     DWORD nMode = 1; 
    53.     int nRes = ioctlsocket(m_sockClient, FIONBIO, &nMode); 
    54.     if (nRes == SOCKET_ERROR) { 
    55.         closeSocket(); 
    56.         returnfalse
    57.     } 
    58. #else 
    59.     // 设置为非阻塞方式 
    60.     fcntl(m_sockClient, F_SETFL, O_NONBLOCK); 
    61. #endif 
    62.  
    63.     unsigned long serveraddr = inet_addr(pszServerIP); 
    64.     if(serveraddr == INADDR_NONE)   // 检查IP地址格式错误 
    65.     { 
    66.         closeSocket(); 
    67.         returnfalse
    68.     } 
    69.  
    70.     sockaddr_in addr_in; 
    71.     memset((void *)&addr_in, 0, sizeof(addr_in)); 
    72.     addr_in.sin_family = AF_INET; 
    73.     addr_in.sin_port = htons(nServerPort); 
    74.     addr_in.sin_addr.s_addr = serveraddr; 
    75.      
    76.     if(connect(m_sockClient, (sockaddr *)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) { 
    77.         if (hasError()) { 
    78.             closeSocket(); 
    79.             returnfalse
    80.         } 
    81.         else    // WSAWOLDBLOCK 
    82.         { 
    83.             timeval timeout; 
    84.             timeout.tv_sec  = nBlockSec; 
    85.             timeout.tv_usec = 0; 
    86.             fd_set writeset, exceptset; 
    87.             FD_ZERO(&writeset); 
    88.             FD_ZERO(&exceptset); 
    89.             FD_SET(m_sockClient, &writeset); 
    90.             FD_SET(m_sockClient, &exceptset); 
    91.  
    92.             int ret = select(FD_SETSIZE, NULL, &writeset, &exceptset, &timeout); 
    93.             if (ret == 0 || ret < 0) { 
    94.                 closeSocket(); 
    95.                 returnfalse
    96.             } else  // ret > 0 
    97.             { 
    98.                 ret = FD_ISSET(m_sockClient, &exceptset); 
    99.                 if(ret)     // or (!FD_ISSET(m_sockClient, &writeset) 
    100.                 { 
    101.                     closeSocket(); 
    102.                     returnfalse
    103.                 } 
    104.             } 
    105.         } 
    106.     } 
    107.  
    108.     m_nInbufLen     = 0; 
    109.     m_nInbufStart   = 0; 
    110.     m_nOutbufLen    = 0; 
    111.  
    112.     struct linger so_linger; 
    113.     so_linger.l_onoff = 1; 
    114.     so_linger.l_linger = 500; 
    115.     setsockopt(m_sockClient, SOL_SOCKET, SO_LINGER, (constchar*)&so_linger, sizeof(so_linger)); 
    116.  
    117.     returntrue
    118.  
    119. bool CGameSocket::SendMsg(void* pBuf, int nSize) 
    120.     if(pBuf == 0 || nSize <= 0) { 
    121.         returnfalse
    122.     } 
    123.  
    124.     if (m_sockClient == INVALID_SOCKET) { 
    125.         returnfalse
    126.     } 
    127.  
    128.     // 检查通讯消息包长度 
    129.     int packsize = 0; 
    130.     packsize = nSize; 
    131.  
    132.     // 检测BUF溢出 
    133.     if(m_nOutbufLen + nSize > OUTBUFSIZE) { 
    134.         // 立即发送OUTBUF中的数据,以清空OUTBUF。 
    135.         Flush(); 
    136.         if(m_nOutbufLen + nSize > OUTBUFSIZE) { 
    137.             // 出错了 
    138.             Destroy(); 
    139.             returnfalse
    140.         } 
    141.     } 
    142.     // 数据添加到BUF尾 
    143.     memcpy(m_bufOutput + m_nOutbufLen, pBuf, nSize); 
    144.     m_nOutbufLen += nSize; 
    145.     returntrue
    146.  
    147. bool CGameSocket::ReceiveMsg(void* pBuf, int& nSize) 
    148.     //检查参数 
    149.     if(pBuf == NULL || nSize <= 0) { 
    150.         returnfalse
    151.     } 
    152.      
    153.     if (m_sockClient == INVALID_SOCKET) { 
    154.         returnfalse
    155.     } 
    156.  
    157.     // 检查是否有一个消息(小于2则无法获取到消息长度) 
    158.     if(m_nInbufLen < 2) { 
    159.         //  如果没有请求成功  或者   如果没有数据则直接返回 
    160.         if(!recvFromSock() || m_nInbufLen < 2) {     // 这个m_nInbufLen更新了 
    161.             returnfalse
    162.         } 
    163.     } 
    164.  
    165.     // 计算要拷贝的消息的大小(一个消息,大小为整个消息的第一个16字节),因为环形缓冲区,所以要分开计算 
    166.     int packsize = (unsigned char)m_bufInput[m_nInbufStart] + 
    167.         (unsigned char)m_bufInput[(m_nInbufStart + 1) % INBUFSIZE] * 256; // 注意字节序,高位+低位 
    168.  
    169.     // 检测消息包尺寸错误 暂定最大16k 
    170.     if (packsize <= 0 || packsize > _MAX_MSGSIZE) { 
    171.         m_nInbufLen = 0;        // 直接清空INBUF 
    172.         m_nInbufStart = 0; 
    173.         returnfalse
    174.     } 
    175.  
    176.     // 检查消息是否完整(如果将要拷贝的消息大于此时缓冲区数据长度,需要再次请求接收剩余数据) 
    177.     if (packsize > m_nInbufLen) { 
    178.         // 如果没有请求成功   或者    依然无法获取到完整的数据包  则返回,直到取得完整包 
    179.         if (!recvFromSock() || packsize > m_nInbufLen) { // 这个m_nInbufLen已更新 
    180.             returnfalse
    181.         } 
    182.     } 
    183.  
    184.     // 复制出一个消息 
    185.     if(m_nInbufStart + packsize > INBUFSIZE) { 
    186.         // 如果一个消息有回卷(被拆成两份在环形缓冲区的头尾) 
    187.         // 先拷贝环形缓冲区末尾的数据 
    188.         int copylen = INBUFSIZE - m_nInbufStart; 
    189.         memcpy(pBuf, m_bufInput + m_nInbufStart, copylen); 
    190.  
    191.         // 再拷贝环形缓冲区头部的剩余部分 
    192.         memcpy((unsigned char *)pBuf + copylen, m_bufInput, packsize - copylen); 
    193.         nSize = packsize; 
    194.     } else
    195.         // 消息没有回卷,可以一次拷贝出去 
    196.         memcpy(pBuf, m_bufInput + m_nInbufStart, packsize); 
    197.         nSize = packsize; 
    198.     } 
    199.  
    200.     // 重新计算环形缓冲区头部位置 
    201.     m_nInbufStart = (m_nInbufStart + packsize) % INBUFSIZE; 
    202.     m_nInbufLen -= packsize; 
    203.     return  true
    204.  
    205. bool CGameSocket::hasError() 
    206. #ifdef WIN32 
    207.     int err = WSAGetLastError(); 
    208.     if(err != WSAEWOULDBLOCK) { 
    209. #else 
    210.     int err = errno; 
    211.     if(err != EINPROGRESS && err != EAGAIN) { 
    212. #endif 
    213.         returntrue
    214.     } 
    215.  
    216.     returnfalse
    217.  
    218. // 从网络中读取尽可能多的数据,实际向服务器请求数据的地方 
    219. bool CGameSocket::recvFromSock(void
    220.     if (m_nInbufLen >= INBUFSIZE || m_sockClient == INVALID_SOCKET) { 
    221.         returnfalse
    222.     } 
    223.  
    224.     // 接收第一段数据 
    225.     int savelen, savepos;           // 数据要保存的长度和位置 
    226.     if(m_nInbufStart + m_nInbufLen < INBUFSIZE)  {   // INBUF中的剩余空间有回绕 
    227.         savelen = INBUFSIZE - (m_nInbufStart + m_nInbufLen);        // 后部空间长度,最大接收数据的长度 
    228.     } else
    229.         savelen = INBUFSIZE - m_nInbufLen; 
    230.     } 
    231.  
    232.     // 缓冲区数据的末尾 
    233.     savepos = (m_nInbufStart + m_nInbufLen) % INBUFSIZE; 
    234.     CHECKF(savepos + savelen <= INBUFSIZE); 
    235.     int inlen = recv(m_sockClient, m_bufInput + savepos, savelen, 0); 
    236.     if(inlen > 0) { 
    237.         // 有接收到数据 
    238.         m_nInbufLen += inlen; 
    239.          
    240.         if (m_nInbufLen > INBUFSIZE) { 
    241.             returnfalse
    242.         } 
    243.  
    244.         // 接收第二段数据(一次接收没有完成,接收第二段数据) 
    245.         if(inlen == savelen && m_nInbufLen < INBUFSIZE) { 
    246.             int savelen = INBUFSIZE - m_nInbufLen; 
    247.             int savepos = (m_nInbufStart + m_nInbufLen) % INBUFSIZE; 
    248.             CHECKF(savepos + savelen <= INBUFSIZE); 
    249.             inlen = recv(m_sockClient, m_bufInput + savepos, savelen, 0); 
    250.             if(inlen > 0) { 
    251.                 m_nInbufLen += inlen; 
    252.                 if (m_nInbufLen > INBUFSIZE) { 
    253.                     returnfalse
    254.                 }    
    255.             } elseif(inlen == 0) { 
    256.                 Destroy(); 
    257.                 returnfalse
    258.             } else
    259.                 // 连接已断开或者错误(包括阻塞) 
    260.                 if (hasError()) { 
    261.                     Destroy(); 
    262.                     returnfalse
    263.                 } 
    264.             } 
    265.         } 
    266.     } elseif(inlen == 0) { 
    267.         Destroy(); 
    268.         returnfalse
    269.     } else
    270.         // 连接已断开或者错误(包括阻塞) 
    271.         if (hasError()) { 
    272.             Destroy(); 
    273.             returnfalse
    274.         } 
    275.     } 
    276.  
    277.     returntrue
    278.  
    279. bool CGameSocket::Flush(void)       //? 如果 OUTBUF > SENDBUF 则需要多次SEND() 
    280.     if (m_sockClient == INVALID_SOCKET) { 
    281.         returnfalse
    282.     } 
    283.  
    284.     if(m_nOutbufLen <= 0) { 
    285.         returntrue
    286.     } 
    287.      
    288.     // 发送一段数据 
    289.     int outsize; 
    290.     outsize = send(m_sockClient, m_bufOutput, m_nOutbufLen, 0); 
    291.     if(outsize > 0) { 
    292.         // 删除已发送的部分 
    293.         if(m_nOutbufLen - outsize > 0) { 
    294.             memcpy(m_bufOutput, m_bufOutput + outsize, m_nOutbufLen - outsize); 
    295.         } 
    296.  
    297.         m_nOutbufLen -= outsize; 
    298.  
    299.         if (m_nOutbufLen < 0) { 
    300.             returnfalse
    301.         } 
    302.     } else
    303.         if (hasError()) { 
    304.             Destroy(); 
    305.             returnfalse
    306.         } 
    307.     } 
    308.  
    309.     returntrue
    310.  
    311. bool CGameSocket::Check(void
    312.     // 检查状态 
    313.     if (m_sockClient == INVALID_SOCKET) { 
    314.         returnfalse
    315.     } 
    316.  
    317.     char buf[1]; 
    318.     int ret = recv(m_sockClient, buf, 1, MSG_PEEK); 
    319.     if(ret == 0) { 
    320.         Destroy(); 
    321.         returnfalse
    322.     } elseif(ret < 0) { 
    323.         if (hasError()) { 
    324.             Destroy(); 
    325.             returnfalse
    326.         } else {    // 阻塞 
    327.             returntrue
    328.         } 
    329.     } else {    // 有数据 
    330.         returntrue
    331.     } 
    332.      
    333.     returntrue
    334.  
    335. void CGameSocket::Destroy(void
    336.     // 关闭 
    337.     struct linger so_linger; 
    338.     so_linger.l_onoff = 1; 
    339.     so_linger.l_linger = 500; 
    340.     int ret = setsockopt(m_sockClient, SOL_SOCKET, SO_LINGER, (constchar*)&so_linger, sizeof(so_linger)); 
    341.  
    342.     closeSocket(); 
    343.  
    344.     m_sockClient = INVALID_SOCKET; 
    345.     m_nInbufLen = 0; 
    346.     m_nInbufStart = 0; 
    347.     m_nOutbufLen = 0; 
    348.  
    349.     memset(m_bufOutput, 0, sizeof(m_bufOutput)); 
    350.     memset(m_bufInput, 0, sizeof(m_bufInput)); 
    #include "stdafx.h"
    #include "Socket.h"
    
    CGameSocket::CGameSocket()
    { 
    	// 初始化
    	memset(m_bufOutput, 0, sizeof(m_bufOutput));
    	memset(m_bufInput, 0, sizeof(m_bufInput));
    }
    
    void CGameSocket::closeSocket()
    {
    #ifdef WIN32
        closesocket(m_sockClient);
        WSACleanup();
    #else
        close(m_sockClient);
    #endif
    }
    
    bool CGameSocket::Create(const char* pszServerIP, int nServerPort, int nBlockSec, bool bKeepAlive /*= FALSE*/)
    {
    	// 检查参数
    	if(pszServerIP == 0 || strlen(pszServerIP) > 15) {
    		return false;
    	}
    
    #ifdef WIN32
    	WSADATA wsaData;
    	WORD version = MAKEWORD(2, 0);
    	int ret = WSAStartup(version, &wsaData);//win sock start up
    	if (ret != 0) {
    		return false;
    	}
    #endif
    
    	// 创建主套接字
    	m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if(m_sockClient == INVALID_SOCKET) {
            closeSocket();
    		return false;
    	}
    
    	// 设置SOCKET为KEEPALIVE
    	if(bKeepAlive)
    	{
    		int		optval=1;
    		if(setsockopt(m_sockClient, SOL_SOCKET, SO_KEEPALIVE, (char *) &optval, sizeof(optval)))
    		{
                closeSocket();
    			return false;
    		}
    	}
    
    #ifdef WIN32
    	DWORD nMode = 1;
    	int nRes = ioctlsocket(m_sockClient, FIONBIO, &nMode);
    	if (nRes == SOCKET_ERROR) {
    		closeSocket();
    		return false;
    	}
    #else
    	// 设置为非阻塞方式
    	fcntl(m_sockClient, F_SETFL, O_NONBLOCK);
    #endif
    
    	unsigned long serveraddr = inet_addr(pszServerIP);
    	if(serveraddr == INADDR_NONE)	// 检查IP地址格式错误
    	{
    		closeSocket();
    		return false;
    	}
    
    	sockaddr_in	addr_in;
    	memset((void *)&addr_in, 0, sizeof(addr_in));
    	addr_in.sin_family = AF_INET;
    	addr_in.sin_port = htons(nServerPort);
    	addr_in.sin_addr.s_addr = serveraddr;
    	
    	if(connect(m_sockClient, (sockaddr *)&addr_in, sizeof(addr_in)) == SOCKET_ERROR) {
    		if (hasError()) {
    			closeSocket();
    			return false;
    		}
    		else	// WSAWOLDBLOCK
    		{
    			timeval timeout;
    			timeout.tv_sec	= nBlockSec;
    			timeout.tv_usec	= 0;
    			fd_set writeset, exceptset;
    			FD_ZERO(&writeset);
    			FD_ZERO(&exceptset);
    			FD_SET(m_sockClient, &writeset);
    			FD_SET(m_sockClient, &exceptset);
    
    			int ret = select(FD_SETSIZE, NULL, &writeset, &exceptset, &timeout);
    			if (ret == 0 || ret < 0) {
    				closeSocket();
    				return false;
    			} else	// ret > 0
    			{
    				ret = FD_ISSET(m_sockClient, &exceptset);
    				if(ret)		// or (!FD_ISSET(m_sockClient, &writeset)
    				{
    					closeSocket();
    					return false;
    				}
    			}
    		}
    	}
    
    	m_nInbufLen		= 0;
    	m_nInbufStart	= 0;
    	m_nOutbufLen	= 0;
    
    	struct linger so_linger;
    	so_linger.l_onoff = 1;
    	so_linger.l_linger = 500;
    	setsockopt(m_sockClient, SOL_SOCKET, SO_LINGER, (const char*)&so_linger, sizeof(so_linger));
    
    	return true;
    }
    
    bool CGameSocket::SendMsg(void* pBuf, int nSize)
    {
    	if(pBuf == 0 || nSize <= 0) {
    		return false;
    	}
    
    	if (m_sockClient == INVALID_SOCKET) {
    		return false;
    	}
    
    	// 检查通讯消息包长度
    	int packsize = 0;
    	packsize = nSize;
    
    	// 检测BUF溢出
    	if(m_nOutbufLen + nSize > OUTBUFSIZE) {
    		// 立即发送OUTBUF中的数据,以清空OUTBUF。
    		Flush();
    		if(m_nOutbufLen + nSize > OUTBUFSIZE) {
    			// 出错了
    			Destroy();
    			return false;
    		}
    	}
    	// 数据添加到BUF尾
    	memcpy(m_bufOutput + m_nOutbufLen, pBuf, nSize);
    	m_nOutbufLen += nSize;
    	return true;
    }
    
    bool CGameSocket::ReceiveMsg(void* pBuf, int& nSize)
    {
    	//检查参数
    	if(pBuf == NULL || nSize <= 0) {
    		return false;
    	}
    	
    	if (m_sockClient == INVALID_SOCKET) {
    		return false;
    	}
    
    	// 检查是否有一个消息(小于2则无法获取到消息长度)
    	if(m_nInbufLen < 2) {
    		//  如果没有请求成功  或者   如果没有数据则直接返回
    		if(!recvFromSock() || m_nInbufLen < 2) {		// 这个m_nInbufLen更新了
    			return false;
    		}
    	}
    
        // 计算要拷贝的消息的大小(一个消息,大小为整个消息的第一个16字节),因为环形缓冲区,所以要分开计算
    	int packsize = (unsigned char)m_bufInput[m_nInbufStart] +
    		(unsigned char)m_bufInput[(m_nInbufStart + 1) % INBUFSIZE] * 256; // 注意字节序,高位+低位
    
    	// 检测消息包尺寸错误 暂定最大16k
    	if (packsize <= 0 || packsize > _MAX_MSGSIZE) {
    		m_nInbufLen = 0;		// 直接清空INBUF
    		m_nInbufStart = 0;
    		return false;
    	}
    
    	// 检查消息是否完整(如果将要拷贝的消息大于此时缓冲区数据长度,需要再次请求接收剩余数据)
    	if (packsize > m_nInbufLen) {
    		// 如果没有请求成功   或者    依然无法获取到完整的数据包  则返回,直到取得完整包
    		if (!recvFromSock() || packsize > m_nInbufLen) {	// 这个m_nInbufLen已更新
    			return false;
    		}
    	}
    
    	// 复制出一个消息
    	if(m_nInbufStart + packsize > INBUFSIZE) {
    		// 如果一个消息有回卷(被拆成两份在环形缓冲区的头尾)
    		// 先拷贝环形缓冲区末尾的数据
    		int copylen = INBUFSIZE - m_nInbufStart;
    		memcpy(pBuf, m_bufInput + m_nInbufStart, copylen);
    
    		// 再拷贝环形缓冲区头部的剩余部分
    		memcpy((unsigned char *)pBuf + copylen, m_bufInput, packsize - copylen);
    		nSize = packsize;
    	} else {
    		// 消息没有回卷,可以一次拷贝出去
    		memcpy(pBuf, m_bufInput + m_nInbufStart, packsize);
    		nSize = packsize;
    	}
    
    	// 重新计算环形缓冲区头部位置
    	m_nInbufStart = (m_nInbufStart + packsize) % INBUFSIZE;
    	m_nInbufLen -= packsize;
    	return	true;
    }
    
    bool CGameSocket::hasError()
    {
    #ifdef WIN32
    	int err = WSAGetLastError();
    	if(err != WSAEWOULDBLOCK) {
    #else
    	int err = errno;
    	if(err != EINPROGRESS && err != EAGAIN) {
    #endif
    		return true;
    	}
    
    	return false;
    }
    
    // 从网络中读取尽可能多的数据,实际向服务器请求数据的地方
    bool CGameSocket::recvFromSock(void)
    {
    	if (m_nInbufLen >= INBUFSIZE || m_sockClient == INVALID_SOCKET) {
    		return false;
    	}
    
    	// 接收第一段数据
    	int	savelen, savepos;			// 数据要保存的长度和位置
    	if(m_nInbufStart + m_nInbufLen < INBUFSIZE)	{	// INBUF中的剩余空间有回绕
    		savelen = INBUFSIZE - (m_nInbufStart + m_nInbufLen);		// 后部空间长度,最大接收数据的长度
    	} else {
    		savelen = INBUFSIZE - m_nInbufLen;
    	}
    
    	// 缓冲区数据的末尾
    	savepos = (m_nInbufStart + m_nInbufLen) % INBUFSIZE;
    	CHECKF(savepos + savelen <= INBUFSIZE);
    	int inlen = recv(m_sockClient, m_bufInput + savepos, savelen, 0);
    	if(inlen > 0) {
    		// 有接收到数据
    		m_nInbufLen += inlen;
    		
    		if (m_nInbufLen > INBUFSIZE) {
    			return false;
    		}
    
    		// 接收第二段数据(一次接收没有完成,接收第二段数据)
    		if(inlen == savelen && m_nInbufLen < INBUFSIZE) {
    			int savelen = INBUFSIZE - m_nInbufLen;
    			int savepos = (m_nInbufStart + m_nInbufLen) % INBUFSIZE;
    			CHECKF(savepos + savelen <= INBUFSIZE);
    			inlen = recv(m_sockClient, m_bufInput + savepos, savelen, 0);
    			if(inlen > 0) {
    				m_nInbufLen += inlen;
    				if (m_nInbufLen > INBUFSIZE) {
    					return false;
    				}	
    			} else if(inlen == 0) {
    				Destroy();
    				return false;
    			} else {
    				// 连接已断开或者错误(包括阻塞)
    				if (hasError()) {
    					Destroy();
    					return false;
    				}
    			}
    		}
    	} else if(inlen == 0) {
    		Destroy();
    		return false;
    	} else {
    		// 连接已断开或者错误(包括阻塞)
    		if (hasError()) {
    			Destroy();
    			return false;
    		}
    	}
    
    	return true;
    }
    
    bool CGameSocket::Flush(void)		//? 如果 OUTBUF > SENDBUF 则需要多次SEND()
    {
    	if (m_sockClient == INVALID_SOCKET) {
    		return false;
    	}
    
    	if(m_nOutbufLen <= 0) {
    		return true;
    	}
    	
    	// 发送一段数据
    	int	outsize;
    	outsize = send(m_sockClient, m_bufOutput, m_nOutbufLen, 0);
    	if(outsize > 0) {
    		// 删除已发送的部分
    		if(m_nOutbufLen - outsize > 0) {
    			memcpy(m_bufOutput, m_bufOutput + outsize, m_nOutbufLen - outsize);
    		}
    
    		m_nOutbufLen -= outsize;
    
    		if (m_nOutbufLen < 0) {
    			return false;
    		}
    	} else {
    		if (hasError()) {
    			Destroy();
    			return false;
    		}
    	}
    
    	return true;
    }
    
    bool CGameSocket::Check(void)
    {
    	// 检查状态
    	if (m_sockClient == INVALID_SOCKET) {
    		return false;
    	}
    
    	char buf[1];
    	int	ret = recv(m_sockClient, buf, 1, MSG_PEEK);
    	if(ret == 0) {
    		Destroy();
    		return false;
    	} else if(ret < 0) {
    		if (hasError()) {
    			Destroy();
    			return false;
    		} else {	// 阻塞
    			return true;
    		}
    	} else {	// 有数据
    		return true;
    	}
    	
    	return true;
    }
    
    void CGameSocket::Destroy(void)
    {
    	// 关闭
    	struct linger so_linger;
    	so_linger.l_onoff = 1;
    	so_linger.l_linger = 500;
    	int ret = setsockopt(m_sockClient, SOL_SOCKET, SO_LINGER, (const char*)&so_linger, sizeof(so_linger));
    
        closeSocket();
    
    	m_sockClient = INVALID_SOCKET;
    	m_nInbufLen = 0;
    	m_nInbufStart = 0;
    	m_nOutbufLen = 0;
    
    	memset(m_bufOutput, 0, sizeof(m_bufOutput));
    	memset(m_bufInput, 0, sizeof(m_bufInput));
    }
    
    1. // 发送消息 
    2. bSucSend = m_pSocket->SendMsg(buf, nLen); 
    3.  
    4. // 接收消息处理(放到游戏主循环中,每帧处理) 
    5. if (!m_pSocket) { 
    6.         return
    7.     } 
    8.  
    9.     if (!m_pSocket->Check()) { 
    10.         m_pSocket = NULL; 
    11.         // 掉线了 
    12.         onConnectionAbort(); 
    13.         return
    14.     } 
    15.  
    16.     // 发送数据(向服务器发送消息) 
    17.     m_pSocket->Flush(); 
    18.  
    19.     // 接收数据(取得缓冲区中的所有消息,直到缓冲区为空) 
    20.     while (true
    21.     { 
    22.         char buffer[_MAX_MSGSIZE] = { 0 }; 
    23.         int nSize = sizeof(buffer); 
    24.         char* pbufMsg = buffer; 
    25.         if(m_pSocket == NULL) 
    26.         { 
    27.             break
    28.         } 
    29.         if (!m_pSocket->ReceiveMsg(pbufMsg, nSize)) { 
    30.             break
    31.         } 
    32.          
    33.         while (true
    34.         { 
    35.             MsgHead* pReceiveMsg = (MsgHead*)(pbufMsg); 
    36.             uint16  dwCurMsgSize = pReceiveMsg->usSize; 
    37. //          CCLOG("msgsize: %d", dwCurMsgSize); 
    38.  
    39.             if((int)dwCurMsgSize > nSize || dwCurMsgSize <= 0) {  // broken msg 
    40.                 break
    41.             } 
    42.  
    43.             CMessageSubject::instance().OnMessage((constchar*)pReceiveMsg, pReceiveMsg->usSize); 
    44.  
    45.             pbufMsg += dwCurMsgSize; 
    46.             nSize   -= dwCurMsgSize; 
    47.             if(nSize <= 0) { 
    48.                 break
    49.             } 
    50.         } 
    51.     } 
    // 发送消息
    bSucSend = m_pSocket->SendMsg(buf, nLen);
    
    // 接收消息处理(放到游戏主循环中,每帧处理)
    if (!m_pSocket) {
    		return;
    	}
    
    	if (!m_pSocket->Check()) {
    		m_pSocket = NULL;
    		// 掉线了
    		onConnectionAbort();
    		return;
    	}
    
    	// 发送数据(向服务器发送消息)
    	m_pSocket->Flush();
    
    	// 接收数据(取得缓冲区中的所有消息,直到缓冲区为空)
    	while (true)
    	{
    		char buffer[_MAX_MSGSIZE] = { 0 };
    		int nSize = sizeof(buffer);
    		char* pbufMsg = buffer;
    		if(m_pSocket == NULL)
    		{
    			break;
    		}
    		if (!m_pSocket->ReceiveMsg(pbufMsg, nSize)) {
    			break;
    		}
    		
    		while (true)
    		{
    			MsgHead* pReceiveMsg = (MsgHead*)(pbufMsg);
    			uint16	dwCurMsgSize = pReceiveMsg->usSize;
    //			CCLOG("msgsize: %d", dwCurMsgSize);
    
    			if((int)dwCurMsgSize > nSize || dwCurMsgSize <= 0) {	// broken msg
    				break;
    			}
    
    			CMessageSubject::instance().OnMessage((const char*)pReceiveMsg, pReceiveMsg->usSize);
    
    			pbufMsg	+= dwCurMsgSize;
    			nSize	-= dwCurMsgSize;
    			if(nSize <= 0) {
    				break;
    			}
    		}
    	}

            这样的一个Socket封装,适用于windows mac ios android等平台, Socket处理是异步非阻塞的,所以可以放心的放到主线程处理消息, 最大支持64k的接收消息缓冲(一般一个消息不可能大于3k)。

            这里展示这个,目的并不是说这个封装有多么优异,多么高科技,多么牛x。  恰恰是想表达它的简单。  这个简单的封装完全可以胜任一个mmo客户端的消息底层(注意是客户端,服务器对消息底层的性能要求要远远大于客户端),甚至是魔兽世界这类的大型mmo都可以用这么一个小的封装来做消息底层。

           对于游戏客户端消息底层的要求非常简单,根本不需要boost::asio什么的开源库。

           1、非阻塞模型,这样我才放心把消息处理放到主线程,多线程处理消息其实很浪费。不知道得多大型的mmo才会用到。

           2、消息接收缓存处理,避免大消息被截掉。

           3、没了,剩下的一些特殊处理应该是上层逻辑来考虑的。比如掉线重连等。

  • 相关阅读:
    jQuery tips
    WCF4.0进阶系列—第十一章 编写代码控制配置和通信 (上)
    WCF4.0进阶系列—第九章 事务支持(上)
    WCF4.0进阶系列第二章 寄宿WCF服务
    WCF4.0进阶系列第五章 在因特网环境下保护WCF服务
    [JavaScript] onkeypress and onchange event
    [JavaScript]使用jQuery定制开发自己的UI
    WCF4.0进阶系列第四章 保护企业内部的WCF服务
    WCF4.0进阶系列第六章 维护服务协定和数据协定
    WCF4.0 进阶系列第一章 WCF简介
  • 原文地址:https://www.cnblogs.com/lancidie/p/3019359.html
Copyright © 2011-2022 走看看