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_开头)

  1. #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <uv.h>
     
    #define log(x) printf("%s\n", 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);
  2.  
  3. // サーバからのレスポンスを表示
    void echo_read(uv_stream_t *server, ssize_t nread, uv_buf_t buf) {
      if (nread == -1) {
        fprintf(stderr, "error echo_read");
        return;
      }
  4.  
  5.   // 結果を buf から取得して表示
      printf("result: %s\n", buf.base);
    }
  6.  
  7. // 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);
    }
  8.  
  9. // サーバへデータ送信後, サーバからのレスポンスを読み込む
    void on_write_end(uv_write_t *req, int status) {
      if (status == -1) {
        fprintf(stderr, "error on_write_end");
        return;
      }
  10.  
  11.   // 書き込みが終わったら、すぐに読み込みを開始
      uv_read_start(req->handle, alloc_buffer, echo_read);
    }
  12.  
  13. // サーバとの接続を確立後, サーバに文字列を送信
    void on_connect(uv_connect_t *req, int status) {
      if (status == -1) {
        fprintf(stderr, "error on_write_end");
        return;
      }
  14.  
  15.   // 送信メッセージを登録
      char *message = "hello.txt";
      int len = strlen(message);
  16.  
  17.   /** これだとセグフォ
       * uv_buf_t buf[1];
       * buf[0].len = len;
       * buf[0].base = message;
       */
  18.  
  19.   // 送信データ用のバッファ
      char buffer[100];
      uv_buf_t buf = uv_buf_init(buffer, sizeof(buffer));
  20.  
  21.   // 送信データを載せる
      buf.len = len;
      buf.base = message;
  22.  
  23.   // ハンドルを取得
      uv_stream_t* tcp = req->handle;
  24.  
  25.   // 書き込み用構造体
      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();
  26.  
  27.   // Network I/O の構造体
      uv_tcp_t client;
  28.  
  29.   // loop への登録
      uv_tcp_init(loop, &client);
  30.  
  31.   // アドレスの取得
      struct sockaddr_in req_addr = uv_ip4_addr("127.0.0.1", 7000);
  32.  
  33.   // TCP コネクション用の構造体
      uv_connect_t connect_req;
  34.  
  35.   // 接続
      uv_tcp_connect(&connect_req, &client, req_addr, on_connect);
  36.  
  37.   // ループを開始
      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连接。

示例代码:

  1. 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示例:
  1. int main() {
        loop = uv_default_loop();
  2.  
  3.     struct addrinfo hints;
        hints.ai_family = PF_INET;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_protocol = IPPROTO_TCP;
        hints.ai_flags = 0;
  4.  
  5.     uv_getaddrinfo_t resolver;
        fprintf(stderr, "irc.freenode.net is... ");
        int r = uv_getaddrinfo(loop, &resolver, on_resolved, "irc.freenode.net", "6667", &hints);
  6.  
  7.     if (r) {
            fprintf(stderr, "getaddrinfo call error %s\n", 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 %s\n", uv_err_name(status));
            return;
        }
  8.  
  9.     char addr[17] = {'\0'};
        uv_ip4_name((struct sockaddr_in*) res->ai_addr, addr, 16);
        fprintf(stderr, "%s\n", addr);
  10.  
  11.     uv_connect_t *connect_req = (uv_connect_t*) malloc(sizeof(uv_connect_t));
        uv_tcp_t *socket = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));
        uv_tcp_init(loop, socket);
  12.  
  13.     uv_tcp_connect(connect_req, socket, (const struct sockaddr*) res->ai_addr, on_connect);
  14.  
  15.     uv_freeaddrinfo(res);
    }

Windows下的libuv工程构建,使用官方推荐的gyp生成vs的解决方案即可

1、安装并设置python(2.6或2.7版本)

2、源码目录下新建build目录,然后将gyp下载至该目录

3、双击执行vcbuild.bat即可

