协程使用之服务器实例

2016-11-07 ruki 更多博文 » 博客 » GitHub »

tbox 协程 服务器

原文链接 https://waruqi.github.io/2016/11/07/coroutine-echo-server.cn/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。


tbox内部的所有io操作都是原生支持协程的,可以在线程和协程间任意切换,内置基于轮询的io调度器(epoll, kqueue等,后续还会支持iocp).

我们在socket操作的时候,只需要像平常顺序编程那样操作就可以实现异步并发收发数据。

这里先给个简答的文件服务器的例子,可参考下,代码非常简单:

文件接收服务器

这个文件服务器的功能很简单,就是不停的接收连接,然后开新协程,进行文件传输。

通过协程,从原始socket写起,也只需要不到100行代码,就可以实现一个高并发的文件服务器。

此处用到了tb_socket_sendf直接对文件句柄操作,发送到socket,内部使用sendfile实现,不需要再上层开buffer读文件发送,因此效率会有明显提升。

tb_socket_wait内部会去将被等待的socket加入poll池,进行等待调度,直到事件到来才会继续执行。

并且协程的io调度器内部做了优化,不会去频繁得从轮询池里面添加删除socket,内部对epoll_ctl等操作会有个缓存,因此效率还是非常高的。

static tb_void_t tb_demo_coroutine_client(tb_cpointer_t priv)
{
    // check
    tb_socket_ref_t sock = (tb_socket_ref_t)priv;
    tb_assert_and_check_return(sock);

    // trace
    tb_trace_d("[%p]: sending %s ..", sock, g_filepath);

    // init file
    tb_file_ref_t file = tb_file_init(g_filepath, TB_FILE_MODE_RO | TB_FILE_MODE_BINARY);
    tb_assert_and_check_return(file);

    // send data
    tb_hize_t send = 0;
    tb_hize_t size = tb_file_size(file);
    tb_long_t wait = 0;
    tb_hong_t time = tb_mclock();
    while (send < size)
    {
        // 直接发送文件,利用sendfile优化文件传输
        tb_hong_t real = tb_socket_sendf(sock, file, send, size - send);

        // trace
        tb_trace_d("[%p]: send: %ld", sock, real);

        // has data?
        if (real > 0)
        {
            send += real;
            wait = 0;
        }
        // no data? wait it
        else if (!real && !wait)
        {
            // 如果在协程模式下,会把socket添加到io调度器中,进行自动调度
            wait = tb_socket_wait(sock, TB_SOCKET_EVENT_SEND, TB_DEMO_TIMEOUT);
            tb_assert_and_check_break(wait >= 0);
        }
        // failed or end?
        else break;
    }

    // trace
    tb_trace_i("[%p]: send: %lld bytes %lld ms", sock, send, tb_mclock() - time);

    // exit file
    tb_file_exit(file);

    // exit socket
    tb_socket_exit(sock);
}
static tb_void_t tb_demo_coroutine_listen(tb_cpointer_t priv)
{
    // done
    tb_socket_ref_t sock = tb_null;
    do
    {
        // init socket
        sock = tb_socket_init(TB_SOCKET_TYPE_TCP, TB_IPADDR_FAMILY_IPV4);
        tb_assert_and_check_break(sock);

        // bind socket
        tb_ipaddr_t addr;
        tb_ipaddr_set(&addr, tb_null, TB_DEMO_PORT, TB_IPADDR_FAMILY_IPV4);
        if (!tb_socket_bind(sock, &addr)) break;

        // listen socket
        if (!tb_socket_listen(sock, 1000)) break;

        // trace
        tb_trace_i("listening ..");

        // 此处也会进入协程io调度,等待accept事件
        while (tb_socket_wait(sock, TB_SOCKET_EVENT_ACPT, -1) > 0)
        {
            // accept client sockets
            tb_size_t       count = 0;
            tb_socket_ref_t client = tb_null;
            while ((client = tb_socket_accept(sock, tb_null)))
            {
                // start client connection
                if (!tb_coroutine_start(tb_null, tb_demo_coroutine_client, client, 0)) break;
                count++;
            }

            // trace
            tb_trace_i("listened %lu", count);
        }

    } while (0);

    // exit socket
    if (sock) tb_socket_exit(sock);
    sock = tb_null;
}
tb_int_t main(tb_int_t argc, tb_char_t** argv)
{
    // check
    tb_assert_and_check_return_val(argc == 2 && argv[1], -1);

    // the file path
    tb_char_t const* filepath = argv[1];
    tb_assert_and_check_return_val(filepath, -1);

    // save the file path
    tb_strlcpy(g_filepath, filepath, sizeof(g_filepath));

    // init scheduler
    tb_co_scheduler_ref_t scheduler = tb_co_scheduler_init();
    if (scheduler)
    {
        // start listening
        tb_coroutine_start(scheduler, tb_demo_coroutine_listen, tb_null, 0);

        // run scheduler
        tb_co_scheduler_loop(scheduler, tb_true);

        // exit scheduler
        tb_co_scheduler_exit(scheduler);
    }
    return 0;
}

http服务器例子

这个例子,可以直接参考tbox的demo源码,这里就不贴了,基本上跟上面的代码差不多,非常精简。。

目前,tbox并没有提供更进一步的http等服务器开发上的封装,因为tbox的定位主要是轻量级跨平台基础库,因此会尽量保持精简(也为此移除了老的asio实现,功能部分重叠),只提供最基础的跨平台操作,不会涉及太多应用层的封装。

另外一个原因,就是协程的实现已经使得服务器开发非常的简单,一般写个精简的http服务器也就几百行代码,因此tbox仅在demo实例代码里面提供了一个精简实现。

有兴趣的同学,可以看下:http_server

虽然这个例子很精简,但是如果只是做做简单的http服务器实现,这个实现完全足够使用了,而且性能还是很不错的。。

后续有时间的话,我会基于tbox单独写个服务器框架。。