该webServer使用epoll+threadpool实现,支持GET、POST方法,并添加CGI进行数据计算并返回网页信息,可以解析返回html、picture、mp3、js、css等文件,可以实现稳定的运行。 使用c++编写。
源码请看我的Github。
流程简述
启动服务器,在浏览器输入服务器地址,将向服务器发送HTTP请求
服务器接收数据,新建任务,将任务添加到任务队列
从线程池中唤醒某线程,执行任务。若没有任务线程会处于wait状态;若任务过多,会存储在任务队列中,等待空闲线程来执行
某线程获得任务后,读取浏览器发送的请求信息,进行解析HTTP首部,根据对应的结果来进行相应的处理,返回信息,若文件不存在则返回
404.html
,若请求方法不存在则返回501
错误信息。若是POST,则需要调用CGI进行处理,并返回相应的信息。
任务结束后,需要进行delete,因为在主进程中,为避免任务未运行完便被析构,需要使用new来新建对象,为避免内存泄露,需要在任务结束后使用delete释放资源。虽然这样使得
new
与delete
分离,但是可以保证程序的正常运行。线程池结束后,需要唤醒阻塞中的所有线程,以使其正常退出。
具体实现细节
由于在实现线程池时,需要用到任务队列,为保证任务队列在添加、删除元素时不会出错,需要对其加锁;同样由于在线程池中线程的运行与否需要受到控制,因此需要使用条件变量来使线程保持同步。所以在
locker.h
中定义了两个类:互斥锁、条件变量。互斥锁比较简单,所以说一下条件变量需要注意的细节:- 条件变量需要与互斥锁配合使用。由于条件变量的操作并非原子操作,因此在进行相关变量状态转变的时候,多线程若操作统一条件变量会造成错误。因此在条件变量之前需要加互斥锁进行保护,结束后及时解锁。
由于每次接收到数据时,需要新建一个任务,因此在
task.h
中定义一个封装任务信息的类。该类负责接受来自浏览器具体的请求信息,再进行解析,根据对应的结果进行处理与返回网页信息等。- 由于在任务结束后会调用delete,从而会进行析构,因此可以在Task的析构函数中关闭连接。即每个任务(每个HTTP请求)对应一个线程,每次任务结束后都会关闭与浏览器连接。
- 在每次任务执行时,为避免读取数据出错,需要循环读取,直到对方关闭连接(recv返回值=0)或请求处理完成后退出循环,即一次任务完成后也要退出循环。PS:一定要注意关闭连接的时间,否则浏览器会一直处于pending状态
threadPool.h
存放线程池的定义与实现。线程池提供线程的调度,及时处理任务队列中的任务;若任务队列为空,则所有线程处于阻塞状态。由于使用模板类,因此需要将类定义与方法实现放在一个文件夹下,因为模板类的成员函数不能单独编译。详见我的另一篇博客:线程池的分析与实现。webServer.h
与webServer.cpp
存放WebServer
类的定义与实现。该类主要进行socket的创建,绑定,监听,与accept,使用epoll实现。在接收到新连接时,将fd注册到内核事件表;有数据写入时,便使用new新建任务,并添加到线程池。由于任务使用new创建,因此任务结束后需要及时delete task。
使用介绍
由于在上传时,将mp3文件删除(上传缓慢),因此需要在与
index.html
同级的目录下放一个mp3文件,并命名为1.mp3
。打开终端,在
makefile
目录下,输入./server port
以运行服务器,其中port
为端口号,以下以8080端口为例:
终端输入 :
./server 8080
打开浏览器,在网址输入:
localhost:8080
或输入网址:
127.0.0.1:8080
即可连接服务器。
- 如果要使用自己的网页,只要将网页放到与
webServer.cpp
统一目录下,把index.html
用新的主页覆盖即可。
参考:《Linux高性能服务器编程》