cocos2dx socket的一個方案


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

 

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

 

// 發送消息
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、沒了,剩下的一些特殊處理應該是上層邏輯來考慮的。比如掉線重連等。

轉載地址:http://blog.csdn.net/langresser_king/article/details/8646088


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM