计算机网络知识总结

TCP/IP

1. 说一下OSI七层模型 TCP/IP四层模型 五层协议

七层模型:应表会传网数物

五层模型:应传网数物

1)五层协议

  • 应用层 :提供用户接口,特指能够发起网络流量的程序
  • 传输层:提供的是进程间的通用数据传输服务。由于应用层协议很多,定义通用的传输层协议就可以支持不断增多的应用层协议。运输层包括两种协议:
    • 传输控制协议 TCP,提供面向连接、可靠的数据传输服务,数据单位为报文段;
    • 用户数据报协议 UDP,提供无连接、尽最大努力的数据传输服务,数据单位为用户数据报。
    • TCP 主要提供完整性服务,UDP 主要提供及时性服务。
  • 网络层:为主机间提供数据传输服务,而运输层协议是为主机中的进程提供服务。网络层把传输层传递下来的报文段或者用户数据报封装成分组。(负责选择最佳路径 规划IP地址)
    • 路由器查看数据包目标IP地址,根据路由表为数据包选择路径。路由表中的类目可以人工添加(静态路由)也可以动态生成(动态路由)。
  • 数据链路层:不同的网络类型,发送数据的机制不同,数据链路层就是将数据包封装成能够在不同的网络传输的帧。能够进行差错检验,但不纠错,监测处错误丢掉该帧。
    • 帧的开始和结束,透明传输,差错校验
  • 物理层:物理层解决如何在连接各种计算机的传输媒体上传输数据比特流,而不是指具体的传输媒体。

2)ISO七层模型中表示层和会话层功能是什么?

  • 表示层 :数据压缩、加密以及数据描述。这使得应用程序不必担心在各台主机中表示/存储的内部格式(二进制、ASCII,比如乱码)不同的问题。
  • 会话层 :建立会话,如session认证、断点续传。通信的应用程序之间建立、维护和释放面向用户的连接。通信的应用程序之间建立会话,需要传输层建立1个或多个连接。(…后台运行的木马,netstat -n)

3)数据在各层之间的传递过程

  在向下的过程中,需要添加下层协议所需要的首部或者尾部,而在向上的过程中不断拆开首部和尾部。

  1. 路由器只有下面三层协议,因为路由器位于网络核心中,不需要为进程或者应用程序提供服务,因此也就不需要运输层和应用层。
  2. 交换机只有下面两层协议

4)TCP/IP四层模型

它只有四层,相当于五层协议中数据链路层和物理层合并为网络接口层

现在的 TCP/IP 体系结构不严格遵循 OSI 分层概念,应用层可能会直接使用 IP 层或者网络接口层。

2. TCP报头格式和UDP报头格式

网络层只把分组发送到目的主机,但是真正通信的并不是主机而是主机中的进程。运输层提供了进程间的逻辑通信,运输层向高层用户屏蔽了下面网络层的核心细节,使应用程序看起来像是在两个运输层实体之间有一条端到端的逻辑通信信道。

(1)UDP 和 TCP 的特点

  • 用户数据报协议 UDP(User Datagram Protocol)是无连接的,尽最大可能交付,没有拥塞控制,面向报文(对于应用程序传下来的报文不合并也不拆分,只是添加 UDP 首部),支持一对一、一对多、多对一和多对多的交互通信。例如:视频传输、实时通信
  • 传输控制协议 TCP(Transmission Control Protocol)是面向连接的,提供可靠交付,有流量控制,拥塞控制,提供全双工通信,面向字节流(把应用层传下来的报文看成字节流,把字节流组织成大小不等的数据块),每一条 TCP 连接只能是点对点的(一对一)。

(2)UDP 首部格式

​ 首部字段只有 8 个字节,包括源端口、目的端口、长度、检验和。

(3)TCP 首部格式

  • 序号 seq :用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。[301,400]为序号301的数据长度,下一个则为401
  • 确认号 ack :期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
  • 数据偏移 :指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。
  • 确认 ACK :当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
  • 同步 SYN :在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
  • 终止 FIN :用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
  • 窗口 :窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。

