TCP/IP协议传输层详解

目录

前言

一、端口号

1、概念

2、相关命令

二、UDP协议 

1、UDP数据报格式

2、UDP的特点

3、UDP的缓冲区 

三、TCP协议

1、TCP数据报格式

2、确认应答(ACK)机制

3、缓冲区

4、TCP报文的6位标志位

5、连接管理机制

5.1 状态变化

5.1 三次握手

5.2 四次挥手

6、超时重传机制

7、滑动窗口

8、流量控制

9、拥塞控制

10、延迟应答

11、粘包问题

12、TCP异常情况

四、总结

1、TCP/UDP对比

2、用UDP实现可靠传输(经典面试题)

3、理解 listen 的第二个参数

总结


前言

传输层协议主要有两个,分别是UDP协议和TCP协议。


一、端口号

1、概念

端口号(Port)标识了一个主机上进行通信的不同的进程。在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号" 这样一个五元组来标识一个通信。

TCP/IP协议传输层详解_第1张图片

2、相关命令

netstat:

netstat是一个用来查看网络状态的重要工具。

语法:netstat   [选项]
功能:查看网络状态
常用选项:

  • n 拒绝显示别名,能显示数字的全部转化成数字
  • l 仅列出有在 Listen (监听) 的服務状态
  • p 显示建立相关链接的程序名
  • t (tcp)仅显示tcp相关选项
  • u (udp)仅显示udp相关选项
  • a (all)显示所有选项,默认不显示LISTEN相关

pidof: 

在查看服务器的进程id时非常方便。

语法:pidof [进程名]
功能:通过进程名, 查看进程id

二、UDP协议 

1、UDP数据报格式

TCP/IP协议传输层详解_第2张图片

  • 16位UDP长度, 表示整个数据报(UDP报头+UDP数据)的最大长度。UDP报头的大小是固定的,所以通过UDP长度就可以得知报头和数据的大小,从而实现解包。
  • 如果校验和出错, 就会直接丢弃。
  • 传输层协议可以根据报头中的16位端口号找到相应进程,把数据交给上层应用,完成与应用层的交互。

我们注意到, UDP协议首部中有一个16位的最大长度。也就是说一个UDP能传输的数据最大是64K(包含UDP首部)。然而64K在当今的互联网环境下, 是一个非常小的数字。如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。

2、UDP的特点

  • 无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接。
  • 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息。
  • 面向数据报,不能够灵活的控制读写数据的次数和数量。面向数据报的意思是UDP会把应用层给的数据原封不动的传输过去,既不会拆分也不会合并。如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节。就好比写信一样,寄一封对方就得收一封,不能收半封。

3、UDP的缓冲区 

  • UDP没有真正意义上的 发送缓冲区.。调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作。
  • UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃。

UDP的socket既能读, 也能写, 这个概念叫做全双工。全双工的意思是recvfrom和sendto可以同时被调用。

三、TCP协议

1、TCP数据报格式

TCP/IP协议传输层详解_第3张图片

各部分用途: 

  • 源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去。
  • 4位TCP报头长度: 表示该TCP头部有多少个4字节,例如首部长度为0101,那么报头长度就是4*5=20。指导报头大小后就能进行解包。
  • 16位窗口大小:在应答时表明自己的接收缓冲区的剩余空间。
  • 16位校验和:其目的是为了发现TCP首部和数据在发送端到接收端之间是否发生改动。如果接收方检测到校验和有差错,则TCP段会被直接丢弃。

其余部分后面详细介绍。

2、确认应答(ACK)机制

TCP是可靠的,所以发送下一个数据前必须确认对方已经收到之前的数据。所以每次再发送数据后,都需要对方主机进行应答,来表明已经收到数据。

TCP/IP协议传输层详解_第4张图片

主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B。如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发。当然传回来的应答也可能丢掉,这个情况下即便B已经收到数据但A不知道,依旧会重发数据,要百分之百确保B收到了上一条数据。重复的数据OS会根据32位序号进行去重操作。

32位序号和32位确认序号:

  • 因为传输路径等问题,有时候先发送的数据反而会后到达,导致应答顺序错乱。为了对方主机能够准确无误的应答对应的信息,每条信息都会带一个序号,也就是数据报中的32位序列,对方主机会根据序列号对数据进行排序。
  • 对方主机在应答的时候也会发送一个序号,存在32位确认序号中。同一条数据的应答序号要比发送序号大1。也就是A给B发送的报文序号为3,那B给A发送的报文中应答序号就应该为4,告诉A 4之前的数据已经全部收到了,下次请从4开始发送。
  • B给A发送的报文中除了有应答序号外依然可以有数据,也可以有发送序号,也就是说确认应答的同时也可以给对方传递数据,同样对方也是如此,这种机制叫做捎带应答。这样就实现了双向通信。tcp也是一个全双工通信协议。