在其它项目中引用libuv.lib时,需要在VS附加依赖项中添加几个系统的lib,不然会报错

libuv.lib

advapi32.lib

iphlpapi.lib

psapi.lib

shell32.lib

userenv.lib

ws2_32.lib

 

Android下编译libuv.a,我安装了虚拟机然后折腾了好一会,最后放弃了,参考Linux生成的mk自己整了一个完整的mk文件

  1. LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
  2.  
  3. LOCAL_MODULE := uv_static
  4.  
  5. LOCAL_MODULE_FILENAME := libuv
  6.  
  7. #LOCAL_SRC_FILES := proj.android/libuv.a
    #LOCAL_EXPORT_CFLAGS := -I$(LOCAL_PATH)/include
  8.  
  9. LOCAL_SRC_FILES := \
    src/fs-poll.c \
    src/inet.c \
    src/threadpool.c \
    src/uv-common.c \
    src/version.c \
    src/unix/async.c \
    src/unix/core.c \
    src/unix/dl.c \
    src/unix/fs.c \
    src/unix/getaddrinfo.c \
    src/unix/getnameinfo.c \
    src/unix/loop.c \
    src/unix/loop-watcher.c \
    src/unix/pipe.c \
    src/unix/poll.c \
    src/unix/process.c \
    src/unix/signal.c \
    src/unix/stream.c \
    src/unix/tcp.c \
    src/unix/thread.c \
    src/unix/timer.c \
    src/unix/tty.c \
    src/unix/udp.c \
    src/unix/proctitle.c \
    src/unix/linux-core.c \
    src/unix/linux-inotify.c \
    src/unix/linux-syscalls.c \
    src/unix/pthread-fixes.c \
    src/unix/android-ifaddrs.c
  10.  
  11. LOCAL_C_INCLUDES := $(LOCAL_PATH)/include \
                        $(LOCAL_PATH)/src \
                        $(LOCAL_PATH)/src/unix
  12.  
  13. LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include \
                               $(LOCAL_PATH)/src \
                               $(LOCAL_PATH)/src/unix
  14.  
  15. LOCAL_CFLAGS := \
    -Wall \
    -fvisibility=hidden \
    -g \
    --std=gnu89 \
    -pedantic \
    -Wall \
    -Wextra \
    -Wno-unused-parameter \
    -Wstrict-aliasing \
    -O3 \
    -fstrict-aliasing \
    -fomit-frame-pointer \
    -fdata-sections \
    -ffunction-sections
  16.  
  17. include $(BUILD_STATIC_LIBRARY)

在项目的jni/Android.mk中只需要添加

LOCAL_WHOLE_STATIC_LIBRARIES += uv_static

$(call import-module,libuv)

//注意添加NDK的搜索路径

而自己项目中需要用到uv.h时,修改相应的Android.mk文件,在

LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../libuv/include \

//目录自己依照自己的环境进行修改

一切正常的话,在eclipse项目的obj目录下会看到编译成功后的文件

 

 

iOS下的编译,默认libuv只提供了Mac下的编译,修改一下就可以让它支持iOS了

1、下载libuv  https://github.com/joyent/libuv/releases

2、shell进入libuv

$mkdir -p build

$git clone https://git.chromium.org/external/gyp.git  build/gyp

3、$./gyp_uv.py -f xcode

生成xcode项目文件uv.xcodeproj

4、打开xcode,修改architectures

 

XCode7的话,直接先最新的SDK版本,上面的用的比较旧。引用项目的话,将libuv.a加到项目中,然后Framework Search Path以及Header Search Path添加路径即可。

 

到这里,三个平台全部都搞定了。我引入到项目中,主要是参考了

libuv_tcp https://github.com/wqvbjhc/libuv_tcp

也借鉴了

libsourcey  https://github.com/sourcey/libsourcey

uvpp  https://github.com/larroy/uvpp

 