3. TCP三次握手?那四次挥手呢?如何保障可靠传输

(1)三次握手

假设 A 为客户端,B 为服务器端。

  • 首先 B 处于 LISTEN(监听)状态,等待客户的连接请求。
  • A 向 B 发送连接请求报文段,SYN=1,ACK=0,选择一个初始的序号 seq = x。
  • B 收到连接请求报文段,如果同意建立连接,则向 A 发送连接确认报文段,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号 seq = y。
  • A 收到 B 的连接确认报文段后,还要向 B 发出确认,确认号为 ack = y+1,序号为 seq = x+1。
  • A 的 TCP 通知上层应用进程,连接已经建立。
  • B 收到 A 的确认后,连接建立。
  • B 的 TCP 收到主机 A 的确认后,也通知其上层应用进程:TCP 连接已经建立。

三次握手对应的socket编程函数

1.客户端调用connect()函数,此时客户端会向服务端发送SYN

2.服务端收到SYN后,会从listen()函数返回SYN+ACK

3.客户端收到connect()函数的返回,之后向服务端发送最后一个ACK

4.服务端收到最后一个ACK以后,将该连接请求从未完成连接队列放入已完成连接队列中,等待accept()从该队列中取出

(2)为什么TCP连接需要三次握手,两次不可以吗,为什么

为了防止已失效的连接请求报文段突然又传送到了服务端,占用服务器资源。 (假设主机A为客户端,主机B为服务器端)

现假定出现一种异常情况,即A发出的第一个连接请求报文段并没有丢失,而是在某些网络节点长时间滞留了,以致延误到连接释放以后的某个时间才到B。本来这是一个已失效的报文段。但是B收到此失效的连接请求报文段后,就误认为是A有发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。

由于现在A并没有发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据。B的许多资源就这样白白浪费了。

采用三次握手的办法可以防止上述现象的发生。例如在刚才的情况下,A不会向B的确认发出确认。B由于收不到确认,就知道A并没有要求建立连接。

(3)四次挥手

总结:

  1. 客户端向服务端发起FIN=1,序号为u
  2. 服务端回复确认号ack=u+1,报文自己的序列号为v,客户端到服务端的连接就关闭了
  3. 服务端向客户端发送FIN信号
  4. 客户端接收到之后,等待两个Maximum Segment Lifetime(一般是2个30s,1min或2min),释放链接,回复确认信号
  5. 服务端收到确认信号后关闭连接

数据传输结束后,通信的双方都可释放连接。现在 A 的应用进程先向其 TCP 发出连接释放报文段,并停止再发送数据,主动关闭 TCP连接。

  • A 把连接释放报文段首部的 FIN = 1,其序号 seq = u,等待 B 的确认。
  • B 发出确认,确认号 ack = u+1,而这个报文段自己的序号 seq = v。(TCP 服务器进程通知高层应用进程)
  • 从 A 到 B 这个方向的连接就释放了,TCP 连接处于半关闭状态。A 不能向 B 发送数据;B 若发送数据,A 仍要接收。
  • 当 B 不再需要连接时,发送连接释放请求报文段,FIN=1。
  • A 收到后发出确认,进入 TIME-WAIT 状态,等待 2 MSL(2*2 = 4 mins)时间后释放连接。
  • B 收到 A 的确认后释放连接。

(4)四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

(5)TIME_WAIT

MSL是Maximum Segment Lifetime英文的缩写,中文可以译为 “报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。2MSL = 2*2mins = 4mins

客户端接收到服务器端的 FIN 报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:

  • 确保最后一个确认报文段能够到达。如果 B 没收到 A 发送来的确认报文段,那么就会重新发送连接释放请求报文段,A 等待一段时间就是为了处理这种情况的发生。
  • 等待一段时间是为了让本连接持续时间内所产生的所有报文段都从网络中消失,使得下一个新的连接不会出现旧的连接请求报文段。

避免time_wait:SO_REUSEADDR设置为1,强制进程立即使用处于TIME_WAIT状态连接占用的端口

