一般在web服务端使用线程池是比较常见的, 本节呢, 就已以实现一个简单的web服务端为目标, 仅仅实现GET请求, 最多在加上POST请求.
如果对web服务端并不熟悉的, 可以回过头去看一下http请求与应答以及一个500行左右的web服务端源码tinyhttpd(整理了源码以及修改和加注释后的linux版本).
线程池主要函数
线程池完整代码位置 : threadpool
因为线程池的代码大概有1000行吧, 没有办法全部进行分析, 所以我就将部分重要的函数罗列出来以便大家看到时候能够清楚.
initpool
: 初始化线程池. 默认是创建的线程的个数是10个.append
: 将 http 事件加入到就绪队列中(就绪队列是由线程池中的所有进程共享的).run
: 当有事件就绪, 则线程池中的所有线程都被唤醒争夺资源, ,每个事件都只能有一个线程处理.
readn
: 负责将内核缓冲区的数据赋值到 httdp 事件中的读缓冲区中.writen
: 负责将 httdp 事件中的写缓冲区数据赋值到内核缓冲区中并发送.process_read
: 分析 http 消息. 即分析 http 请求消息的行, 头部和消息体部分.process_write
: 负责将事件请求的文件内容放入 httdp 事件的写缓冲区中, 并通知主进程写事件准备就绪. 最终由主进程负责应答.
主函数
这里我们主函数的代码粘贴了一下, 打算用主函数的过程来分析整个简单的流程.
- 初始化线程池.
- 将套接字监听事件注册到 epoll 监听中.
- 有套接字监听事件就绪, 以
inithttpd
函数将其初始化. - 客户端发送 http 请求后, 由
readn
函数将请求复制到 httpd事件的读缓冲区中. append
将 httpd 事件加入到就绪队列中.- 线程池中线程 调用
processing
: 解析请求, 分离请求方式(GET还是POST等), http 版本, 请求文件. - 并将请求的文件打开, 将数据复制到 httpd事件的写缓冲区中. 写入完成后通知主进程写事件就绪.
- 线程池中线程 调用
- 主进程调用
writen
函数将 httpd事件的写缓冲区数据发送给客服端, 完成一次应答.
int main(int argc, char *argv[]){
int listenfd, clientfd;
struct sockaddr_in addr;
struct threadpool pool;
....
initpool(&pool);
// 将 listen监听描述符加入 epoll 的监听队列中并设置为非阻塞
add_event(epollfd, listenfd, 0);
int n;
while(1){
n = epoll_wait(epollfd, evs, sizeof(evs), -1);
for(int i = 0; i < n; ++i){
int fd = evs[i].data.fd;
if(fd == listenfd){
socklen_t len;
struct sockaddr_in addr;
len = sizeof(addr);
clientfd = accept(listenfd, (struct sockaddr *)&addr, &len);
if(clientfd < 0){
fprintf(stderr, "accept error\n");
continue;
}
inithttpd(&users[clientfd], clientfd, (struct sockaddr *)&addr);
}
else if(evs[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR))
close_conn(&users[fd]);
// 读事件就绪 :
// readn 将从缓冲区中将 http 请求读入到 httdp 读缓冲区中
// append 将事件加入到就绪队列中, 由线程池中的线程处理
else if(evs[i].events & EPOLLIN){
if(readn(&users[fd]))
append(&pool, &users[fd]);
else
close_conn(&users[fd]);
}
// 写事件就绪 :
// writen 将 http 状态码以及请求的静态文件发送给客户端
// 如果发送过程中出现问题则断开连接
else if(evs[i].events & EPOLLOUT){
if(writen(&users[fd]) == -1)
close_conn(&users[fd]);
}
}
}
return 0;
}
小结
因为代码有很多, 这里也不好一一的讲解, 所以还是希望有兴趣的可以看一下源码, 也可以添加其他的功能(如 : 内存池等)实现成自己的小项目, 如果有问题也欢迎指出.