一起玩玩libuv(三)

这次我们利用uv_tcp_t来实现一个简单的echo server

1. libuv为TCP通信提供了什么

libuv中使用TCP通信和基本的socket编程相差无几:

  1. 使用uv_tcp_init初始化一个uv_tcp_t
  2. 使用uv_tcp_bind绑定ip和port
  3. 使用uv_listen监听客户端连接
  4. 使用uv_accept获取成功的连接

对libuv来说,TCP也是流数据的一种,所以读入和写出使用uv_writeuv_read_start两个接口。

2. 具体实现

具体的实现并不复杂:

  1. 创建第一个负责监听客户端连接的uv_tcp_t(server),使用uv_listen注册回调函数
  2. 在connect_cb的回调函数中使用uv_accept获取一个已经完成的连接,并赋值给一个新的uv_tcp_t(client)
  3. 关于向客户端写入:创建一个Request(uv_write_t)和一个Buffer(uv_buf_t),Buffer里面存储着要传送的信息,Request可以理解为一个向Stream的写入请求
  4. 关于读客户端信息:uv_read_start接口用于向libuv申请需要监听哪个uv_tcp_t的读信息,并在回调中处理读信息,回调函数nread参数说明信息长度,buf参数有信息内容

代码如下:

#include <stdio.h>
#include <uv.h>

#define DEFAULT_PORT 7000
#define DEFAULT_BACKLOG 5
#define PS ">>> "

struct sockaddr_in addr;

void alloc_buffer(uv_handle_t* handle,
                    size_t suggested_size,
                    uv_buf_t* buf) {
    buf->base = (char*)malloc(suggested_size);
    buf->len = suggested_size;
}

void write_to_remote(uv_stream_t* client, char* content, int size);

void read_cb(uv_stream_t* client,
             ssize_t nread,
             const uv_buf_t* buf) {
    if(nread < 0) {
        if(nread != UV_EOF)
            fprintf(stderr, "Read error %s\n", uv_strerror(nread));
        uv_close(client, NULL);
    } else if(nread > 0){
        write_to_remote(client, buf->base, nread);
    }
}

void write_cb(uv_write_t* req, int status) {
    if(status) {
        fprintf(stderr, "write cb error %s\n", uv_strerror(status));
    }
    free(req);
}

void write_to_remote(uv_stream_t* client, char* content, int size) {
    if(size < 0)
        size = strlen(content);
    uv_write_t* w_req = (uv_write_t*)malloc(sizeof(uv_write_t));
    char * reply = (char*)malloc(size + 5);
    strcat(reply, content);
    strcat(reply, PS);
    uv_buf_t buf = uv_buf_init(reply, strlen(reply));
    uv_write(w_req, client, &buf, 1, write_cb);
}

void connect_cb(uv_stream_t* server, int status) {
    if(status < 0) {
        fprintf(stderr, "new connect error %s", uv_strerror(status));
    } else {
        uv_tcp_t *client = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
        uv_tcp_init(uv_default_loop(), client);
        if(uv_accept(server, client) == 0) {
            write_to_remote(client, "Hi, I will repeat you.\n", -1);
            uv_read_start(client, alloc_buffer, read_cb);

        } else {
            fprintf(stderr, "accept error.\n");
        }
    }
}

int main()
{
    uv_tcp_t server;
    uv_tcp_init(uv_default_loop(), &server);

    uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);

    uv_tcp_bind(&server, (const struct sockaddr_in*)&addr, 0);

    int r = uv_listen(&server, DEFAULT_BACKLOG, connect_cb);
    if(r) {
        fprintf(stderr, "uv listen error.\n");
        return 1;
    }

    return uv_run(uv_default_loop(), UV_RUN_DEFAULT);
}

3. 验证

使用telnet连接Server,连接成功后,将会首先收到客户端的一条回复,之后你的每一次输入Server都会返回给客户端。

whosemario:~ whosemario$ telnet 127.0.0.1 7000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hi, I will repeat you.
>>> Hi, I am at home
Hi, I am at home
>>> I am Happy.
I am Happy.
>>> OK
OK
>>> Play
Play
>>>