libuv经过Node.js的实践和应用,已经证明非常之成熟,本来之前项目用的是这个:clsocket https://github.com/DFHack/clsocket 当初选它的主要原因是它支持Windows、Linux、Mac OSX(我猜测的),但致命的缺点就是仅支持阻塞的TCP,这样就会导致一个问题,在连接游戏服务器、聊天服务器的时候游戏主界面会直接被卡死,等连接成功后才能恢复正常。而LuaSocket之前游戏也替换过,发现的问题主要是依赖lua的循环检测是否有新的数据(定时器),从而导致明显的界面延时。Cocos2d-x 3.x版本因为性能大幅提升,似乎此问题感受并不明显,而我们因为项目历史明显,lua 与 C 结合的很死,本身跑起来就一卡一卡的。
当然还有很多优秀的C TCP网络库,不过大部分似乎写的时候就只准备支持Linux/Unix,压根就没想支持Windows。而我们开发人员首先肯定是先在Windows下进行开发,神马?用Mac,公司连给iMac换个512G的SSD审批就很难,就别做梦了。自己出钱或者自带mac笔记本行么?不行!不允许使用外置USB,想用吗?走个OA单申请一下,一般开发人员的申请是直接被拒绝的,主程除外(好吧,我算比较幸运的 - “主程”)
吐槽归吐槽,活还是要干活的滴。libuv在实际使用中我发现的几个问题,如果连接socket时后台主动断开连接,那么后台最后发送出来的消息有可能会接收不到(概率性的,解决方法就是让后台发送消息完之后延时几秒再关闭socket连接)。iOS设备在关闭电源后,socket立马就断掉了,游戏从后台切换到前台时需要能自动重连一次。而libuv因为本身是用纯C实现的,它的回调方法基本上都是static函数,用C 封装的话有点小麻烦,网上也有人用C 11封装的比较好,可惜我使用的NDK版本比较低,支持不了C 11的特性只好放弃,但人家的思路还是可以借鉴的,觉得很赞。
以客户端为例,先了解下libuv的基本使用,示例来自gist (所有的libuv函数都以uv_开头)
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <uv.h>
#define log(x) printf("%sn", x);
uv_loop_t *loop;
void on_connect(uv_connect_t *req, int status);
void on_write_end(uv_write_t *req, int status);
uv_buf_t alloc_buffer(uv_handle_t *handle, size_t suggested_size);
void echo_read(uv_stream_t *server, ssize_t nread, uv_buf_t buf);
// サーバからのレスポンスを表示
void echo_read(uv_stream_t *server, ssize_t nread, uv_buf_t buf) {
if (nread == -1) {
fprintf(stderr, "error echo_read");
return;
}
// 結果を buf から取得して表示
printf("result: %sn", buf.base);
}
// suggeseted_size で渡された領域を確保
uv_buf_t alloc_buffer(uv_handle_t *handle, size_t suggested_size) {
// 読み込みのためのバッファを、サジェストされたサイズで確保
return uv_buf_init((char*) malloc(suggested_size), suggested_size);
}
// サーバへデータ送信後, サーバからのレスポンスを読み込む
void on_write_end(uv_write_t *req, int status) {
if (status == -1) {
fprintf(stderr, "error on_write_end");
return;
}
// 書き込みが終わったら、すぐに読み込みを開始
uv_read_start(req->handle, alloc_buffer, echo_read);
}
// サーバとの接続を確立後, サーバに文字列を送信
void on_connect(uv_connect_t *req, int status) {
if (status == -1) {
fprintf(stderr, "error on_write_end");
return;
}
// 送信メッセージを登録
char *message = "hello.txt";
int len = strlen(message);
/** これだとセグフォ
* uv_buf_t buf[1];
* buf[0].len = len;
* buf[0].base = message;
*/
// 送信データ用のバッファ
char buffer[100];
uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
// 送信データを載せる
buf.len = len;
buf.base = message;
// ハンドルを取得
uv_stream_t* tcp = req->handle;
// 書き込み用構造体
uv_write_t write_req;
int buf_count = 1;
// 書き込み
uv_write(&write_req, tcp, &buf, buf_count, on_write_end);
}
int main(void) {
// loop 生成
loop = uv_default_loop();
// Network I/O の構造体
uv_tcp_t client;
// loop への登録
uv_tcp_init(loop, &client);
// アドレスの取得
struct sockaddr_in req_addr = uv_ip4_addr("127.0.0.1", 7000);
// TCP コネクション用の構造体
uv_connect_t connect_req;
// 接続
uv_tcp_connect(&connect_req, &client, req_addr, on_connect);
// ループを開始
return uv_run(loop);
}
libuv使用的基本步骤:
1、生成一个loop (uv_default_loop() 或者 uv_loop_t _loop)
2、初始化一个client,uv_tcp_init
3、连接指定的服务器,uv_tcp_connect
4、开启消息循环,uv_run
通常使用时,我们都需要新启动一个线程,在该线程中来执行uv_run来保证不阻塞当前调用的线程(uv_run是阻塞的,不会立即返回)。
使用线程的关键函数:uv_thread_create(创建线程)、uv_async_init、uv_async_send(线程通信),消息的发送是异步的,在另外一个线程中多次(二次或更多)调用了uv_async_send函数后它只会保证uv_async_init回调函数至少被调用一次
uv_async_send是非阻塞的,同样也不是线程安全的,在变量访问时应该尽量和互斥量或读写锁来保证访问顺序。
我们游戏服务器是双线的,所以返回给客户端的数据是域名 端口,这里需要先将域名转为ip然后进行uv_tcp_connect连接。
示例代码:
代码语言:javascript复制uv_getaddrinfo_t* getaddrinfo_handle = (uv_getaddrinfo_t*)malloc(sizeof(uv_getaddrinfo_t));
getaddrinfo_handle->data = this;
int r = uv_getaddrinfo(&loop_, getaddrinfo_handle, &AfterDNSResolved, m_strDomain.c_str(), NULL, NULL);
//r 返回0时表示正常,非0则说明出错了可通过 uv_err_name(r)、uv_strerror(r)获得出错信息
uvbook的QueryDNS示例:
代码语言:javascript复制int main() {
loop = uv_default_loop();
struct addrinfo hints;
hints.ai_family = PF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = 0;
uv_getaddrinfo_t resolver;
fprintf(stderr, "irc.freenode.net is... ");
int r = uv_getaddrinfo(loop, &resolver, on_resolved, "irc.freenode.net", "6667", &hints);
if (r) {
fprintf(stderr, "getaddrinfo call error %sn", uv_err_name(r));
return 1;
}
return uv_run(loop, UV_RUN_DEFAULT);
}
void on_resolved(uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) {
if (status < 0) {
fprintf(stderr, "getaddrinfo callback error %sn", uv_err_name(status));
return;
}
char addr[17] = {'