(6)如何保证可靠传输

  • 应用数据被分割成TCP认为最适合发送的数据块。
  • 超时重传:当TCP发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。如果不能及时收到一个确认,将重发这个报文段。
  • TCP给发送的每一个包进行编号,接收方对数据包进行排序,把有序数据传送给应用层。
  • 校验和:TCP将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP将丢弃这个报文段和不确认收到此报文段。
  • TCP的接收端会丢弃重复的数据。
  • 流量控制:TCP连接的每一方都有固定大小的缓冲空间,TCP的接收端只允许发送端发送接收端缓冲区能接纳的数据。当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。TCP使用的流量控制协议是可变大小的滑动窗口协议。
  • 拥塞控制:当网络拥塞时,减少数据的发送。

(7)TCP和HTTP

5. TCP和UDP区别?如何改进TCP

  • TCP和UDP区别

    • UDP 是无连接的,即发送数据之前不需要建立连接。

    • UDP 使用尽最大努力交付,即不保证可靠交付,同时也不使用拥塞控制。

    • UDP 是面向用户数据报的。UDP 没有拥塞控制,很适合多媒体通信的要求。

    • UDP 支持一对一、一对多、多对一和多对多的交互通信。

    • UDP 的首部开销小,只有 8 个字节。

    • TCP 是面向连接的运输层协议。

    • 每一条 TCP 连接只能有两个端点(endpoint),每一条 TCP 连接只能是点对点的(一对一)。

    • TCP 提供可靠交付的服务。

    • TCP 提供全双工通信。

    • TCP是面向字节流。  

    • 首部最低20个字节。

  • TCP加快传输效率的方法

    • 采取一块确认的机制

6. TCP滑动窗口

窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小

发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。

在 TCP 中,滑动窗口是为了实现流量控制。如果对方发送数据过快,接收方就来不及接收,接收方就需要通告对方,减慢数据的发送。

7. TCP拥塞控制

流量控制是为了控制发送方发送速率,保证接收方来得及接收。

接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

(1)慢开始与拥塞避免

  发送的最初执行慢开始,令 cwnd=1,发送方只能发送 1 个报文段;当收到确认后,将 cwnd 加倍,因此之后发送方能够发送的报文段数量为:2、4、8 …

  注意到慢开始每个轮次都将 cwnd 加倍,这样会让 cwnd 增长速度非常快,从而使得发送方发送的速度增长速度过快,网络拥塞的可能也就更高。设置一个慢启动阈值 ssthresh,当 cwnd >= ssthresh 时,进入拥塞避免,每个轮次只将 cwnd 加 1。

  如果出现了超时,则令 ssthresh = cwnd/2,然后重新执行慢开始。

(2)快重传与快恢复

  在接收方,要求每次接收到报文段都应该对最后一个已收到的有序报文段进行确认。例如已经接收到 M1 和 M2,此时收到 M4,应当发送对 M2 的确认。

  在发送方,如果收到三个重复确认,那么可以知道下一个报文段丢失,此时执行快重传,立即重传下一个报文段。例如收到三个 M2,则 M3 丢失,立即重传 M3。

  在这种情况下,只是丢失个别报文段,而不是网络拥塞。因此执行快恢复,令 ssthresh = cwnd/2 ,cwnd = ssthresh,注意到此时直接进入拥塞避免。慢开始和快恢复的快慢指的是 cwnd 的设定值,而不是 cwnd 的增长速率。慢开始 cwnd 设定为 1,而快恢复 cwnd 设定为 ssthresh。

(3)发送窗口的上限值

  发送方的发送窗口的上限值应当取为接收方窗口 rwnd 和拥塞窗口 cwnd 这两个变量中较小的一个,即应按以下公式确定:

  • 发送窗口的上限值 = Min {rwnd, cwnd}

