Cocos2dx3. x use socket to create the server and client for improvement, cocos2dx3. xsocket
A netizen used the SocketClient written by the author as the network data receiving Class of the game client, which caused some problems.
This problem is because a Sprite (Sprite: create(+1.png ") is created when onRecv is executed, and the sprite data after creation is disordered, or the data returned by MoveTo is also chaotic. The reason is that the memory is applied for in multiple threads, and problems may occur when used in the main thread. To solve this problem, I read the implementation method of cocos2dx WebSocket,It is found that when data is received, the callback function is not called immediately, but the data information is added to the Message Queue. When the main thread is updated, the message queue is checked to execute the corresponding callback function.To this end, we have made some improvements to SocketClient and SocketServer. Of course, the usage method has not changed much, and the problems that occur when the sub-thread applies for memory are also solved.
SocketBase. h adds enumeration and SocketMessage to save received messages to the message queue.
Enum MessageType {DISCONNECT, RECEIVE, NEW_CONNECTION}; class SocketMessage {private: MessageType msgType; // message type Data * msgData; // message Data 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 );}};
Add two member variables to process received messages.
Std: list <SocketMessage *> _ UIMessageQueue; // liststd: mutex _ UIMessageQueueMutex for storing messages; // mutex variable for processing messages
When a message is received, the message is added to the queue, which is similar to the WebSocket of cocos2dx.
If (ret> 0 & onRecv! = Nullptr) {std: lock_guard <std: mutex> lk (_ UIMessageQueueMutex); // mutually exclusive SocketMessage * msg = new SocketMessage (RECEIVE, (unsigned char *) recvBuf, ret); _ UIMessageQueue. push_back (msg); // Add to message queue}
When initializing the client, initClient sets scheduling so that each frame of the UI can check whether messages exist.
Director: getInstance ()-> getScheduler ()-> scheduleUpdate (this, 0, false );
Update Functions
Void SocketClient: update (float dt) {if (_ UIMessageQueue. size () = 0) // exit {return;} _ UIMessageQueueMutex if no message exists. lock (); // If a message exists during the first check, set mutex. // if no message exists, release the mutex. check twice, for example, if two updates are scheduled, the first check _ UIMessageQueue is executed. size ()! If it is set to 0, the lock is mutually exclusive. At this time, the second one also checks UIMessageQueue. size ()! = 0, also lock at this time to wait for the first _ UIMessageQueueMutex. unlock (), there is no message after the first execution, then the second executes the following check, the result has no message, must be unlocked, so that no error occurs, the two checks ensure that threads do not lock each other. If (_ UIMessageQueue. size () = 0) {_ UIMessageQueueMutex. unlock (); return;} SocketMessage * msg = * (_ UIMessageQueue. begin (); // gets the first message that enters the queue. It is first served. Of course, you can also use the priority queue to first execute the message _ UIMessageQueue with the highest priority. pop_front (); // remember to delete the message switch (msg-> getMsgType () from the queue. // execute the corresponding callback function {case DISCONNECT: if (onDisconnect) according to the Message type) this-> onDisconnect (); break; case RECEIVE: if (onRecv) {this-> onRecv (const char *) msg-> getMsgData ()-> getBytes (), msg-> getMsgData ()-> getSize ();} break; default: break;} CC_SAFE_DELETE (msg); // delete a message because the new message is used to save the message, delete _ UIMessageQueueMutex here. unlock (); // mutually exclusive unlock}
At the same time, for ease of operation, it is ensured that new SocketClient delete SocketClient is not displayed when SocketClient is used. The constructor and destructor are set to private. Those who have read the design mode should know the purpose of doing so,
Construct is provided to create SocketClient and destroy to destroy SocketClient.
SocketClient* SocketClient::construct(){ SocketClient* client = new SocketClient; return client;}void SocketClient::destroy(){ delete this;}
Delete the corresponding items in the destructor
SocketClient ::~ SocketClient (void) {this-> clear ();} void SocketClient: clear () {if (_ socektClient! = 0) // close {_ mutex. lock (); this-> closeConnect (_ socektClient); _ mutex. unlock () ;}for (auto msg: _ UIMessageQueue) // deletes a message. The message is not processed {CC_SAFE_DELETE (msg);} _ UIMessageQueue. clear (); Director: getInstance ()-> getScheduler ()-> unscheduleAllForTarget (this );}
When a new connection request is sent, SocketServer also saves the message in the message queue.
if (onNewConnection) { std::lock_guard<std::mutex> lk(_UIMessageQueueMutex); SocketMessage * msg = new SocketMessage(NEW_CONNECTION, (unsigned char*)&socket, sizeof(HSocket)); _UIMessageQueue.push_back(msg); }
Some changes have been made to receive messages,
Because the received message must be determined which client sent the message, the corresponding client's socket must be saved.
Struct RecvData {HSocket socketClient; int dataLen; char data [1024] ;}; if (ret> 0 & onRecv! = Nullptr) {std: lock_guard <std: mutex> lk (_ UIMessageQueueMutex); RecvData recvData; // Save the socket information 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 );}
Message Processing during 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; }
The Singleton mode is used for the server.
SocketServer* SocketServer::getInstance(){ if (s_server == nullptr) { s_server = new SocketServer; } return s_server;}void SocketServer::destroyInstance(){ CC_SAFE_DELETE(s_server);}
In order to test the correctness of the modification, we made a demo. The demo is very simple. after starting the demo, we chose Server or Client.
Click any location on the Server and you will see an enemy going to the specified location. If a client is connected, the client also has enemy to execute corresponding commands Based on the messages sent by the Server, because it is only a simple demo, there is not much synchronization on the left.
The effect is as follows:
Three figures
The top Server
The connection fails when the Server is not started in the lower-left corner,
The connection is successfully established at the bottom right of the page and controlled by the Server. When the Server is closed, the connection is disconnected.
Download source code and resources