参考资料:https://deepwiki.com/qinguoyi/TinyWebServer
功能梳理
select
- 使用 线性表 来表示文件描述符集合
- 文件描述符 在 用户态 被加入文件描述符集合中
- 用户态到内核态的拷贝
- 遍历
- 只支持 LT 模式
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
| #include <stdio.h> #include <stdlib.h> #include <sys/select.h>
int some_task() { int sockfd = g_sockfd;
fd_set rfds; struct timeval tv; int error = 0;
while(1) { tv.tv_sec = 5; tv.tv_usec = 0;
FD_ZERO(&rfds); FD_SET(sockfd, &rfds);
error = select(sockfd + 1, &rfds, NULL, NULL, &tv); if (error == 0) { continue; } else if (error < 0) { if(error == EINTR) { continue; } else { break; } } if(FD_ISSET(sockfd, &rfds)) { some_recv(sockfd); } }
return error; }
|
poll
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
| void time_poll(void) { struct pollfd *pfds; nfds_t nfds; int ready; TIMER *timer;
FOREACH(timer, &g_timer_head, list) { nfds++; }
pfds = calloc(nfds, sizeof(struct pollfd)); if (pfds == NULL) err(EXIT_FAILURE, "malloc"); for (nfds_t j = 0; j < nfds; j++) { pfds[j].fd = timer->fd; pfds[j].events = POLLIN; }
while(1) { ready = poll(pfds, nfds, -1); if (ready > 0) { time_poll_handle(pfds, nfds) } else if (ready < 0) { if(errno == EINTR) { continue; } else { return; } } else { continue; } } }
|
epoll
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
| #define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd;
epollfd = epoll_create1(0); if (epollfd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); }
ev.events = EPOLLIN; ev.data.fd = listen_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); }
for (;;) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); }
for (n = 0; n < nfds; ++n) { if (events[n].data.fd == listen_sock) { conn_sock = accept(listen_sock, (struct sockaddr *) &addr, &addrlen); if (conn_sock == -1) { perror("accept"); exit(EXIT_FAILURE); } setnonblocking(conn_sock); ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) { perror("epoll_ctl: conn_sock"); exit(EXIT_FAILURE); } } else { do_use_fd(events[n].data.fd); } } }
|
总结
LT 和 ET
LT (水平触发)
ET (边缘触发)
在使用 ET 模式时,必须要保证该文件描述符是非阻塞的(确保在没有数据可读时,该文件描述符不会一直阻塞);并且每次调用 read 和 write 的时候都必须等到它们返回 EWOULDBLOCK(确保所有数据都已读完或写完)。
Reactor 和 Proactor
服务器程序通常需要处理三类事件:I/O事件,信号及定时事件。
有两种事件处理模式:
Reactor 模式:要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生(可读、可写),若有,则立即通知工作线程(逻辑单元),将 socket 可读可写事件放入请求队列,交给工作线程处理。
Proactor 模式:将所有的I/O操作都交给主线程和内核来处理(进行读、写),工作线程仅负责处理逻辑,如主线程读完成后 users[sockfd].read(),选择一个工作线程来处理客户请求 pool->append(users + sockfd)。
通常使用同步 I/O 模型(如 epoll_wait)实现 Reactor,使用异步 I/O(如 aio_read 和 aio_write )实现 Proactor。
本项目使用同步 I/O 模拟的 Proactor 事件处理模式。
同步 I/O 和 异步 I/O
同步(阻塞) I/O: 在一个线程中,CPU执行代码的速度极快,然而,一旦遇到IO操作,如读写文件、发送网络数据时,就需要等待 IO 操作完成,才能继续进行下一步操作。
异步(非阻塞) I/O:当代码需要执行一个耗时的 IO 操作时,它只发出 IO 指令,并不等待 IO 结果,然后就去执行其他代码了。一段时间后,当 IO 返回结果时,再通知 CPU 进行处理。
Asan