转自:http://www.cnblogs.com/elephant-x/archive/2013/09/06/3304903.html
声明:本文参考了langresser发布的blog“跨平台的游戏客户端Socket封装 ”
Socket处理是异步非阻塞的,所以可以放心的放到主线程处理消息,并且在原作者的基本上进行了系列优化,考虑了客户端可能建立多个SOCKET,因此加入了Manager概念,与cocos2d-x进行了融合。
本文基于cocos2d-x3.0+VS2012
文件目录结构截图:
文件源码:
TCPSocket.h
1 #ifndef __CC_TCPSOCKET_H__ 2 #define __CC_TCPSOCKET_H__ 3 4 #include "cocos2d.h" 5 #include "ExtensionMacros.h" 6 #include "WorldPacket.h" 7 #ifdef WIN32 8 #include <windows.h> 9 #include <WinSock.h> 10 #pragma comment( lib, "ws2_32.lib" ) 11 #else 12 #include <sys/socket.h> 13 #include <fcntl.h> 14 #include <errno.h> 15 #include <netinet/in.h> 16 #include <arpa/inet.h> 17 18 #define SOCKET int 19 #define SOCKET_ERROR -1 20 #define INVALID_SOCKET -1 21 22 #endif 23 24 NS_CC_EXT_BEGIN 25 #ifndef CHECKF 26 #define CHECKF(x) \ 27 do \ 28 { \ 29 if (!(x)) { \ 30 log_msg("CHECKF", #x, __FILE__, __LINE__); \ 31 return 0; \ 32 } \ 33 } while (0) 34 #endif 35 36 #define _MAX_MSGSIZE 16 * 1024 // 暂定一个消息最大为16k 37 #define BLOCKSECONDS 30 // INIT函数阻塞时间 38 #define INBUFSIZE (64*1024) //? 具体尺寸根据剖面报告调整 接收数据的缓存 39 #define OUTBUFSIZE (8*1024) //? 具体尺寸根据剖面报告调整。 发送数据的缓存,当不超过8K时,FLUSH只需要SEND一次 40 41 class CC_DLL TCPSocket 42 { 43 public: 44 TCPSocket(void); 45 bool Create(const char* pszServerIP, int nServerPort, int tagid, int nBlockSec = BLOCKSECONDS, bool bKeepAlive = false); 46 bool SendMsg(void* pBuf, int nSize); 47 bool ReceiveMsg(void* pBuf, int& nSize); 48 bool Flush(void); 49 bool Check(void); 50 void Destroy(void); 51 SOCKET GetSocket(void) const { return m_sockClient; } 52 53 int getTagID(){ return m_tag; } 54 private: 55 bool recvFromSock(void); // 从网络中读取尽可能多的数据 56 bool hasError(); // 是否发生错误,注意,异步模式未完成非错误 57 void closeSocket(); 58 59 SOCKET m_sockClient; 60 61 // 发送数据缓冲 62 char m_bufOutput[OUTBUFSIZE]; //? 可优化为指针数组 63 int m_nOutbufLen; 64 65 // 环形缓冲区 66 char m_bufInput[INBUFSIZE]; 67 int m_nInbufLen; 68 int m_nInbufStart; // INBUF使用循环式队列,该变量为队列起点,0 - (SIZE-1) 69 int m_tag; 70 }; 71 72 typedef std::function<bool(int,int,WorldPacket&)> ProAllFunc; // 接收所有协议,自行处理,@socket标识,@协议头,@数据包,返回是否分发 73 typedef std::function<void(int,WorldPacket&)> ProFunc; // 接收单个协议,@socket标识,@数据包 74 typedef std::function<void(int)> sckFunc; // 连接成功/断开事件 75 76 #define SCT_CALLBACK_1(func, _Object) std::bind(&func,_Object, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3) 77 #define SCT_CALLBACK_2(func, _Object) std::bind(&func,_Object, std::placeholders::_1, std::placeholders::_2) 78 #define SCT_CALLBACK_3(func, _Object) std::bind(&func,_Object, std::placeholders::_1) 79 // 创建SOCKET管理器 80 #define CREATE_TCPSOCKETMGR(pNode) pNode->addChild(new TCPSocketManager(), 0) 81 82 class CC_DLL TCPSocketManager : 83 public Node 84 { 85 public: 86 TCPSocketManager() 87 { 88 assert(!mSingleton); 89 this->mSingleton = this; 90 // 开启update 91 scheduleUpdate(); 92 }; 93 ~TCPSocketManager(){}; 94 // 创建socket并添加到管理器 95 TCPSocket *createSocket(const char* pszServerIP, // IP地址 96 int nServerPort, // 端口 97 int _tag, // 标识ID 98 int nBlockSec = BLOCKSECONDS, // 阻塞时间ms 99 bool bKeepAlive = false);100 // 注册协议包101 void register_process(const uint16 &entry, ProFunc callback);102 // 注册接收所有协议103 void register_all_process(ProAllFunc callback){ _pProcess = callback; }104 // 注册socket连接成功事件105 void register_connect(sckFunc callback){ _pOnConnect = callback; }106 // 注册socket断线事件107 void register_disconnect(sckFunc callback){ _OnDisconnect = callback; }108 109 // 单独添加socket到管理器110 bool addSocket(TCPSocket *pSocket);111 // 删除socket112 bool removeSocket(int _tag);113 // 断开socket114 void disconnect(int _tag);115 // 获取socket116 TCPSocket *GetSocket(int _tag);117 // 发送消息118 bool SendPacket(int _tag, WorldPacket *packet);119 120 void update(float delta);121 122 static TCPSocketManager &getSingleton(){ assert(mSingleton); return *mSingleton;}123 124 private: 125 ProAllFunc _pProcess;126 sckFunc _pOnConnect;127 sckFunc _OnDisconnect;128 std::list<TCPSocket*> m_lstSocket;129 std::map<uint16, ProFunc> _mapProcess;130 static TCPSocketManager * mSingleton;131 };132 133 #define sSocketMgr TCPSocketManager::getSingleton()134 NS_CC_EXT_END135 136 #endif //__CC_TCPSOCKET_H__
TCPSocket.cpp
#include "TCPSocket.h"#include "support/zip_support/unzip.h"NS_CC_EXT_BEGINTCPSocket::TCPSocket(){ // 初始化 memset(m_bufOutput, 0, sizeof(m_bufOutput)); memset(m_bufInput, 0, sizeof(m_bufInput));}void TCPSocket::closeSocket(){#ifdef WIN32 closesocket(m_sockClient); WSACleanup();#else close(m_sockClient);#endif}bool TCPSocket::Create(const char* pszServerIP, int nServerPort, int tagid, 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)); m_tag = tagid; return true;}bool TCPSocket::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 TCPSocket::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+2] + (unsigned char)m_bufInput[(m_nInbufStart + 3) % 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 TCPSocket::hasError(){#ifdef WIN32 int err = WSAGetLastError(); if(err != WSAEWOULDBLOCK) {#else int err = errno; if(err != EINPROGRESS && err != EAGAIN) {#endif return true; } return false;}// 从网络中读取尽可能多的数据,实际向服务器请求数据的地方bool TCPSocket::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 TCPSocket::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 TCPSocket::Check(void){ // 检查状态 if (m_sockClient == INVALID_SOCKET) { return false; } char buf[1]; int ret = recv(m_sockClient, buf, 1, MSG_PEEK); if(ret ==