我自己收藏的一个libuv for lua的项目,觉得也挺不错的。luv https://github.com/luvit/luv  有兴趣可以去捣鼓一下,跟Node.js差不多

 

附带贴一下uvbook的二个链接:

英文版(最新V1.3.0)  https://nikhilm.github.io/uvbook/introduction.html

中文版(V0.9.8) http://www.nowx.org/uvbook/index.html

libuv在cocos2d-x中的使用的更多相关文章

  1. 如何在cocos2d项目中enable ARC

    如何在cocos2d项目中enable ARC 基本思想就是不支持ARC的代码用和支持ARC的分开,通过xcode中设置编译选项,让支持和不支持ARC的代码共存. cocos2d是ios app开发中 ...

  2. 如何在Cocos2D游戏中实现A*寻路算法(六)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  3. 如何在Cocos2D游戏中实现A*寻路算法(一)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  4. 如何在Cocos2D游戏中实现A*寻路算法(八)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  5. 如何在Cocos2D游戏中实现A*寻路算法(七)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  6. 如何在Cocos2D游戏中实现A*寻路算法(五)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  7. 如何在Cocos2D游戏中实现A*寻路算法(四)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  8. 如何在Cocos2D游戏中实现A*寻路算法(三)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  9. 如何在Cocos2D游戏中实现A*寻路算法(二)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...

  10. Cocos2D场景中对象引用为nil时的判断

    如果该对象在SpriteBuilder中属性中设置了name,则检查是否 [self.scene getChildByName:@"theNameOfTheNode" recurs ...

随机推荐

  1. Oracle常量

    Oracle是有常量的,而SqlServer是没有常量的 queryFrom constant ) := ' hello ';

  2. TNetHTTPClient演示

    TNetHTTPClient演示 TNetHTTPClient是DELPHI新增加的异步HTTP通信控件(区别于INDY的阻塞控件). unit Unit1; interface uses Winap ...

  3. 使用GITHUB的体会

    github的网页链接  https://github.com/zhangji123/test 学习总结 通过学习使用github软件使我掌握了其使用方法及其独特之处 1.github的网址: htt ...

  4. Daily Scrum 12.4

    今日完成任务: 对数据库完成了整理,以下是整理的内容: # 表 改动 原因 1 Answer 保留credit列,作为投票数 建议改名为vote,同意?   2 Answer qid.uid设置为外码 ...

  5. load-on-startup在web.xml中的含义

    在servlet的配置当中,<load-on-startup>1</load-on-startup>的含义是: 标记容器是否在启动的时候就加载这个servlet. 当值为0或者 ...

  6. unity --项目总结

    最近做的unity的项目涉及到的问题如下: 1.绘制折线图问题: 起初利用的unity自带的linerender组件,这种方法绘制的线不均匀,效果不好.然后又利用画线插件Ves……开头的那个,结果那个 ...

  7. [转]MySQL中存储过程权限问题

    MySQL中以用户执行存储过程的权限为EXECUTE 比如我们在名为configdb的数据库下创建了如下存储过程,存储过程的定义者为user_admin use configdb; drop proc ...

  8. office2003-2007 绿色版 出错 文件丢失(未解决)

    - 这个版本是我大学时候(2012)年一直用到现在的版本:目录结构如下: 原来一直在32位系统中使用,没有出错过; - 刚装的两台电脑系统分别为 Win7Pro 和 Win10Pro ,都是64位的: ...

  9. JavaWeb学习总结(三)——Tomcat服务器学习和使用(二)

    一.打包JavaWeb应用 在Java中,使用"jar"命令来对将JavaWeb应用打包成一个War包,jar命令的用法如下:

  10. write函数出错返回invalid argument(EINVAL)问题

    还是在下载机上面遇到的. 话说为了长久的下载,后面又买了个16G的U盘格成EXT3放在角落下载,结果发现总是有几个种子在下载的时候会出错提示invalid argument. 之前也出过一样的错误提示 ...