Cocos2d-x Tutorial 之 Socket的使用(1)

本篇博文记录下 cocos2-x 中 socket 链接的建立

使用到了第三方跨平台的 socket 库 netlink

主页

下载地址

引擎版本:quick-cocos2d-x 3.3

一、配置环境

  • 1、下载并增加到 Xcode

从netlink官网下载netlink的包,解压缩并将解压缩后的文件夹放到 Classes 目录下,在 Xcode 中右键 Add Files ,增加 netLink 到项目中,打开 netLink group 删除多余的条目,只留下 include 和 src 即可。

  • 2、配置 SearchPath

打开项目文件,选择 project (如果只需要给某一个 target 增加,选择相应的 target ),选择 Build Settings ,选择 Search Paths ,给 User Header Search Path 增加

    $(SRCROOT)/../Classes/netLink/include
  • 3、编译项目 注意这时候编译的时候可能会报一个错误,如下图

解决办法:

打开 netLink/include/netlink/core.h, 在末尾加上 #include “unistd.h”

1
2
3
4
#include "netlink/exception.h"
#include "netlink/release_manager.h"
#include "netlink/util.h"
#include "unistd.h"

然后在编译,以上编译成功说明环境配置ok

二、编写 Socket 工具类

Socket工具类就直接贴代码加注释了

SocketUtil.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#ifndef __MyLuaGame__SocketUtil__
#define __MyLuaGame__SocketUtil__

#include 
#include "cocos2d.h"

namespace NL {
    class SocketGroup;
};
class ReceiveDeal;
class DisconnectDeal;

class SocketUtil : public cocos2d::Ref
{
public:

    typedef std::function<void(std::vector<int>)> ccMsgFunc;
    typedef std::function<void(bool)> ccStateFunc;

    //单例
    static SocketUtil* getInstance();

    //建立socket
    bool connectServer(const std::string & strIp, unsigned int nPort);

    //发送消息
    bool sendMsg(std::vector<int> & bytes);

    //接收消息
    void receiveMsg(std::vector<int> & bytes);

    //socket状态改变事件
    void socketStateChange(bool connected);

    //关闭链接
    void close();

    //增加消息回调
    void addMsgHandler(const ccMsgFunc& func);

    //增加状态回调
    void addStateHandler(const ccStateFunc& func);

    //增加消息回调 Lua
    void addMsgHandlerLua(const int func);

    //增加状态回调 Lua
    void addStateHandlerLua(const int func);

private:
    SocketUtil();
    virtual ~SocketUtil();

    //socket线程
    void connect_thread();

    //抛出消息
    void dispatchMsg(float dt);


public:
    std::string _ip;//ip地址
    unsigned int _port;//端口
    bool _disconnect;//socket链接状态
    std::mutex _mutex;//锁
    NL::SocketGroup * _socketGroup;//socket组
    ReceiveDeal * _receiveDeal;//消息处理类对象
    DisconnectDeal * _disconnectDeal;//断线处理类对象
    std::vector<std::vector<int>> _result;//消息集合
    std::vector<bool> _socketResult;//网络状态消息集合
    ccMsgFunc _msgHandler;
    ccStateFunc _stateHandler;
    int _msgHandlerLua;
    int _stateHandlerLua;

private:
    static SocketUtil* _instance;

};

#endif /* defined(__MyLuaGame__SocketUtil__) */

SocketUtil.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#include "SocketUtil.h"
#include "netlink/netlink.h"
#include "CCLuaEngine.h"
#include "LuaBasicConversions.h"

using namespace std;
using namespace cocos2d;
using namespace NL;

#pragma mark - ReceiveDeal
class ReceiveDeal : public NL::SocketGroupCmd {
public:
    virtual void exec(NL::Socket* socket, NL::SocketGroup* group, void* reference)
    {
        int readSize=socket->nextReadSize();//读取当前可读取的数据长度
        unsigned char readCh[readSize];//定义存储数据的字节数组
        socket->read(readCh, readSize);//读取
        vector<int> v;                 //此处是针对我们项目,可以删掉直接传
        for (int i=0; i < readSize; i++) {
            v.push_back(readCh[i]);
        }
        SocketUtil::getInstance()->receiveMsg(v);//发送服务端消息
    }
};