tcp是面向字节流的,TCP将每个字节的数据都进行了编号,即为序列号。我们可以理解成有一个数组以字节为单位线性的存储了缓冲区的数据,每个字节对应一个下标。每个报文在发送时会携带一串数据,这个报文的序号为这一串数据对应的最大下标。发送下一串数据时就能通过下标锁定起始位置。

TCP/IP协议传输层详解_第5张图片

3、缓冲区

TCP协议是自带发送和接收缓冲区的。用户层在进行write/send操作时,并不是把数据发到网络中,而是拷贝到TCP协议的缓冲区中。用户层接收数据时同理,也是直接从TCP协议的缓冲区中拿速度。

缓冲区的作用:

  • 提高效率。用户只要把数据写进TCP协议缓冲区中即可,不必关心对方主机收没收到,之后的事情全部由TCP协议处理,用户层可以继续去干自己的事情。
  • 实现应用层和TCP层的解耦。TCP是操作系统层的协议,只有OS和TCP知道对方主机的状态,所以只有TCP能决定数据如何发,发多少,什么时候发等细节问题。所以TCP也叫做传输控制协议,用户层只要放心把数据交给TCP即可,由TCP来调控,实现了用户层和TCP层的解耦。

4、TCP报文的6位标志位

在任何一个时刻,服务器都可能收到成百tcp上千个报文,每个报文的类别和要实现的功能都不相同。所以服务器要根据TCP的标志位对它们进行区分。

  • URG: 紧急指针是否有效。由于TCP报文是有序号的,所以一般是按序进行处理。如果某个报文中携带了紧急信息可以用URG标识,会被优先处理。TCP组成中的报文指针就是指向紧急数据的,紧急数据最多占一个字节,否则会破坏TCP的有序性。
  • ACK: 确认序号。
  • PSH: TCP缓冲区快满的时候,发送端会发送带有PSH的报文,提示接收端应用程序立刻从TCP缓冲区把数据读走。
  • RST: 对方要求重新建立连接。我们把携带RST标识的称为复位报文段。
  • SYN: 请求建立连接。我们把携带SYN标识的称为同步报文段。
  • FIN: 通知对方, 本端要关闭了。我们称携带FIN标识的为结束报文段。

5、连接管理机制

5.1 状态变化

TCP/IP协议传输层详解_第6张图片

服务端状态转化: 

  • [CLOSED -> LISTEN] 服务器端调用listen后进入LISTEN状态, 等待客户端连接。
  • [LISTEN -> SYN_RCVD] 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发送SYN确认报文。
  • [SYN_RCVD -> ESTABLISHED] 服务端一旦收到客户端的确认报文, 就进入ESTABLISHED状态, 可以进行读写数据了。
  • [ESTABLISHED -> CLOSE_WAIT] 当客户端主动关闭连接(调用close), 服务器会收到结束报文段, 服务器返回确认报文段并进入CLOSE_WAIT。
  • [CLOSE_WAIT -> LAST_ACK] 进入CLOSE_WAIT后说明服务器准备关闭连接(需要处理完之前的数据); 当服务器真正调用close关闭连接时, 会向客户端发送FIN, 此时服务器进入LAST_ACK状态, 等待最后一个ACK到来(这个ACK是客户端确认收到了FIN)。
  • [LAST_ACK -> CLOSED] 服务器收到了对FIN的ACK, 彻底关闭连接。

客户端状态转化: 

  • [CLOSED -> SYN_SENT] 客户端调用connect, 发送同步报文段。
  • [SYN_SENT -> ESTABLISHED] connect调用成功, 则进入ESTABLISHED状态, 开始读写数据。
  • [ESTABLISHED -> FIN_WAIT_1] 客户端主动调用close时, 向服务器发送结束报文段, 同时进入FIN_WAIT_1。
  • [FIN_WAIT_1 -> FIN_WAIT_2] 客户端收到服务器对结束报文段的确认, 则进入FIN_WAIT_2, 开始等待服务器的结束报文段。
  • [FIN_WAIT_2 -> TIME_WAIT] 客户端收到服务器发来的结束报文段, 进入TIME_WAIT, 并发出LAST_ACK。
  • [TIME_WAIT -> CLOSED] 客户端要等待一个2MSL(Max Segment Life, 报文最大生存时间)的时间, 才会进入CLOSED状态。