9. 如何区分流量控制和拥塞控制

  • 拥塞控制所要做的都有一个前提,就是网络能够承受现有的网络负荷。
  • 拥塞控制是一个全局性的过程,涉及到所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。
  • 流量控制往往指在给定的发送端和接收端之间的点对点通信量的控制。
  • 流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
  • 流量控制属于通信双方协商;拥塞控制涉及通信链路全局。
  • 流量控制需要通信双方各维护一个发送窗、一个接收窗,对任意一方,接收窗大小由自身决定,发送窗大小由接收方响应的TCP报文段中窗口值确定;拥塞控制的拥塞窗口大小变化由试探性发送一定数据量数据探查网络状况后而自适应调整。
  • 实际最终发送窗口 = min{流控发送窗口,拥塞窗口}。

HTTP

1. HTTP状态

服务器返回的 响应报文 中第一行为状态行,包含了状态码以及原因短语,用来告知客户端请求的结果。

状态码 类别 原因短语
1XX Informational(信息性状态码) 接收的请求正在处理
2XX Success(成功状态码) 请求正常处理完毕
3XX Redirection(重定向状态码) 需要进行附加操作以完成请求
4XX Client Error(客户端错误状态码) 服务器无法处理请求
5XX Server Error(服务器错误状态码) 服务器处理请求出错

(1)1XX 信息

  • 100 Continue :表明到目前为止都很正常,客户端可以继续发送请求或者忽略这个响应。

(2)2XX 成功

  • 200 OK
  • 204 No Content :请求已经成功处理,但是返回的响应报文不包含实体的主体部分。一般在只需要从客户端往服务器发送信息,而不需要返回数据时使用。
  • 206 Partial Content :表示客户端进行了范围请求。响应报文包含由 Content-Range 指定范围的实体内容。

(3)3XX 重定向

  • 301 Moved Permanently :永久性重定向
  • 302 Found :临时性重定向
  • 303 See Other :和 302 有着相同的功能,但是 303 明确要求客户端应该采用 GET 方法获取资源。
  • 注:虽然 HTTP 协议规定 301、302 状态下重定向时不允许把 POST 方法改成 GET 方法,但是大多数浏览器都会在 301、302 和 303 状态下的重定向把 POST 方法改成 GET 方法。
  • 304 Not Modified :如果请求报文首部包含一些条件,例如:If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since,如果不满足条件,则服务器会返回 304 状态码。
  • 307 Temporary Redirect :临时重定向,与 302 的含义类似,尽管302标准禁止post变化get,但实际使用时大家不遵守,但是 307 要求浏览器强制不会把重定向请求的 POST 方法改成 GET 方法。

(4)4XX 客户端错误

  • 400 Bad Request :请求报文中存在语法错误。
  • 401 Unauthorized :该状态码表示发送的请求需要有认证信息(BASIC 认证、DIGEST 认证)。如果之前已进行过一次请求,则表示用户认证失败。
  • 403 Forbidden :请求被拒绝,服务器端没有必要给出拒绝的详细理由。
  • 404 Not Found

(5)5XX 服务器错误

  • 500 Internal Server Error :服务器正在执行请求时发生错误。
  • 503 Service Unavailable :服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

5. GET和POST的区别?【阿里面经OneNote】

就下面的找几个点和面试官侃侃而谈即可,不可能全部都记得,想到什么讲什么吧

  • GET 被强制服务器支持
  • 浏览器对URL的长度有限制,所以GET请求不能代替POST请求发送大量数据
  • GET请求发送数据更小
  • GET请求是不安全的
  • GET请求是幂等的
    • 幂等的意味着对同一URL的多个请求应该返回同样的结果
  • POST请求不能被缓存
  • POST请求相对GET请求是「安全」的
    • 这里安全的含义仅仅是指是非修改信息
  • GET用于信息获取,而且是安全的和幂等的
    • 所谓安全的意味着该操作用于获取信息而非修改信息。换句话说,GET 请求一般不应产生副作用。就是说,它仅仅是获取资源信息,就像数据库查询一样,不会修改,增加数据,不会影响资源的状态。
  • POST是用于修改服务器上的资源的请求
  • 发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

