socket & I/O复用学习整理笔记
socket
下面来自《网络是怎样连接的》
操作系统中的网络控制软件(协议栈)和网络硬件(网卡)
2.png应用程序下方是socket库,包括,解析器,解析器用来向DNS服务器发出查询。
再下面就是操作系统内部了,其中包括协议栈。协议栈的上半部分有两块,分别是负责用 TCP 协议收发数据的部分和负责用 UDP 协议收发数据的部分,它们会接受应用程序的委托执行收发数据的操作。
下面一半是用IP 协议控制网络包收发操作的部分。在互联网上传送数据时,数据会被切分成一个一个的网络包。而将网络包发送给通讯对象的操作是由IP负责的。IP还包括ICMP协议和ARP协议。
ICMP 用于告知网络包传送过程中产生的错误以及各种控制消息,ARP 用于根据 IP 地址查询相应的以太网 MAC 地址.
IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收的操作。
套接字——通信控制信息
在协议栈内部有一块用于存放控制信息的内存空间,这里记录了用于控制通信操作的控制信息,例如通信对象的 IP 地址、端口号、通信操作的进行状态等。本来套接字就只是一个概念而已,并不存在实体,如果一定要赋予它一个实体,我们可以说这些控制信息就是套接字的实体,或者说存放控制信息的内存空间就是套接字的实体。
协议栈在执行操作时需要参阅这些信息。例如:发送数据需要查看套接字中的通信对象IP地址和端口号。且需要在数据发送一段时间后未收到回应重新发送数据,需要协议栈知道发送数据操作经过多少时间。套接字中必须要记录是否已经收到响应,以及发送数据后经过了多长时间。
协议栈是根据套接字中记录的控制信息来工作的
Windows中的套接字举例:
3.png首先是创建套接字的阶段。应用程序调用 socket 申请创建套接字,协议栈根据应用程序的申请执行创建套接字的操作。
在这个过程中,协议栈首先会分配用于存放一个套接字所需的内存空间。用于记录套接字控制信息的内存空间并不是一开始就存在的,因此我们先要开辟出这样一块空间来。套接字刚刚创建时,数据收发操作还没有开始,因此需要在套接字的内存空间中写入表示这一初始状态的控制信息。
接下来,需要将表示这个套接字的描述符告知应用程序。描述符相当于用来区分协议栈中的多个套接字的号码牌。
应用程序提供描述符,协议栈找到对应的套接字。知道连接的状态。
(我的理解是套接字,类似于进程控制块PCB,存放一些信息。)
服务器客户端连接的过程
1、服务器先创建套接字,等待客户端向该套接字连接管道。(服务器程序一般会在启动后就创建好套接字并等待客户端连接管道)
2、服务器进入等待状态,客户端就可以连接管道。客户端也先创建一个套接字。然后从该套接字延伸出管道。连接到服务器的套接字上。
3、将数据送入套接字收发数据。
四个过程:
(1)创建套接字(创建套接字阶段)
(2)将管道连接到服务器端的套接字上(连接阶段)
(3)收发数据(通信阶段)
(4)断开管道并删除套接字(断开阶段)
套接字创建完成后,协议栈会返回一个描述符,应用程序会将收到的描述符存放在内存中。描述符是用来识别不同的套接字的。
我们需要委托协议栈将客户端创建的套接字与服务器那边的套接字连接起来。应用程序通过调用 Socket 库中的名为 connect 的程序组件来完成这一操作。这里的要点是当调用 connect 时,需要指定描述符、服务器 IP 地址和端口号这 3 个参数
描述符:本机套接字
IP:找到服务器
端口:找到服务器套接字。
描述符:应用程序用来识别套接字的机制
IP 地址和端口号:客户端和服务器之间用来识别对方套接字的机制
收发消息
当执行数据收发操作时,我们还需要一块用来临时存放要收发的数据的内存空间,这块内存空间称为缓冲区,它也是在连接操作(socket connect)的过程中分配的。
发送:
应用程序需要在内存中准备好要发送的数据。根据用户输入的网址生成的 HTTP 请求消息就是我们要发送的数据。接下来,当调用 write时,需要指定描述符和发送数据(图 1.18 ③),然后协议栈就会将数据发送到服务器。由于套接字中已经保存了已连接的通信对象的相关信息,所以只要通过描述符指定套接字,就可以识别出通信对象,并向其发送数据。
接着,发送数据会通过网络到达我们要访问的服务器。
接收:
4.png当消息返回后,需要执行的是接收消息的操作。接收消息的操作是通过 Socket 库中的 read 程序组件委托协议栈来完成的(图 1.18 ③’)。调用read 时需要指定用于存放接收到的响应消息的内存地址,这一内存地址称为接收缓冲区。于是,当服务器返回响应消息时,read 就会负责将接收到的响应消息存放到接收缓冲区中。由于接收缓冲区是一块位于应用程序内
部的内存空间,因此当消息被存放到接收缓冲区中时,就相当于已经转交给了应用程序。
当浏览器收到数据之后,收发数据的过程就结束了。接下来,我们需要调用 Socket 库的 close 程序组件进入断开阶段(图 1.18 ④)。最终,连接在套接字之间的管道会被断开,套接字本身也会被删除
I/O
一个输入操作通常包括两个阶段:
- 等待数据准备好
- 从内核向进程复制数据
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
recvfrom() 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
Unix 有五种 I/O 模型:
- 阻塞式 I/O : 应用进程被阻塞,数据从内核缓冲区复制到应用进程缓冲区才返回。
- 非阻塞式 I/O:应用进程继续执行,需要不断的执行系统调用,获知I/O是否完成,这种方式成为轮询(polling)
- I/O 复用(select 和 poll)
- 信号驱动式 I/O(SIGIO)
- 异步 I/O(AIO)
I/O复用:使用select 或者poll等待数据,并把等待多个套接字中任何
使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。
如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
I/O多路复用实际上就是用select, poll, epoll监听多个io对象,当io对象有变化(有数据)的时候就通知用户进程。好处就是单个进程可以处理多个socket。
【看上去是用一个进程去监视多个I/O事件,进而有情况通知应用进程】
select的调用会阻塞到有文件描述符可以进行IO操作或被信号打断或者超时才会返回。
select 数组 1024最大连接数 轮询检测
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。
poll 链表 没有最大连接数限制 轮询检测
它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
epoll
epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd描述符)的
优势:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口); 2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数; 即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
select、poll、epoll之间的区别 - liangzen的文章 - 知乎 https://zhuanlan.zhihu.com/p/130211695
下图来源知乎
5.png更多知识:
如果这篇文章说不清epoll的本质,那就过来掐死我吧! (2) - 罗培羽的文章 - 知乎 https://zhuanlan.zhihu.com/p/64138532