前提知识
Socket是可以实现主机与主机之间通过网络进行通信的一种通信方式
Socket就是相当于一个邮箱的存在, 服务器端或客户端可以往这个邮箱中填充数据, 然后发送给对方, 当对方发现邮箱中有数据就可以读取, 邮箱为空就可以发送数据给对方
服务器端需要为每个通信的客户端都维护一个Socket, 来维持和客户端之间的通信
服务器客户端模型
服务器端要给客户端提供服务就需要和客户端建立连接然后接受客户端的请求
分析下服务器端的工作
监听服务器端用于与客户端建立连接的Socket, 当有客户端的连接请求的时候分配用于和客户端进行通信的Socket- 监听已经和客户端建立连接的Socket,
读取客户端发送过来的请求, 按照客户端的请求写入对应的响应发送给客户端
可以发现服务器端和客户端的关系就是一问一答的关系, 客户端发问题请求过来, 服务器端回答问题响应
所以服务器的程序只要用一个while(true) 一直去调用监听, 读取, 写入这几个函数就可以实现和客户端的交流通信了
但是, 设计服务器主要的问题就是, 监听, 写入, 读取这几个过程都是阻塞过程
也就是说如果我们调用了对应的监听函数, 那么我们的程序就会一直阻塞在这里直到监听函数返回告诉我们有连接到达, 所以我们的程序无法单靠一个循环来完成监听连接, 读取客户端请求, 发送数据给客户端这几个操作
在面对多客户端的情况下, 服务器端无法同时满足所有客户端的请求并且同时监听是否客户端连接过来
为了解决这个问题, 一开始的做法就是使用多线程多进程
我们的main函数其实就是一个进程, 单靠一个进程无法实现上面的工作的话就可以使用多线程 / 多进程了
多进程模型
主进程负责监听是否有连接到达, 一旦有连接到达就创建进程来处理这个连接的剩余请求
好处 : 成功解决上面程序无法同时监听连接和客户端请求的问题
缺点 : 进程创建消耗资源大, 并且进程之间的资源同步问题解决困难, 在进程与进程之间切换的时候性能消耗很大
多线程模型
既然进程创建和切换消耗的资源大的话就使用线程这种轻量级的模型来应多客户端的请求, 当客户端连接到达的时候就为这个客户端分配一个线程来专门监听客户端的请求
好处 : 线程可以共享进程的部分资源, 并且没有进程那么大的切换开销, 线程的创建和销毁可以通过线程池操作来减少资源消耗
缺点 : 多线程还是有上限的, 对于同时很多个客户端的请求系统还是会忙不过来, 并且多线程和多进程为了避免共享的资源竞争问题是需要加锁的, 这样更加增加了服务器处理客户端请求的时间
IO多路复用
多线程多进程服务器模型出现的主要原因还是因为我们不知道什么时候socket能够进行读写操作, 所以我们需要让程序一直阻塞在读写操作的函数调用上, 一个进程只能一次监听一个Socket, 这个是函数的锅, 没办法只能让程序进程在那死等, 所以才使用多线程多进程
所以后面为了解决多线程多进程的缺点, 出现了IO多路复用技术, 可以实现一个进程同时监听多个Socket
只要进程处理请求的时间控制在很小的间隔内, 进程就可以短时间内处理多个Socket了
select / poll / epoll 就是操作系统内核提供给程序员的多路复用系统调用, 将监听操作交给操作系统内核去实现, 程序只要负责处理请求即可
select / poll / epoll几个函数作用都一样, 我们可以传递我们要监听的所有Socket给这几个函数, 调用的时候我们的进程就阻塞在这个这个系统调用等待返回
一旦Socket上有请求发送过来, select / poll / epoll这几个调用就会返回有请求的socket, 这样我们的程序就能就能直接处理请求, 而不必一直在哪傻等着
好处 : 解决了要使用多线程 / 多进程模型去监听多个socket的根本原因
- 一个进程 / 线程无法同时监听多个Socket
避免了多线程 / 多进程的资源竞争问题
- 因为对客户端请求的处理都在同一个进程内, 单进程肯定是没有竞争问题的
高性能网络模型Reactor
因为IO多路复用的高性能, 所以现在的服务器基本都是使用IO多路复用来实现服务器的请求处理的
IO多路复用可以让一个进程去同时监听多个Socket并处理对应的请求, 但是这样无法充分发挥现代多核CPU的优势, 所以我们还是可以使用多线程 / 多进程模型来处理请求的
对于客户端的连接请求我们可以看作是 连接事件, 对于客户端的请求我们可以看作是可读事件, 响应客户端的请求就是可写事件了
Reactor模式就是将这些事件放到对应的线程 / 进程中去专职处理, 然后主进程main就负责分发这些事件到对应的线程 / 进程中
单Reactor单进程 / 线程
使用Reactor监听分发事件, 然后直接处理对应的事件
优点 : 实现简单, 不用考虑进程线程之间的通信和竞争问题
缺点 : 多核CPU利用率低
单Reactor多线程 / 多进程方案
将对应事件的处理分发到对应的线程中, 这样能充分利用多核CPU的性能
缺点就是引入了多线程竞争的问题了, 一般不会选择多进程, 因为进程之间的通信方式更加复杂
单Reactor存在问题
一个Reactor对象负责监听和响应在主线程中运行, 在高并发的情况下容易成为性能瓶颈
多Reactor多进程 / 线程
原本是主线程监听所有的事件, 然后将事件分发到对应的线程去执行
现在是将监听分开来, 一个Reactor只监听一种事件并负责对应的处理, 这种实现其实比单Reactor多线程的方案实现要简单, 因为各自线程分工明确, 不用再传递对应的处理给其他线程