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

posted @ 2013-04-13 22:06  oayx  阅读(5743)  评论(4)    收藏  举报