53. 线程池


一般在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 事件的写缓冲区中, 并通知主进程写事件准备就绪. 最终由主进程负责应答.

主函数

这里我们主函数的代码粘贴了一下, 打算用主函数的过程来分析整个简单的流程.

  1. 初始化线程池.
  2. 将套接字监听事件注册到 epoll 监听中.
  3. 有套接字监听事件就绪, 以 inithttpd 函数将其初始化.
  4. 客户端发送 http 请求后, 由 readn 函数将请求复制到 httpd事件的读缓冲区中.
  5. append 将 httpd 事件加入到就绪队列中.
    • 线程池中线程 调用processing : 解析请求, 分离请求方式(GET还是POST等), http 版本, 请求文件.
    • 并将请求的文件打开, 将数据复制到 httpd事件的写缓冲区中. 写入完成后通知主进程写事件就绪.
  6. 主进程调用 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;
}


小结

因为代码有很多, 这里也不好一一的讲解, 所以还是希望有兴趣的可以看一下源码, 也可以添加其他的功能(如 : 内存池等)实现成自己的小项目, 如果有问题也欢迎指出.


TinyHTTPd 源码分析

你可能感兴趣的:(TCP,线程池,网络编程,网络编程学习)