这次我们利用uv_tcp_t来实现一个简单的echo server
1. libuv为TCP通信提供了什么
libuv中使用TCP通信和基本的socket编程相差无几:
- 使用uv_tcp_init初始化一个uv_tcp_t
- 使用uv_tcp_bind绑定ip和port
- 使用uv_listen监听客户端连接
- 使用uv_accept获取成功的连接
对libuv来说,TCP也是流数据的一种,所以读入和写出使用uv_write和uv_read_start两个接口。
2. 具体实现
具体的实现并不复杂:
- 创建第一个负责监听客户端连接的uv_tcp_t(server),使用uv_listen注册回调函数
- 在connect_cb的回调函数中使用uv_accept获取一个已经完成的连接,并赋值给一个新的uv_tcp_t(client)
- 关于向客户端写入:创建一个Request(uv_write_t)和一个Buffer(uv_buf_t),Buffer里面存储着要传送的信息,Request可以理解为一个向Stream的写入请求
- 关于读客户端信息: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
>>>