前言
IO多路复用目前在大厂的面试中,一般在两个地方可能会被问到,一个是在问到网络这一块的时候,另一个是在问到 Redis 这一块的时候,因为 Redis 底层也是使用了IO多路复用,所以整体来说 IO多路复用,也算是一道比较高频的一个面试题,所以今天跟大家来分享一下。
本文内容有视频版本,喜欢看视频的同学可以直接通过下面的链接观看。如果你对文章的内容有疑惑,可以先看视频的对应内容,视频可能讲的会更细一点。
基础概念
首先我们了解下2个基础概念,这2个概念在后续的文章中会反复用到。
Socket
套接字。百科:对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。
例子1:客户端将数据通过网线发送到服务端,客户端发送数据需要一个出口,服务端接收数据需要一个入口,这两个“口子”就是 Socket。
例子2:两个人通过电话进行通信,两个人都需要持有1个电话,socket 就类似于这个电话。
FD:file descriptor
文件描述符,非负整数。“一切皆文件”,linux 中的一切资源都可以通过文件的方式访问和管理。而 FD 就类似文件的索引(符号、指针),指向某个资源,内核(kernel)利用 FD 来访问和管理资源。
之前在视频中有同学问既然有 socket,为什么文章内容全是用的 FD 来举例,这是因为当我们调用内核函数创建 socket 后,内核返回给我们的是 socket 对应的文件描述符(fd),所以我们对 socket 的操作基本都是通过 fd 来进行。
Socket 通信
接着我们通过一张图来看下客户端和服务器使用 socket 进行通信的核心流程。
图中函数的含义如下:
socket:创建一个套接字
bind:将 socket 绑定到指定地址
listen:使套接字处于监听状态,等待客户端连接到来
accept:接受客户端连接
connect:客户端发起连接
read:从 fd 对应的 socket 中读取数据
write:将数据写入 fd 对应的 socket 中
close:关闭 socket 文件描述符
核心交互流程如下:
1)服务器端通过 socket、bind、listen 对 socket 进行初始化,最后阻塞在 accept 等待客户端请求到来。
2)客户端通过 socket 进行初始化,然后使用 connect 向服务端发起连接请求。此时客户端会和服务端进行 TCP 三次握手,三次握手完成后,客户端和服务端建立连接完毕,开始进入数据传输过程。
3)客户端发起 write 系统调用写入数据,数据从用户空间拷贝到内核空间 socket 缓冲区,最后内核将数据通过网络发送到服务器。
4)数据经过网络传输到达服务器网卡,接着内核将数据拷贝到对应的 socket 接收队列,最后将数据从内核空间拷贝到用户空间。
5)客户端和服务器完成交互后,调用 close 函数来断开连接。
IO模型小例子
接着我们通过一个例子来了解下各种IO模型。
例子:你是一个老师,让学生做作业,学生做完作业后收作业。
同步阻塞:逐个收作业,先收A,再收B,接着是C、D,如果有一个学生还未做完,则你会等到他写完,然后才继续收下一个。
解析:这就是同步阻塞的特点,只要中间有一个未就绪,则你会被阻塞住,从而影响到后面的其他学生。
同步非阻塞:逐个收作业,先收A,再收B,接着是C、D,如果有一个学生还未做完,则你会跳过该学生,继续去收下一个。
解析:可以看到同步非阻塞相较于同步阻塞已经是更好的方案了,你不会因为某个学生未就绪而阻塞住,这样就可以减少对后续学生的影响。但是这个方案也可能会出现其他问题,如果你下去收作业的时候,全部学生都还没做完,则你可能会白走一圈,然后一个作业也没收到。
select/poll:学生写完了作