TIME_WAIT状态:

现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server, 结果是:

 这是因为server的应用虽然终止了,但是tcp的协议并没有完全断开,因此不能再次监听同样的server端口。

  • TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态。
  • 我们使用Ctrl-C终止了server, 所以server是主动关闭连接的一方, 在TIME_WAIT期间仍然不能再次监听同样的server端口。
  • MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在Centos7上默认配置的值是60s。

TIME_WAIT的时间为为什么是2MSL?

  • MSL是TCP报文存活的最大时间,一个报文最多存活MSL时间,发送加响应往返最多需要2MSL,2MSL的话可以保证在两个方向上正在传输的报文都消失。(否则如果关闭后立刻重启的话有可能收到上一个进程中延迟的数据)。
  • 同时也是保证了最后一个报文的可靠到达,假设最后一个ACK丢失, 那么服务器会再重发一个FIN。此刻虽然客户端已经不在了,但是TCP连接还在,仍然可以重发LAST_ACK。

解决TIME_WAIT状态引起的bind失败的方法:

在server的TCP连接没有完全断开之前不允许重新监听, 某些情况下可能是不合理的:

  • 服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求)。
  • 如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务器端主动清理掉), 就会产生大量TIME_WAIT连接。
  • 由于我们的请求量很大, 就可能导致TIME_WAIT的连接数很多, 每个连接都会占用一个通信五元组(源ip,源端口, 目的ip, 目的端口, 协议). 其中服务器的ip和端口和协议是固定的.。如果新来的客户端连接的ip和端口号和TIME_WAIT占用的链接重复了, 就会出现问题。

解决办法:使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符。

CLOSE_WAIT 状态:

如果服务器忘记调用close关闭socket,四次挥手没有正确完成,将会使服务器这一边的连接卡在CLOSE_WAIT状态,不能正确退出。

5.1 三次握手

tcp协议是面向链接的,tcp socket在通信的时候要先进行connect操作建立链接。在建立链接的过程中需要进行三次握手。

三次握手过程:

  • 客户端向服务器发送带有SYH标识的报文请求连接。
  • 服务器向客户端发送带有SYH和ACK的报文表示收到信息并同意连接。
  • 客户端向服务器发送带有ACK的报文表示收到。

在客户端看来,在第三步向服务器发送ACK后连接就已经建立完成了。但在服务器看来必须收到ACK后才算连接完成。但是第三步发送的带有ACK的报文可能在中途弄丢,这时候就会三次握手失败。但用户端确以为连接成功了,于是可能就会开始向服务器发送数据,而服务器发现在交互数据前并没有建立好连接,就会察觉双方连接异常了,从而向客户端发送带有RST标识的报文,要求对方重新建立连接。

这里说明一下什么是连接:在服务器中存在大量的连接,这些连接是要被管理的,管理的本质就是先描述再组织。所以在三次握手完成后,双方的OS会为这个连接创建相应的数据结构描述相关信息并加以维护,很显然,维护连接是有成本的。

为什么是三次呢?

握手是为了判断主机和网络是否都正常,三次是验证全双工双方都有收发的最小次数。A向B请求连接,第一次握手A向B发送SYN,第二次握手A收到了B发来的确认响应,此时可以判断A具有收发数据的能力,首先A收到了B发来的数据证明它有收数据的能力,其次B给了A响应证明了A成功向B发送了数据,具有发数据的能力。但此时只能判断B有收数据的能力,因为B无法判断是否成功发送了数据,需要A向B进行反馈,也就是第三次握手,B才能知道自己成功发送了数据。

三次握手在安全方面也有一定的优势:之前提到了连接是需要维护的,如果是一次握手就能连接成功,那么向要攻击某个服务器只需要向它发送大量的SYN请求就能消耗掉它非常大的空间,从而使其崩溃。两次握手和一次握手一样,因为进行第二次握手时无法判断对方有没有收到,所以本质上还是需要收到SYN就建立连接维护。如果是三次握手的话,攻击方想要向对某个服务器发动攻击,需要在发送SYN后把服务器的相关信息维护起来,因为要等待服务器二次握手后再对服务器发送ACK。这个过程对攻击方来说成本也是非常大的,同样需要维护很多资源,一定程度上避免了攻击方用一台电脑就能轻松的搞垮某个服务器。

5.2 四次挥手

一般而言,请求建立连接的是客户端,而终止连接是双方的事情。在终止连接时要进行四次挥手。

