这是学习网络编程后写的一个练手的小程序,可以帮助复习I/O模型,epoll使用,线程池,HTTP协议等内容。

程序代码是基于《Linux高性能服务器编程》一书编写的。

首先回顾程序中的核心内容和主要问题,最后给出相关代码。

0. 功能和I/O模型

实现简易的HTTP服务端,现仅支持GET方法,通过浏览器访问可以返回相应内容。

I/O模型采用Reactor(I/O复用 + 非阻塞I/O) + 线程池。 使用epoll事件循环用作事件通知,如果listenfd上可读,则调用accept,把新建的fd加入epoll中;

是已连接sockfd,将其加入到线程池中由工作线程竞争执行任务。

 

1. 线程池怎么实现?

程序采用c++编写,要自己封装一个简易的线程池类。大致思路是创建固定数目的线程(如跟核数相同),然后类内部维护一个生产者—消费者队列。

提供相应的添加任务(生产者)和执行任务接口(消费者)。按照操作系统书中典型的生产者—消费者模型维护增减队列任务(使用mutex和semaphore)。

mutex用于互斥,保证任意时刻只有一个线程读写队列,semaphore用于同步,保证执行顺序(队列为空时不要读,队列满了不要写)。

 

2. epoll用条件触发(LT)还是边缘触发(ET)?

考虑这样的情况,一个工作线程在读一个fd,但没有读完。如果采用LT,则下一次事件循环到来的时候,又会触发该fd可读,此时线程池很有可能将该fd分配给其他的线程处理数据。

这显然不是我们想要看到的,而ET则不会在下一次epoll_wait的时候返回,除非读完以后又有新数据才返回。所以这里应该使用ET。

当然ET用法在《Tinychatserver: 一个简易的命令行群聊程序》也有总结过。用法的模式是固定的,把fd设为nonblocking,如果返回某fd可读,循环read直到EAGAIN。

 

3. 继续上面的问题,如果某个线程在处理fd的同时,又有新的一批数据发来(不是老数据没读完,是来新数据了),即使使用了ET模式,因为新数据的到来,仍然会触发该fd可读,所以仍然存在将该fd分给其他线程处理的情况。

这里就用到了EPOLLONESHOT事件。对于注册了EPOLLONESHOT事件的文件描述符,操作系统最大触发其上注册的一个可读、可写或者异常事件,且只触发一次。

网友评论