#pragma mark - DisconnectDeal
class DisconnectDeal : public NL::SocketGroupCmd{
public:
    virtual void exec(NL::Socket* socket, NL::SocketGroup* group, void* reference)
    {
        SocketUtil::getInstance()->close();         //关闭链接
        SocketUtil::getInstance()->socketStateChange(false);//发送断线消息
    }
};

#pragma mark - SocketUtil

SocketUtil* SocketUtil::_instance = nullptr;

SocketUtil* SocketUtil::getInstance()
{
    if(_instance == nullptr)
    {
        _instance = new SocketUtil();
    }
    return _instance;
}

SocketUtil::SocketUtil()
{
    _ip = "127.0.0.1";
    _port = 3000;
    _disconnect = true;
    _msgHandler = nullptr;
    _stateHandler = nullptr;
    _msgHandlerLua = 0;
    _stateHandlerLua = 0;
    std::lock_guard<std::mutex> lg(_mutex);
    _socketGroup = new SocketGroup();
    _receiveDeal = new ReceiveDeal();
    _disconnectDeal = new DisconnectDeal();
    Scheduler* scheduler = Director::getInstance()->getScheduler();//启动一个定时器去轮循分发消息
    scheduler->schedule(schedule_selector(SocketUtil::dispatchMsg), this, 0, false);
}

SocketUtil::~SocketUtil()
{
    delete _socketGroup;
    delete _receiveDeal;
    delete _disconnectDeal;
    Scheduler* scheduler = Director::getInstance()->getScheduler();//停止定时器
    scheduler->unschedule(schedule_selector(SocketUtil::dispatchMsg),this);
}

bool SocketUtil::connectServer(const string & strIp, unsigned int nPort) {
    if (!_disconnect) {
        printf("socket is already connected\n");
        return true;
    }
    this->_ip= strIp;
    this->_port = nPort;
    try {
        NL::init();//初始化
        _socketGroup->setCmdOnRead(_receiveDeal);//设置消息处理
        _socketGroup->setCmdOnDisconnect(_disconnectDeal);//设置断线处理
        thread connectThread(&SocketUtil::connect_thread, this);//连接操作是阻塞的,因此启动一个子线程去连接
        connectThread.detach();
    } catch (NL::Exception e) {
        cout << "connectServer: \n***ERROR*** " << e.what();
    }
    return false;
}

void SocketUtil::connect_thread()
{
    try {
        Socket* socket = new Socket(_ip, _port);
        _socketGroup->add(socket);
        //socket建立成功
        _disconnect = false;
        socketStateChange(true);
        while (true) {
            if (_disconnect) {
                Socket* socket = _socketGroup->get(0);
                socket->disconnect();
                _socketGroup->remove(socket);
                delete socket;
                break;
            }
            try {
                _socketGroup->listen(500);
            } catch (NL::Exception e) {
                cout << "listen_thread: \n***ERROR*** " << e.what();
            }
        }
    } catch (NL::Exception e) {
        cout << "connect_thread: \n***ERROR*** " << e.what();
        //socket建立失败
        _disconnect = true;
        socketStateChange(false);
    }
}

bool SocketUtil::sendMsg(std::vector<int> & bytes)//此处也可以改造
{
    if (_disconnect) {
        printf("socket disconnect\n");
        return false;
    }

    unsigned long int bytesLen = bytes.size();

    //转换成 byte 数组
    unsigned char ch[bytesLen];
    for (int i=0; i<bytesLen; i++) {
        ch[i] = bytes.at(i);
    }

    try {
        //发送
        _socketGroup->get(0)->send(ch,bytesLen);
        return true;
    } catch (NL::Exception e) {
        cout << "sendMsg: \n***ERROR*** " << e.what() << endl;
        return false;
    }
}

void SocketUtil::receiveMsg(std::vector<int> & bytes)
{
    _mutex.lock();//加锁
    _result.push_back(bytes);
    _mutex.unlock();//解锁
}

void SocketUtil::socketStateChange(bool connected)
{
    _mutex.lock();//加锁
    _socketResult.push_back(connected);
    _mutex.unlock();//解锁
}

