Cocos2dx3.x使用socket建立服務端和用戶端改進,cocos2dx3.xsocket

來源:互聯網
上載者:User

Cocos2dx3.x使用socket建立服務端和用戶端改進,cocos2dx3.xsocket

由於一個網友使用筆者寫的SocketClient作為遊戲用戶端網路資料接收類,出現了一些問題
這個問題就是因為當執行onRecv時建立了一個Sprite(Sprite::create(“1.png”)),而建立完成後sprite的資料混亂,或者MoveTo時返回的也是混亂資料。原因在於在多線程申請記憶體,在主線程使用就會出現問題。為瞭解決這個問題,特意看了cocos2dx的WebSocket的實現方式,發現當接收到資料時並不是立即調用回呼函數,而是將資料資訊加入到訊息佇列,當主線程更新時檢查訊息佇列,來執行相應的回呼函數,為此就對SocketClient和SocketServer做了一些改進,當然使用方法沒有太大改變,同時解決了子線程申請記憶體出現的問題。
SocketBase.h 增加枚舉,及SocketMessage來儲存接收到訊息到訊息佇列

enum MessageType{    DISCONNECT,    RECEIVE,    NEW_CONNECTION};class SocketMessage{private:    MessageType msgType;    // 訊息類型    Data* msgData;          // 訊息資料public:    SocketMessage(MessageType type, unsigned char* data, int dataLen)    {        msgType = type;        msgData = new Data;        msgData->copy(data, dataLen);    }    SocketMessage(MessageType type)    {        msgType = type;        msgData = nullptr;    }    Data* getMsgData() { return msgData; }    MessageType getMsgType() { return msgType; }    ~SocketMessage()    {        if (msgData)            CC_SAFE_DELETE(msgData);    }};

增加兩個成員變數,作為處理接收的訊息

std::list<SocketMessage*> _UIMessageQueue;  // 儲存訊息的liststd::mutex   _UIMessageQueueMutex;      // 處理訊息的互斥變數

當接收到訊息時將訊息排入佇列, 仿照cocos2dx的WebSocket

if (ret > 0 && onRecv != nullptr){    std::lock_guard<std::mutex> lk(_UIMessageQueueMutex);       // 互斥    SocketMessage * msg = new SocketMessage(RECEIVE, (unsigned char*)recvBuf, ret);    _UIMessageQueue.push_back(msg); // 加入訊息佇列}

當在初始化用戶端時initClient,設定調度,讓UI每幀都檢查是否有訊息
Director::getInstance()->getScheduler()->scheduleUpdate(this, 0, false);
更新函數

void SocketClient::update(float dt){    if (_UIMessageQueue.size() == 0)        // 如果沒有訊息就退出    {        return;    }    _UIMessageQueueMutex.lock();        // 第一次檢查有訊息,設定互斥// 第二次檢查,如果已經沒有訊息就釋放互斥,要檢查兩次,舉個例子如果有兩個調度update,第一個執行上面的檢查_UIMessageQueue.size() !=0則會互斥鎖住,這時第二個也去檢查UIMessageQueue.size() !=0,也鎖住這時要等待第一個_UIMessageQueueMutex.unlock(),第一個執行完後沒有訊息,那麼第二個執行下面的檢查,結果沒有訊息,一定要unlock,這樣才能不出錯,兩次檢查保證線程不互鎖。    if (_UIMessageQueue.size() == 0)            {        _UIMessageQueueMutex.unlock();        return;    }    SocketMessage *msg = *(_UIMessageQueue.begin());        // 擷取第一個進入隊列的訊息,先到先服務,當然也可以用優先順序隊列,先執行優先順序高的訊息    _UIMessageQueue.pop_front();        // 記得從隊列刪除訊息    switch (msg->getMsgType())          // 根據訊息類型執行相應的回呼函數    {    case DISCONNECT:        if (onDisconnect)            this->onDisconnect();        break;    case RECEIVE:        if (onRecv)        {            this->onRecv((const char*)msg->getMsgData()->getBytes(), msg->getMsgData()->getSize());        }        break;    default:        break;    }    CC_SAFE_DELETE(msg);            // 刪除訊息,因為儲存訊息是用的new,所以這裡要刪除    _UIMessageQueueMutex.unlock();  // 互斥解鎖}

同時為了操作方便,保證使用SocketClient時不出現new SocketClient delete SocketClient, 將建構函式和解構函式設定為私人的,看過設計模式的同學應該都知道這樣做的目的,
提供construct建立SocketClient,和destroy銷毀SocketClient。

SocketClient* SocketClient::construct(){    SocketClient* client = new SocketClient;    return client;}void SocketClient::destroy(){    delete this;}

在解構函式刪除相應的東西

SocketClient::~SocketClient(void){    this->clear();}void SocketClient::clear(){    if (_socektClient != 0) // 關閉    {        _mutex.lock();        this->closeConnect(_socektClient);        _mutex.unlock();    }    for (auto msg : _UIMessageQueue)    // 刪除訊息,不對訊息進行處理    {        CC_SAFE_DELETE(msg);    }    _UIMessageQueue.clear();    Director::getInstance()->getScheduler()->unscheduleAllForTarget(this);}

SocketServer 當有新串連請求時,也將訊息儲存在訊息佇列

if (onNewConnection)    {        std::lock_guard<std::mutex> lk(_UIMessageQueueMutex);        SocketMessage * msg = new SocketMessage(NEW_CONNECTION, (unsigned char*)&socket, sizeof(HSocket));        _UIMessageQueue.push_back(msg);    }

對接收訊息做了一些改變,
由於接收到的訊息要確定是哪個client發來的,要儲存相應的client的socket

struct RecvData{    HSocket socketClient;    int dataLen;    char data[1024];};if (ret > 0 && onRecv != nullptr){    std::lock_guard<std::mutex> lk(_UIMessageQueueMutex);    RecvData recvData;          // 儲存socket資訊    recvData.socketClient = socket;    memcpy(recvData.data, buff, ret);    recvData.dataLen = ret;    SocketMessage * msg = new SocketMessage(RECEIVE, (unsigned char*)&recvData, sizeof(RecvData));    _UIMessageQueue.push_back(msg);}

同時在update時處理訊息

switch (msg->getMsgType())    {    case NEW_CONNECTION:        if (onNewConnection)        {            this->onNewConnection(*(HSocket*)msg->getMsgData()->getBytes());        }        break;    case DISCONNECT:        if (onDisconnect)        {            this->onDisconnect(*(HSocket*)msg->getMsgData()->getBytes());        }        break;    case RECEIVE:        if (onRecv)        {            RecvData* recvData = (RecvData*)msg->getMsgData()->getBytes();            this->onRecv(recvData->socketClient, (const char*)recvData->data, recvData->dataLen);        }        break;    default:        break;    }

對服務端使用了單例模式

SocketServer* SocketServer::getInstance(){    if (s_server == nullptr)    {        s_server = new SocketServer;    }    return s_server;}void SocketServer::destroyInstance(){    CC_SAFE_DELETE(s_server);}

為了測試修改的正確性,特地做了一個demo,demo很簡單,啟動後選擇Server還是Client
在Server點擊任意位置就會看到一個enemy走向指定位置,如果有用戶端串連,用戶端同樣有enemy根據Server發出的訊息執行相應的命令,由於只是一個簡單的demo,並沒左太多的同步處理。
效果如下:

三個圖
最上面的作為Server
左下方在Server未啟動時串連失敗,
右下方成功串連並接受Server控制,Server關閉時,串連斷開。

源碼及資源下載

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.