跨平台的游戏客户端Socket封装,调整_移动开发_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > 移动开发 > 跨平台的游戏客户端Socket封装,调整

跨平台的游戏客户端Socket封装,调整

 2013/9/4 15:14:29  elephant-x  博客园  我要评论(0)
  • 摘要:原文链接:http://www.cnblogs.com/lancidie/archive/2013/04/13/3019359.html头文件:#pragmaonce#ifdefWIN32#include<windows.h>#include<WinSock.h>#else#include<sys/socket.h>#include<fcntl.h>#include<errno.h>#include<netinet/in
  • 标签:游戏 客户 客户端 socket 跨平台

原文链接:http://www.cnblogs.com/lancidie/archive/2013/04/13/3019359.html

头文件

#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)
};

.cpp文件

    #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(constchar* pszServerIP, int nServerPort, int nBlockSec, bool bKeepAlive /*= FALSE*/) 
    { 
        // 检查参数 
        if(pszServerIP == 0 || strlen(pszServerIP) > 15) { 
            returnfalse; 
        } 
     
    #ifdef WIN32 
        WSADATA wsaData; 
        WORD version = MAKEWORD(2, 0); 
        int ret = WSAStartup(version, &wsaData);//win sock start up 
        if (ret != 0) { 
            returnfalse; 
        } 
    #endif 
     
        // 创建主套接字 
        m_sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
        if(m_sockClient == INVALID_SOCKET) { 
            closeSocket(); 
            returnfalse; 
        } 
     
        // 设置SOCKET为KEEPALIVE 
        if(bKeepAlive) 
        { 
            int     optval=1; 
            if(setsockopt(m_sockClient, SOL_SOCKET, SO_KEEPALIVE, (char *) &optval, sizeof(optval))) 
            { 
                closeSocket(); 
                returnfalse; 
            } 
        } 
     
    #ifdef WIN32 
        DWORD nMode = 1; 
        int nRes = ioctlsocket(m_sockClient, FIONBIO, &nMode); 
        if (nRes == SOCKET_ERROR) { 
            closeSocket(); 
            returnfalse; 
        } 
    #else 
        // 设置为非阻塞方式 
        fcntl(m_sockClient, F_SETFL, O_NONBLOCK); 
    #endif 
     
        unsigned long serveraddr = inet_addr(pszServerIP); 
        if(serveraddr == INADDR_NONE)   // 检查IP地址格式错误 
        { 
            closeSocket(); 
            returnfalse; 
        } 
     
        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(); 
                returnfalse; 
            } 
            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(); 
                    returnfalse; 
                } else  // ret > 0 
                { 
                    ret = FD_ISSET(m_sockClient, &exceptset); 
                    if(ret)     // or (!FD_ISSET(m_sockClient, &writeset) 
                    { 
                        closeSocket(); 
                        returnfalse; 
                    } 
                } 
            } 
        } 
     
        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, (constchar*)&so_linger, sizeof(so_linger)); 
     
        returntrue; 
    } 
     
    bool CGameSocket::SendMsg(void* pBuf, int nSize) 
    { 
        if(pBuf == 0 || nSize <= 0) { 
            returnfalse; 
        } 
     
        if (m_sockClient == INVALID_SOCKET) { 
            returnfalse; 
        } 
     
        // 检查通讯消息包长度 
        int packsize = 0; 
        packsize = nSize; 
     
        // 检测BUF溢出 
        if(m_nOutbufLen + nSize > OUTBUFSIZE) { 
            // 立即发送OUTBUF中的数据,以清空OUTBUF。 
            Flush(); 
            if(m_nOutbufLen + nSize > OUTBUFSIZE) { 
                // 出错了 
                Destroy(); 
                returnfalse; 
            } 
        } 
        // 数据添加到BUF尾 
        memcpy(m_bufOutput + m_nOutbufLen, pBuf, nSize); 
        m_nOutbufLen += nSize; 
        returntrue; 
    } 
     
    bool CGameSocket::ReceiveMsg(void* pBuf, int& nSize) 
    { 
        //检查参数 
        if(pBuf == NULL || nSize <= 0) { 
            returnfalse; 
        } 
         
        if (m_sockClient == INVALID_SOCKET) { 
            returnfalse; 
        } 
     
        // 检查是否有一个消息(小于2则无法获取到消息长度) 
        if(m_nInbufLen < 2) { 
            //  如果没有请求成功  或者   如果没有数据则直接返回 
            if(!recvFromSock() || m_nInbufLen < 2) {     // 这个m_nInbufLen更新了 
                returnfalse; 
            } 
        } 
     
        // 计算要拷贝的消息的大小(一个消息,大小为整个消息的第一个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; 
            returnfalse; 
        } 
     
        // 检查消息是否完整(如果将要拷贝的消息大于此时缓冲区数据长度,需要再次请求接收剩余数据) 
        if (packsize > m_nInbufLen) { 
            // 如果没有请求成功   或者    依然无法获取到完整的数据包  则返回,直到取得完整包 
            if (!recvFromSock() || packsize > m_nInbufLen) { // 这个m_nInbufLen已更新 
                returnfalse; 
            } 
        } 
     
        // 复制出一个消息 
        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 
            returntrue; 
        } 
     
        returnfalse; 
    } 
     
    // 从网络中读取尽可能多的数据,实际向服务器请求数据的地方 
    bool CGameSocket::recvFromSock(void) 
    { 
        if (m_nInbufLen >= INBUFSIZE || m_sockClient == INVALID_SOCKET) { 
            returnfalse; 
        } 
     
        // 接收第一段数据 
        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) { 
                returnfalse; 
            } 
     
            // 接收第二段数据(一次接收没有完成,接收第二段数据) 
            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) { 
                        returnfalse; 
                    }    
                } elseif(inlen == 0) { 
                    Destroy(); 
                    returnfalse; 
                } else { 
                    // 连接已断开或者错误(包括阻塞) 
                    if (hasError()) { 
                        Destroy(); 
                        returnfalse; 
                    } 
                } 
            } 
        } elseif(inlen == 0) { 
            Destroy(); 
            returnfalse; 
        } else { 
            // 连接已断开或者错误(包括阻塞) 
            if (hasError()) { 
                Destroy(); 
                returnfalse; 
            } 
        } 
     
        returntrue; 
    } 
     
    bool CGameSocket::Flush(void)       //? 如果 OUTBUF > SENDBUF 则需要多次SEND() 
    { 
        if (m_sockClient == INVALID_SOCKET) { 
            returnfalse; 
        } 
     
        if(m_nOutbufLen <= 0) { 
            returntrue; 
        } 
         
        // 发送一段数据 
        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) { 
                returnfalse; 
            } 
        } else { 
            if (hasError()) { 
                Destroy(); 
                returnfalse; 
            } 
        } 
     
        returntrue; 
    } 
     
    bool CGameSocket::Check(void) 
    { 
        // 检查状态 
        if (m_sockClient == INVALID_SOCKET) { 
            returnfalse; 
        } 
     
        char buf[1]; 
        int ret = recv(m_sockClient, buf, 1, MSG_PEEK); 
        if(ret == 0) { 
            Destroy(); 
            returnfalse; 
        } elseif(ret < 0) { 
            if (hasError()) { 
                Destroy(); 
                returnfalse; 
            } else {    // 阻塞 
                returntrue; 
            } 
        } else {    // 有数据 
            returntrue; 
        } 
         
        returntrue; 
    } 
     
    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, (constchar*)&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)); 
    } 

使用例子

    // 发送消息 
    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((constchar*)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、没了,剩下的一些特殊处理应该是上层逻辑来考虑的。比如掉线重连等。

 

原文的代码本来没有问题,但有个注释打错了,可能会对一些开发者造成误解,即:

.cpp文件里面

 

// 计算要拷贝的消息的大小(一个消息,大小为整个消息的第一个16字节位,有些游戏是把长度放在第二个16位,那就把m_nInbufStart再+2,即向后移2字节取2字节),因为环形缓冲区,所以要分开计算 
        int packsize = (unsigned char)m_bufInput[m_nInbufStart] + 
            (unsigned char)m_bufInput[(m_nInbufStart + 1) % INBUFSIZE] * 256; // 注意字节序,高位+低位 

 

 

 

发表评论
用户名: 匿名