展会信息港展会大全

Cocos2dx3.x使用socket创建服务端和客户端改进,cocos2dx3.xsocket
来源:互联网   发布日期:2015-09-28 13:27:21   浏览:3047次  

导读: Cocos2dx3.x使用socket创建服务端和客户端改进,cocos2dx3.xsocket 由于一个网友使用笔者写的SocketClient作为游戏客户端网络数据接收类,出...

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;// 储存消息的list

std::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关闭时,连接断开。

源码及资源下载

http://www.bkjia.com/Androidjc/959403.htmlwww.bkjia.comtruehttp://www.bkjia.com/Androidjc/959403.htmlTechArticleCocos2dx3.x使用socket创建服务端和客户端改进,cocos2dx3.xsocket 由于一个网友使用笔者写的SocketClient作为游戏客户端网络数据接收类,出现了一...

赞助本站

人工智能实验室

相关热词: android开发 android教程

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港