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 源码分析

根据Java,JavaScript,mysql,c语言,vue3,回答下列问题 1.什么是面向对象编程? 2.Java中的集合包括什么? 3.spring boot三个架构? mapper的作用 4.Mybatis mapper.xml的配置映射 5.Radis 数据结构 6.Redis数据一致性问题 7.Mysql 数据结构 char和varchar的区别 8.Vue生命周期,钩子函数,响应式变量,npm,常用的element -ui 9.springboot常用注解有哪些 10.Nnginx 11.Rocketmq消息延迟 12.Mybatis用法 13.Arralist用过哪些api 14.遍历集合有哪几种 15.MySQL数据类型用过哪些 16.Date和datetime的区别 17.Git常见命令 18.Html标签通常有哪些 css 19.数据采集与分析用的哪个库 20.可视化用的什么技术 21.拦截器 22.常见异常 23.事务 24.组件通信,父组件如何给子组件传递信息 25.路由组件 26.反射 27.线程池,多线程 28.什么是注解(标记) 29.Aop 30.Git 31.idea常用插件 32.什么是反向代理 33.第三方接口 34.Lombok用法 35.Vue3常用的api 36.列举vue3的常用生命周期 37.Vue router 38.遍历数组的方法 39.Foreach和map的区别(有返回值) 40.Radis数据类型? 41.多态 42.Hashmap遍历方式 43.Springboot配置文件的相关知识 44.前端三件套 45.数据库与其外键的主要内容 46.常见表的关系 47.若依框架 48.Nginx相关内容与配置文件 49.Linux命令 50.后端接口怎么定义的 51.前端如何搭建 52.数据库常见的相关命令语句 53.数据库表设计 54.V-if 55.V-show
04-28
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值