基本概念
browser与server通信,在浏览器输入域名或IP地址:port号,浏览器进行域名解析为相应的ip地址或根据ip地址向对应server发送http请求。 其中首先要TCP协议进行三次握手,建立连接,然后http协议生成对应的http请求报文,通过TCP,IP协议发送到目标server。
HTTP: 超文本传输协议,请求和响应消息头以ASCII形式发出,消息内容类似MIME;默认端口80,本地发出请求的客户端叫做用户代理程序,和服务器之间会存在多个“中间层”,比如代理服务器、网关或者隧道。
采用请求/响应模型,客户端向server发送请求报文,包括请求方法,URL,协议版本,请求头,请求数据。
服务器响应内容包括协议版本,成功/错误代码,服务器信息,响应头部,响应数据。
请求响应步骤:
状态码及描述:服务器编程基本框架
I/O处理单元:等待并接受客户连接,接受客户数据,返回服务器响应给客户端。但是数据收发不一定在该单元执行,也可能在逻辑单元,取决于事件处理模式。
逻辑单元:一个线程或进程,分析并处理数据,传递结果给I/O或直接给客户端,取决于事件处理模式。多个客户端->多个逻辑单元,以实现并发处理。
网络存储单元:数据库、缓存、文件,但不是必须的。
请求队列:各单元之间通信方式的抽象。I/O通知逻辑单元,逻辑单元访问存储单元等。通常被实现为池的一部分。
事件处理模式:
服务器需要处理三类事件:I/O事件,信号、定时事件。
两种处理模式:
Reactor: 常用同步IO模型实现
主线程(IO处理单元)只监听fd是否有事发生,有就通知工作线程(逻辑单元),将socket可读可写事件放入请求队列,交给工作线程。 在工作线程中读写数据,接受新连接,处理客户请求。主线程不处理其他实质性工作。
1. 主线程向epoll内核时间表注册socket上的读就绪事件。
2. 主线程调用epoll_wait等待有数据可读。
3. 当socket有数据可读,epoll_wait通知主线程,主线程将socket可读事件放入请求队列。
4. 睡眠在请求队列的某个工作线程被唤醒,从socket上读数据,处理客户请求,往epoll内核表注册socket写就绪事件。
5. 主线程调用epoll_wait等待socket可写。
6. 当socket可写,epoll_wait通知主线程,主线程将socket可写事件放入请求队列。
7. 睡眠在请求队列的某个工作线程被唤醒,往socket上写入服务器处理客户请求的结果。
Proactor: 常用异步IO实现
将所有IO操作都交给主线程和内核,工作线程仅负责业务逻辑。
1 主线程调用aio_read向内核注册socket上读完成事件,并告诉内核用户读缓冲区的位置,以及完成时如何通知应用。
2 主线程继续处理其他逻辑。
3 当socket上数据被读入用户缓冲区,内核向应用发送信号,通知数据可用。
4 应用预先定义好的信号处理函数选择一个工作线程处理客户请求,处理完后,调用aio_write向内核注册socket上写完成事件,并告诉内核用户写缓冲区位置,以及完成时如何通知应用。
5 主线程继续处理其他逻辑
6 当用户缓冲区内容写入socket,内核向应用发送信号,通知应用数据发完。
7 应用预先定义的信号处理函数选择一个工作线程善后,如决定是否关闭socket.
但在 Linux 下的异步 I/O 是不完善的,aio 系列函数是由 POSIX 定义的异步操作接口,不是真正的操作系统级别支持的,而是在用户空间模拟出来的异步,并且仅仅支持基于本地文件的 aio 异步操作,网络编程中的 socket 是不支持的,这使得基于 Linux 的高性能网络程序都是使用 Reactor 方案。
而 Windows 里实现了一套完整的支持 socket 的异步编程接口,这套接口就是 IOCP,是由操作系统级别实现的异步 I/O,真正意义上异步 I/O,因此在 Windows 里实现高性能网络程序可以使用效率更高的 Proactor 方案。
项目实现:模拟Proactor模式
使用同步IO模拟proactor
1. 主线程向epoll内核时间表注册socket上的读就绪事件。
2. 主线程调用epoll_wait等待有数据可读。
3. 当socket有数据可读,epoll_wait通知主线程,主线程从socket循环读取数据,直到没有数据可读,然后将读取到的数据封装为一个请求对象插入请求队列。
4. 睡眠在请求队列的某个工作线程被唤醒,获得请求并处理,然后往epoll内核表注册socket写就绪事件。
5. 主线程调用epoll_wait等待socket可写。
6. 当socket可写,epoll_wait通知主线程,主线程往socket上写入server处理客户请求的结果。
线程池
使主线程和子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新任务来时,主线程将任务添加到工作队列中,唤醒一个子线程,他将从工作队列中取出任务并执行,其他子线程将继续在工作队列上睡眠。
线程数量受限与CPU数量N:
CPU密集型任务:N 或N+1; (视频剪辑等消耗CPU资源)
IO密集型:多于N。 因为IO处理较慢,多于N则为CPU处理更多的任务。
1.以空间换时间,以服务器的硬件资源换取运行效率。
2.池是一组资源的集合,在服务器启动之初就被完全创建好并初始化,称为静态资源。
3.当server进入运行阶段,开始处理客户请求,需要资源时直接从池中获取,无需动态分配。
4.当server处理完一个客户连接,可以把相关资源放回池中,无需释放资源。