6. HTTP和HTTPS的区别【阿里面经OneNote】

  • http是HTTP协议运行在TCP之上。所有传输的内容都是明文,客户端和服务器端都无法验证对方的身份。
  • https是HTTP运行在SSL/TLS之上,SSL/TLS运行在TCP之上。所有传输的内容都经过加密,加密采用对称加密,但对称加密的密钥用服务器方的证书进行了非对称加密。此外客户端可以验证服务器端的身份,如果配置了客户端验证,服务器方也可以验证客户端的身份。
  • https协议需要到ca申请证书,一般免费证书很少,需要交费。
  • http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议
  • http和https使用的是完全不同的连接方式用的端口也不一样,前者是80,后者是443。
  • http的连接很简单,是无状态的
  • HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 要比http协议安全

7.对称加密与非对称加密

对称加密:指的就是加、解密使用的同是一串密钥,所以被称做对称加密。对称加密只有一个密钥作为私钥。
常见的对称加密算法:DES,AES等。

非对称加密:指的是加、解密使用不同的密钥,一把作为公开的公钥,另一把作为私钥。公钥加密的信息,只有私钥才能解密。反之,私钥加密的信息,只有公钥才能解密。

举个例子,你向某公司服务器请求公钥,服务器将公钥发给你,你使用公钥对消息加密,那么只有私钥的持有人才能对你的消息解密。与对称加密不同的是,公司服务器不需要将私钥通过网络发送出去,因此安全性大大提高。
最常用的非对称加密算法:RSA

8.ABC类地址和私有地址

A类:最前面是0,1.0.0.1-126.255.255.254

B类:最前面是10,128.1.0.1-191.255.255.254

C类:最前面是110,192.0.1.1-223.255.255.254

D类:最前面是1110224.0.0.1-239.255.255.254

E类:是保留地址。该类IP地址的最前面为“1111”

私有地址(内部局域网可以使用的)

A级:10.0.0.0 - 10.255.255.255

B级:172.16.0.0 - 172.31.255.255

C级:192.168.0.0 - 192.168.255.255

保留地址(特殊用途的)

A类:127.X.X.X:本地循环测试

B类:169.254.X.X:当自动获取ip没有找到dhcp服务器的时候,分配一个169.254开头的地址

Socket

###一、I/O模型

阻塞式I/O

应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。

应该注意到,在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率效率会比较高。

下图中,recvfrom() 用于接收 Socket 传来的数据,并复制到应用进程的缓冲区 buf 中。这里把 recvfrom() 当成系统调用。

1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

非阻塞式 I/O

应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成,这种方式称为轮询(polling)。

由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。

![image-20190324160743975](/Users/apple/Library/Application Support/typora-user-images/image-20190324160743975.png)

I/O 复用

使用 select 或者 poll 等待数据,并且可以等待多个socket任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。

所以,I/O 多路复用的特点是通过一种机制一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,select()函数就可以返回。

它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O,即事件驱动 I/O。

如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接,那么就需要创建相同数量的线程。相比于多进程和多线程技术,I/O 复用不需要进程线程创建和切换的开销,系统开销更小。

信号驱动 I/O

应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。

相比于非阻塞式 I/O 的轮询方式,信号驱动 I/O 的 CPU 利用率更高。

异步 I/O

应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。

异步 I/O 与信号驱动 I/O 的区别在于,异步 I/O 的信号是通知应用进程 I/O 完成,而信号驱动 I/O 的信号是通知应用进程可以开始 I/O。

五大 I/O 模型比较

  • 同步 I/O:将数据从内核缓冲区复制到应用进程缓冲区的阶段,应用进程会阻塞。
  • 异步 I/O:不会阻塞。

阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O 都是同步 I/O,它们的主要区别在第一个阶段。

非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。

二、I/O 复用

select/poll/epoll 都是 I/O 多路复用的具体实现,select 出现的最早,之后是 poll,再是 epoll。

select

1
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

有三种类型的描述符类型:readset、writeset、exceptset,分别对应读、写、异常条件的描述符集合。fd_set 使用数组实现,数组大小使用 FD_SETSIZE 定义。