四次挥手过程:

  • 服务器向客户端(当然也可以反过来)发送FIN表示要终止连接。
  • 客户端向服务器发送ACK表示收到。
  • 客户端再向服务器发送FIN表示要终止连接。
  • 客户端再想服务器发送ACK表示收到。

6、超时重传机制

前面提到过,在主机A发送的数据收不到主机B的应答时就会进行重传。最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回"。但是这个时间的长短, 随着网络环境的不同, 是有差异的:

  • 如果超时时间设的太长, 会影响整体的重传效率。
  • 如果超时时间设的太短, 有可能会频繁发送重复的包。

TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间:

  • Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。
  • 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。依次类推,每次重传都以指数形式增加。
  • 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接。

7、滑动窗口

刚才我们讨论了确认应答策略, 对每一个发送的数据段, 都要给一个ACK确认应答。收到ACK后再发送下一个数据段,这样做有一个比较大的缺点, 就是性能较差。 尤其是数据往返的时间较长的时候。既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)。

TCP/IP协议传输层详解_第7张图片 滑动窗口大小:滑动窗口大小指的是无需等待确认应答就可以继续发送数据的最大值。以上图为例,滑动窗口的大小为4000,再发送前四个段的数据时无需等待。

滑动窗口是处在发送缓冲区中的,发送缓冲区中的数据可以分成三部分,分别是已经发送的数据,滑动窗口中的数据(也就是正在发送和准备发送的数据),和没有发送的数据。

TCP/IP协议传输层详解_第8张图片

 在收到确认应答后,滑动窗口会向后滑动。具体移动多少要根据具体情况分析:

情况一: 数据包已经抵达, ACK被丢了。

TCP/IP协议传输层详解_第9张图片

如上图所示,中间的ACK丢了,并不会造成任何影响。因为TCP的特性的是报文中的确认序号代表的是序号前的数据全部收到。通过确认序号为6001的应答发送方就可以判断出前面发送的数据已经被接收到。所以滑动窗口起始处直接移动到6001的位置。但是如果是末尾的ACK丢了,比如发送方最后接收到5001的应答,但6001丢了,那么发送方就要重发5000到6000的数据。

情况二:数据包就直接丢了

TCP/IP协议传输层详解_第10张图片

在某一段数据发送过程中丢失后,接收端会重复发送相同的ACK,比如如果1000到2000缺失,那么接收方之后不管收到任何数据都会发送确认序号为1001的ACK,提醒发送端1001后有数据缺失。在发送端收到三个相同序号的确认应答时则从此序号处重发部分数据,一旦接收方收到缺失数据后应答的确认序号就会马上恢复正常,发送方收到后就开始从正常的序号后继续发数据。 

从上述例子中可以看出,滑动窗口的左侧一般会随着收到的确认序号而移动,保证左侧的数据对方全部收到。而右侧的移动则是根据应答报文的窗口大小也就是接收缓冲区的大小而改变,确保不会发送超出对方缓冲区剩余空间的数据。所以说滑动窗口不但位置会改变,大小也会改变。在三次握手期间,接收方的应答中是没有数据的,但是已经包含了窗口大小,发送方就会根据对方窗口大小设置滑动窗口初始值。 

这种机制被称为 "高速重发控制"(也叫 "快重传")。快重传机制和超时重传机制在TCP中协同进行的,快重传机制保证了重传的速度,但因为必须收到三次重复应答后才会触发,某些情况下可能条件不会满足。而超时重传可以确保对方没有收到的数据一定被重传。换句话说超时重传是用来给快重传兜底的。

8、流量控制

在前面介绍窗口大小的时候提到过,接收端处理数据的速度是有限的,如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应。因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度。这个机制就叫做流量控制。

  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过ACK端通知发送端;
  • 窗口大小字段越大, 说明网络的吞吐量越高。
  • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端。发送端接受到这个窗口之后, 就会减慢自己的发送速度。
  • 如果接收端缓冲区满了, 就会将窗口置为0。这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端。

9、拥塞控制

TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据,但如果不加以控制的话数据过多很可能出现问题。流量控制主要考虑的是对方主机的接收能力。除此之外,网络的承受力也是有限的。因为网络中存在很多计算机发送数据,如果当前网络状态已经比较拥堵,导致大量数据到达缓慢或丢包,此时如果触发了重传机制重新发送大量数据很可能会使网络状态雪上加霜。因此,TCP引入慢启动机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据。

此处引入一个概念程为拥塞窗口:

  • 发送数据开始的时候,拥塞窗口为1,每当收到一个ACK应答,拥塞窗口的值都加倍。
  • 滑动窗口的大小为拥塞窗口和对方应答报文中窗口大小的最小值。

