文章目录
0 - 前言
本文是针对《Linux高性能服务器编程》的笔记
1 - 服务器模型
1-1 C/S模型
![Alt](https://img-blog.csdnimg.cn/20210514194236123.png# =300)
C/S(客户端/服务器)模型:所有客户端都通过服务器来获取所需的资源
C/S的工作流程如 图8-2 所示:
server:启动后,创建一个或多个socket,并调用bind函数将socket绑定到server端口上,之后调用listen函数监听有无客户端连接。由于多个client的请求是异步(相互之前没有关联)的,因此server必须使用某种I/O模型(Linux下有select、poll、epoll)来监听多个client。当client有连接请求(client调用connect函数)到来时,server调用accept函数接受连接,并分配一个逻辑单元(如子线程)处理新的连接。逻辑单元读取客户请求,server处理该请求,并将处理结果发送给client。
client:当服务器稳定(一般指server调用listen函数)后,client调用connect函数请求与server连接。
1-2 P2P模型
P2P即点对点模型,该模型使得每台机器消耗服务的同时也在提供服务。P2P的缺点在于:当用户间的传输请求变多时,网络的负载加重。
2 - 服务器编程框架
对于单个服务器,以上四种部件分别对应如下功能或角色:
-
I/O处理单元:①等待并接受新的client连接;②接收client数据;③将server响应返回给client
-
逻辑单元:一般是进程/线程,①处理client数据;②将处理结果发送给I/O单元或直接发给client
- 一台服务器通常具有多个逻辑单元,以实现对多个client的任务处理
-
网络存储单元:可以是本地数据库、文件或缓存,但不是必须的
-
请求队列:各单元之间的通信方式的抽象。比如,I/O单元收到client请求数据时,以请求队列的方式通知一个逻辑单元前来处理。同样,多个逻辑单元同时访问一个存储单元,也需要请求队列来协调竞态条件(当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件)。请求队列通常被实现为池的一部分。
3 - I/O模型
socket API中,能够被阻塞的系统调用有accept、send、recv、connect
对于阻塞I/O(阻塞的文件描述符)执行的系统调用若没有事件发生会在原地一直等待,直到事件发生才会执行下去
对于非阻塞I/O(非阻塞的文件描述符)执行的系统调用总是立即返回,若事件没有发生,返回-1。
阻塞I/O、I/O复用、SIGIO信号都属于同步I/O,因为这三种模型中,I/O读写都发生在I/O事件发生之后
异步I/O是用户直接对I/O进行读写,并且不论I/O是否阻塞,读写操作立即返回结果
4 - 事件处理模式
同步I/O模型通常用于实现Reactor模式,异步I/O用于实现Proactor模式
4-1 Reactor
主线程负责监听文件描述集合中是否有事件发生,有的话就通知工作线程去处理事件。即,工作线程完成读写数据、接受新连接、处理client请求
Reactor工作流程如下:
5 - 池
池(poll)是在一组资源的集合,在server启动之前就已经创建好并初始化,这就是静态资源分配。server正式运行后,即开始处理client请求了,如果server需要相关的资源,就直接在池中获取,避免了动态分配。当server处理完一个client请求后,将相关资源放回池中,避免系统调用来释放资源,从而减少server对内核的频繁访问。
常见的池有:内存池、进程池、线程池、连接池。
- 内存池:用于socket的接收缓存和发送缓存。
- 进程池/线程池:用于并发编程。当server需要一个工作进程或工作线程处理新的client请求,直接从池中获取,避免了动态调用fork或pthread_create函数来创建进程或线程
- 连接池:用于server的内部永久连接。比如,逻辑单元需要频繁访问本地数据库时,频繁发起连接与释放连接效率太低。使用连接池后,当逻辑单元访问数据库时,直接从连接池中获取一个连接的实体使用,完成数据库访问后将该实体返还给连接池。
6 - 上下文切换和锁
上下文切换:进程切换或线程切换导致的系统开销。
锁:对共享资源的访问权限。