timeout 为超时参数,调用 select 会一直阻塞直到有描述符的事件到达或者等待的时间超过 timeout。

成功调用返回结果大于 0,出错返回结果为 -1,超时返回结果为 0。

poll

1
int poll(struct pollfd *fds, unsigned int nfds, int timeout);

pollfd 使用链表实现。

select和poll的比较

1. 功能

select 和 poll 的功能基本相同,不过在一些实现细节上有所不同。

  • select 会修改描述符,而 poll 不会;
  • select 的描述符类型使用数组实现,FD_SETSIZE 大小默认为 1024,因此默认只能监听 1024 个描述符。如果要监听更多描述符的话,需要修改 FD_SETSIZE 之后重新编译;而 poll 的描述符类型使用链表实现,没有描述符数量的限制;
  • poll 提供了更多的事件类型,并且对描述符的重复利用上比 select 高。
  • 如果一个线程对某个描述符调用了 select 或者 poll,另一个线程关闭了该描述符,会导致调用结果不确定。
2. 速度

select 和 poll 速度都比较慢。

  • select 和 poll 每次调用都需要将全部描述符从应用进程缓冲区复制到内核缓冲区。
  • select 和 poll 的返回结果中没有声明哪些描述符已经准备好,所以如果返回值大于 0 时,应用进程都需要使用轮询的方式来找到 I/O 完成的描述符。
3. 可移植性

几乎所有的系统都支持 select,但是只有比较新的系统支持 poll。

epoll

1
2
3
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll_ctl() 用于向内核注册新的描述符或者是改变某个文件描述符的状态。已注册的描述符在内核中会被维护在一棵红黑树上,通过回调函数内核会将 I/O 准备好的描述符加入到一个链表中管理,进程调用 epoll_wait() 便可以得到事件完成的描述符。

从上面的描述可以看出,epoll 只需要将描述符从进程缓冲区向内核缓冲区拷贝一次,并且进程不需要通过轮询来获得事件完成的描述符。

epoll 仅适用于 Linux OS,epoll 比 select 和 poll 更加灵活而且没有描述符数量限制。

epoll 对多线程编程更有友好,一个线程调用了 epoll_wait() 另一个线程关闭了同一个描述符也不会产生像 select 和 poll 的不确定情况。

工作模式

epoll 的描述符事件有两种触发模式:LT(level trigger,可以不立即处理事件)和 ET(edge trigger,需要立即处理时间)。

#####1.LT模式

当 epoll_wait() 检测到描述符事件到达时,将此事件通知进程,进程可以不立即处理该事件,下次调用 epoll_wait() 会再次通知进程。是默认的一种模式,并且同时支持 Blocking 和 No-Blocking。

2. ET模式

和 LT 模式不同的是,通知之后进程必须立即处理事件,下次再调用 epoll_wait() 时不会再得到事件到达的通知。

很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。只支持 No-Blocking,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

应用场景

很容易产生一种错觉认为只要用 epoll 就可以了,select 和 poll 都已经过时了,其实它们都有各自的使用场景。

1. select 应用场景

select 的 timeout 参数精度为 1ns,而 poll 和 epoll 为 1ms,因此 select 更加适用于实时性要求比较高的场景,比如核反应堆的控制。

select 可移植性更好,几乎被所有主流平台所支持。

2. poll 应用场景

poll 没有最大描述符数量的限制,如果平台支持并且对实时性要求不高,应该使用 poll 而不是 select。

3. epoll 应用场景

只需要运行在 Linux 平台上,有大量的描述符需要同时轮询,并且这些连接最好是长连接。

需要同时监控小于 1000 个描述符,就没有必要使用 epoll,因为这个应用场景下并不能体现 epoll 的优势。

需要监控的描述符状态变化多,而且都是非常短暂的,也没有必要使用 epoll。因为 epoll 中的所有描述符都存储在内核中,造成每次需要对描述符的状态改变都需要通过 epoll_ctl() 进行系统调用,频繁系统调用降低效率。并且 epoll 的描述符存储在内核,不容易调试。