libuv编译及使用笔记

libuv使用笔记。

一、编译

下载:

1
https://github.com/libuv/libuv/releases/tag/v1.20.3

编译:

1
2
3
./configure --prefix=/home/latelee/bin/libuv
make
make install

在/home/latelee/bin/libuv生成include和lib目录

二、使用

包含头文件:

1
#include <uv.h>

Makefile中指定libuv.a库,该库依赖pthread线程,注意,libuv.a必须在pthread库之前,否则编译时会提示libuv.a缺少pthread库的函数。

1
../libuv/libuv.a -lpthread -lrt

服务器源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
/**
基于TCP的回显程序,即客户端传输什么内容,服务器就返回什么内容。

*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <uv.h>
#include "task.h"

#define CHECK(ret, msg) if (ret) \
{ \
printf("%s: [%s(%d): %s]\n", msg, uv_err_name((ret)), (int) ret, uv_strerror((ret))); \
exit(1); \
}

const static char* HOST = "0.0.0.0"; // 使用4个0即可,不需要写具体的IP
const static int PORT = 12345;
const static int NBUFS = 1; /* number of buffers we write at once */

// tcp连接句实例
static uv_tcp_t tcpServer;

typedef struct
{
uv_write_t req;
uv_buf_t buf;
} write_req_t;

static void OnClose(uv_handle_t* client)
{
free(client);
printf("Closed connection\n");
}

// 断开连接时会调用到此函数
// 注意,并不是退出服务端程序
static void OnShutdown(uv_shutdown_t* req, int status)
{
printf("On Shutdown...\n");
uv_close((uv_handle_t*)req->handle, OnClose);
free(req);
}

static void OnAlloc(uv_handle_t *handle, size_t size, uv_buf_t *buf)
{
/* libuv suggests a buffer size but leaves it up to us to create one of any size we see fit */
buf->base = malloc(size);
buf->len = size;
if (buf->base == NULL)
{
printf("OnAlloc buffer didn't properly initialize\n");
}
}

// 注:OnAfterWrite仅是做一些释放工作,真正发送数据是在uv_write完成的
static void OnAfterWrite(uv_write_t *req, int status)
{
CHECK(status, "OnAfterWrite");

printf("Replied to client\n");

/* Since the req is the first field inside the wrapper write_req, we can just cast to it */
/* Basically we are telling C to include a bit more data starting at the same memory location, which in this case is our buf */
write_req_t *write_req = (write_req_t*)req;
free(write_req->buf.base);
free(write_req);
}

static void OnRead(uv_stream_t* client, ssize_t nread, const uv_buf_t* buf)
{
int ret = 0;
uv_shutdown_t *shutdown_req;

printf("OnRead: nread: %ld\n", nread);
/* Errors or EOF */
if (nread < 0)
{
if (nread != UV_EOF)
CHECK(nread, "OnRead");

/* Client signaled that all data has been sent, so we can close the connection and are done */
free(buf->base);

shutdown_req = malloc(sizeof(uv_shutdown_t));
ret = uv_shutdown(shutdown_req, client, OnShutdown);
CHECK(ret, "uv_shutdown");
return;
}

if (nread == 0)
{
/* Everything OK, but nothing read and thus we don't write anything */
free(buf->base);
return;
}

/* Check if we should quit the server which the client signals by sending "QUIT" */
if (!strncmp("QUIT", buf->base, fmin(nread, 4)))
{
printf("Closing the server\n");
free(buf->base);
/* Before exiting we need to properly close the server via uv_close */
/* We can do this synchronously */
uv_close((uv_handle_t*) &tcpServer, NULL);
printf("Closed server, exiting\n");
exit(0);
}

/* 6. Write same data back to client since we are an *echo* server and thus can reuse the buffer used to read*/
/* We wrap the write req and buf here in order to be able to clean them both later */
write_req_t *write_req = malloc(sizeof(write_req_t));
write_req->buf = uv_buf_init(buf->base, nread);
ret = uv_write(&write_req->req, client, &write_req->buf, NBUFS, OnAfterWrite);

CHECK(ret, "uv_write");
}

static void ShowSockname(struct sockaddr* addr)
{
struct sockaddr_in* check_addr = (struct sockaddr_in*)addr;
char check_ip[17];
int r;

r = uv_ip4_name(check_addr, (char*) check_ip, sizeof check_ip);
ASSERT(r == 0);

printf("accepted from: %s:%d\n", check_ip, ntohs(check_addr->sin_port));
}

static void OnConnection(uv_stream_t* server, int status)
{
static int cnt = 1;
int ret = 0;
uv_shutdown_t* shutdown_req;

struct sockaddr sockname, peername;
int namelen = sizeof(struct sockaddr);

CHECK(status, "OnConnection");

/* 4. Accept client connection */
printf("Accepting Connection\n");

/* 4.1. Init client connection using `server->loop`, passing the client handle */
uv_tcp_t *client = malloc(sizeof(uv_tcp_t));
ret = uv_tcp_init(server->loop, client);

CHECK(ret, "uv_tcp_init");

/* 4.2. Accept the now initialized client connection */
ret = uv_accept(server, (uv_stream_t*)client);
if (ret)
{
printf("trying to accept connection %d\n", ret);

shutdown_req = malloc(sizeof(uv_shutdown_t));
ret = uv_shutdown(shutdown_req, (uv_stream_t*) client, OnShutdown);
CHECK(ret, "uv_shutdown");
}

// 获取对端IP地址,并打印之
ret = uv_tcp_getpeername(client, &peername, &namelen);
if (ret != 0)
{
printf("uv_tcp_getpeername failed.\n");
return;
}

ShowSockname(&peername);

printf("Connection accepted, cnt: %d\n", cnt++);

/* 5. Start reading data from client */
ret = uv_read_start((uv_stream_t *)client, OnAlloc, OnRead);
CHECK(ret, "uv_read_start");
}

int main(void)
{
int ret = 0;

// 创建loop实例
uv_loop_t *loop = uv_default_loop();

// 1、初始化TCP
ret = uv_tcp_init(loop, &tcpServer);
CHECK(ret, "uv_tcp_init");

// 2、绑定IP地址和端口
struct sockaddr_in addr;
ret = uv_ip4_addr(HOST, PORT, &addr);
CHECK(ret, "uv_ip4_addr");

ret = uv_tcp_bind(&tcpServer, (struct sockaddr*)&addr, AF_INET);
CHECK(ret, "uv_tcp_bind");

// 3、监听端口
// 注:uv_tcp_t继承于uv_stream_t(前面的结构成员完全一样),可以直接强制转换
// 内部调用uv_tcp_listen->uv__handle_start
// SOMAXCONN可设置其它值,默认128
ret = uv_listen((uv_stream_t*) &tcpServer, SOMAXCONN, OnConnection);
CHECK(ret, "uv_listen");

printf("Listening on %s:%d\n", HOST, PORT);

// 4、运行循环体,内部使用for死循环,根据epoll轮询,
// 直到在异常退出
uv_run(loop, UV_RUN_DEFAULT);

return 0;
}

李迟 2018-05-17