void SocketUtil::dispatchMsg(float dt)
{
    _mutex.lock();//加锁
    for (int i=0; i<_socketResult.size(); i++) {
        bool connected = _socketResult.at(i);
        //通知游戏网络状态
        if (_stateHandler != nullptr) {
            _stateHandler(connected);
        }
        if (_stateHandlerLua != 0) {
            LuaStack* stack = LuaEngine::getInstance()->getLuaStack();
            stack->pushBoolean(connected);
            stack->executeFunctionByHandler(_stateHandlerLua, 1);
        }
    }
    _socketResult.clear();
    for (int i=0; i<_result.size(); i++) {
        vector<int> bytes = _result.at(i);
        //通知游戏收到数据
        if (_msgHandler != nullptr) {
            _msgHandler(bytes);
        }
        if (_msgHandlerLua != 0) {
            LuaStack* stack = LuaEngine::getInstance()->getLuaStack();
            ccvector_int_to_luaval(stack->getLuaState(), bytes);
            stack->executeFunctionByHandler(_msgHandlerLua, 1);
        }
    }
    _result.clear();
    _mutex.unlock();//解锁
}

void SocketUtil::close()
{
    //释放socket占用的资源
    _disconnect = true;
    socketStateChange(false);
}

void SocketUtil::addMsgHandler(const ccMsgFunc &func)
{
    _msgHandler = func;
}

void SocketUtil::addStateHandler(const ccStateFunc &func)
{
    _stateHandler = func;
}

void SocketUtil::addMsgHandlerLua(const int func)
{
    _msgHandlerLua = func;
}

void SocketUtil::addStateHandlerLua(const int func)
{
    _stateHandlerLua = func;
}

编译测试是否通过,通过即可进行下一步

三、编写服务端代码

新建一个 test.js 文件,并拷贝如下代码,监听 127.0.0.1 的 3000 端口。

下面代码作用是:客户端发送的内容,原封不动的返回回去。

test.js

1
2
3
4
5
6
7
var net = require('net');

var server = net.createServer(function (socket) {
  socket.pipe(socket);
});

server.listen(3000, '127.0.0.1');

保存代码为 test.js 并在命令行执行 node test.js 即可启动一个简单的 socket 服务端

可以 telnet 一下试试是否启动成功

1
telnet 127.0.0.1 3000

四、客户端链接服务器

修改 AppDelegate.h

继承 Ref,增加函数 schedulerSendMsg

AppDelegate.h

1
2
3
4
5
6
7
8
9
class  AppDelegate : private cocos2d::Application, public cocos2d::Ref
{
    ...
    void schedulerSendMsg(float dt);
    void schedulerClose(float dt);
    void msgHandler(std::vector<int> bytes);
    void stateHandler(bool state);
    ...
};

修改 AppDelegate.cpp

启动以后链接 socket,等待2秒,发送1条消息

AppDelegate.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
bool AppDelegate::applicationDidFinishLaunching()
{
    ...
    //增加回调
    SocketUtil::getInstance()->addMsgHandler(CC_CALLBACK_1(AppDelegate::msgHandler, this));
    SocketUtil::getInstance()->addStateHandler(CC_CALLBACK_1(AppDelegate::stateHandler, this));
    //建立链接
    SocketUtil::getInstance()->connectServer("127.0.0.1", 3000);
    //等待2秒,发送两条数据
    Director::getInstance()->getScheduler()->schedule(CC_CALLBACK_1(AppDelegate::schedulerSendMsg, this), this, 0, 1, 2, false, "send msg");
    //等待4秒,关闭链接
    Director::getInstance()->getScheduler()->schedule(CC_CALLBACK_1(AppDelegate::schedulerClose, this), this, 0, 0, 4, false, "close");
    ...
}

void AppDelegate::schedulerSendMsg(float dt)
{
    string myString = "hello server";
    std::vector<int> bytes(myString.begin(), myString.end());
    SocketUtil::getInstance()->sendMsg(bytes);
}

void AppDelegate::schedulerClose(float dt)
{
    SocketUtil::getInstance()->close();
}

void AppDelegate::msgHandler(std::vector<int> bytes)
{
    printf("receive data\n");
}

void AppDelegate::stateHandler(bool state)
{
    printf("state change: %s\n", state?"true":"false");
}

启动以后,等待几秒,检查控制台输出

1
2
3
4
state change: true
receive data
receive data
state change: false

至此,Socket前端和后端建立链接,发送和接受消息完成。

–EOF–

你可能感兴趣的:(Socket编程)