以tcp_echo_server.c为例,说明创建tcp服务器的程序流程。main函数非常简单:
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: tcp port\n");
return -10;
}
int port = atoi(argv[1]);
hloop_t* loop = hloop_new(0); //创建一个loop
//创建一个用来监听的io,用来处理客户端连接事件
hio_t* listenio = hloop_create_tcp_server(loop, "0.0.0.0", port, on_accept);
if (listenio == NULL) {
return -20;
}
printf("listenfd=%d\n", hio_fd(listenio));
hloop_run(loop); //正常情况下,会一直阻塞在该接口
hloop_free(&loop);
return 0;
}
第一步就是先使用hloop_new创建一个loop,因为libhv是one loop per thread模型,所以该结构体也是整个网络库的核心。大体看下创建流程。
hloop_t* hloop_new(int flags) {
hloop_t* loop;
HV_ALLOC_SIZEOF(loop); //申请内存
hloop_init(loop); //初始化
loop->flags |= flags; //设置flag
return loop;
}
上面的flag因为在本程序中没有用,所以先不说了。核心就是hloop_init:
static void hloop_init(hloop_t* loop) {
loop->status = HLOOP_STATUS_STOP;
loop->pid = hv_getpid(); //获取进程ID
loop->tid = hv_gettid(); //获取线程ID,注意,此线程ID非pthread_self的那个pthread_t类型的线程id
// idles
list_init(&loop->idles); //初始化空闲事件列表
// timers
heap_init(&loop->timers, timers_compare); //初始化定时器堆
// ios
io_array_init(&loop->ios, IO_ARRAY_INIT_SIZE); //初始化io事件集合
// readbuf
loop->readbuf.len = HLOOP_READ_BUFSIZE;
HV_ALLOC(loop->readbuf.base, loop->readbuf.len); //初始化读缓冲区
// iowatcher
iowatcher_init(loop); //初始化io事件监视器
// custom_events
//下面是与custom事件相关的初始化
hmutex_init(&loop->custom_events_mutex);
event_queue_init(&loop->custom_events, CUSTOM_EVENT_QUEUE_INIT_SIZE);
loop->sockpair[0] = loop->sockpair[1] = -1;
if (Socketpair(AF_INET, SOCK_STREAM, 0, loop->sockpair) != 0) {
hloge("socketpair create failed!");
}
// NOTE: init start_time here, because htimer_add use it.
//设置开始时间,这个时间是从Epoch开始的时间,但实际上该时间是可以通过修改系统时间修改的,这就是为什么需要下面的时间
loop->start_ms = gettimeofday_ms();
//这个时间是无法被修改的相对时间,通过这个无法被修改的相对时间,就可以调整上面的开始时间
loop->start_hrtime = loop->cur_hrtime = gethrtime_us();
}
关于线程ID的说明,可以参考陈硕大神的《Linux多线程服务端编程》,或者我的muduo系列博客https://blog.youkuaiyun.com/qu1993/article/details/109403603,这里就不再特殊说明了。
空闲事件、定时器事件、custom事件前面都分析过了,实际上这一篇博客主要就是分析io事件的。最后的关于时间的说明比较简单,因为时间相关的内容本身就比较复杂,这篇博客也不多分析了。
所以上面的初始化大多数都已经分析过了,这也是为什么我在写了前面那些篇博客之后才准备分析最重要的服务器创建流程,应该说前面的博客都是为这一篇做铺垫的,这一篇也是本系列博客中最重要的一篇。
初始化之后,libhv提供了一个非常简单的创建服务端的接口,hloop_create_tcp_server
// @tcp_server: socket -> bind -> listen -> haccept
// @see examples/tcp.c
HV_EXPORT hio_t* hloop_create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb);
这个接口封装了创建socket,绑定bind、监听listen、接受客户端accept回调等整个服务端创建流程。下面分析这个创建服务端最重要的接口。
hio_t* hloop_create_tcp_server (hloop_t* loop, const char* host, int port, haccept_cb accept_cb) {
int listenfd = Listen(port, host);
if (listenfd < 0) {
return NULL;
}
hio_t* io = haccept(loop, listenfd, accept_cb);
if (io == NULL) {
closesocket(listenfd);
}
return io;
}
Listen接口获取监听套接字描述符,基本就是那套固定流程 socket -> bind -> listen,不再多说。比较重要的是下面的haccept接口:
hio_t* haccept(hloop_t* loop, int listenfd, haccept_cb accept_cb) {
hio_t* io = hio_get(loop, listenfd);
assert(io != NULL);
if (accept_cb) {
io->accept_cb = accept_cb;
}
hio_accept(io);
return io;
}
hio_get之前就遇到过,但是没有分析,因为本来就打算在这里分析的。
hio_t* hio_get(hloop_t* loop, int fd) {
//因为fd是io array的下标,所以要确保下标是有效的
if (fd >= loop->ios.maxsize) {
int newsize = ceil2e(fd);
io_array_resize(&loop->ios, newsize > fd ? newsize : 2*fd);
}
hio_t* io = loop->ios.ptr[fd