像上面这样的拥塞窗口增长速度, 是指数级别的. "慢启动" 只是指初使时慢, 但是增长速度非常快。为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.,此处引入一个叫做慢启动的阈值,当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长。当发生网络拥塞时,网络窗口的值会降为原来的一半,

TCP/IP协议传输层详解_第11张图片

 在发生网络拥塞后,ssthresh阈值变为拥塞窗口的一半,拥塞窗口重新从1开始增长。少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞。当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降。拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案。

10、延迟应答

如果接收数据的主机立刻返回ACK应答, 这时候返回的窗口可能比较小。

假设接收端缓冲区为1M. 一次收到了500K的数据,如果立刻应答, 返回的窗口就是500K。但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了。在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来。如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M。

一定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率。

11、粘包问题

首先要明确, 粘包问题中的 "包" , 是指的应用层的数据包。

  • 在TCP的协议头中, 没有如同UDP一样的 "报文长度" 这样的字段, 但是有一个序号这样的字段。
  • 站在传输层的角度, TCP是一个一个报文过来的, 按照序号排好序放在缓冲区中。站在应用层的角度, 看到的只是一串连续的字节数据。
  • 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包。

那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界。对于定长的包, 保证每次都按固定大小读取即可,对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可)。

12、TCP异常情况

进程终止:文件的生命周期是随进程的,进程终止会释放文件描述符,发送FIN,和正常关闭没有什么区别。

机器重启:和进程终止相同,因为机器在关机前会先把所有进程关掉。

机器掉电/网线断开:服务器认为连接还在,一旦服务器有写入操作就会发现连接已经不在了,就会进行reset。即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在。如果对方不在, 也会把连接释放。

13、基于TCP应用层的协议

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP

四、总结

1、TCP/UDP对比

我们说了TCP是可靠连接, 那么是不是TCP一定就优于UDP呢? TCP和UDP之间的优点和缺点, 不能简单, 绝对的进行比较。归根结底, TCP和UDP都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定。

2、用UDP实现可靠传输(经典面试题)

很多小伙伴再第一次见到这道题的时候都有点蒙,实际上最好的可靠传输协议已经摆在我们面前了,那就是TCP协议。想增加UDP的可靠性,只要参考TCP的可靠性机制, 在应用层实现类似的逻辑即可:

  • 引入序列号,保证数据顺序。
  • 引入确认应答,接收方收到数据后要给发送方应答。
  • 引入超时重传,如果发送方发送数据后一定时间内没有收到应答则重发数据。
  • ……

3、理解 listen 的第二个参数

Linux内核协议栈为一个tcp连接管理使用两个队列:

  • 半连接队列:用来保存处于SYN_SENT和SYN_RECV状态的请求。
  • 全连接队列(accept队列):用来保存处于established状态,但是应用层没有调用accept取走的请求。

在写套接字的时候,我们接触过一个listen接口,其中的第二个参数作用为设置全连接队列的长度,全连接队列的长度等于listen第二个参数加1。在调用cannect请求后,如果没有被接收方accept接收,那么这个连接会处在EATABLISHED状态存在全连接队列中等待被接收,全连接里的连接本质上已经建立完成了。如果全连接队列已满时,那么之后收到的连接请求都会在一次挥手后暂停,卡在SYN_RECV状态存在半连接队列中。

为什么全连接一定要有数量限制呢?举个例子,一般生意火爆的饭店(例如海底捞)都会在门口放几张椅子,如果在店里满的时候有客人来可以在坐在椅子上等待。如果没有这几张椅子,客人在店满员的时候会直接走掉,如果等店里有客人出来时恰好有没有新客人来,那么店铺里就会有桌子空出来,造成资源浪费。所以增加几张椅子使门外总有一些等待的客人,就能使资源利用率最大化。但椅子设的太多也没有意义,因为店内不可能同时涌出大量客人,几张椅子上的人就足够补足空缺。全等待队列也是如此,全连接队列的意义是在应用层空闲时可以立刻把建好的连接加到应用层中,把长度设的过长并没有太大的意义,而且全连接队列中的连接也需要资源去维护,如果长度过长的话,在维护过程中反而会浪费掉很多资源。


总结

本文主要对TCP/IP协议中的传输层进行了讲解,希望能给大家带来帮助。江湖路远,来日方长,我们下次见。

你可能感兴趣的:(Linux系统与网络编程,网络,网络协议,tcp/ip)