精美网站源码下载,大数据技术与应用,成都网站优化外包,怎样查看网站服务商传输层TCP协议
1. TCP协议介绍
TCP#xff08;Transmission Control Protocol#xff0c;传输控制协议#xff09;是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同#xff0c;在于TCP是有连接、可靠、面向字节流的。具体来说#xff0c;TCP设置了一大…传输层TCP协议
1. TCP协议介绍
TCPTransmission Control Protocol传输控制协议是一个要对数据的传输进行详细控制的传输层协议。 TCP 与 UDP 的不同在于TCP是有连接、可靠、面向字节流的。具体来说TCP设置了一大堆机制来保证它的连接是可靠的它的字节流传输是可靠的。
TCP协议结构 16位源端口号 表示数据从哪个进程来。
16位目的端口号 表示数据到哪个进程去。
32位序号 todo
32位确认序号 todo
4位TCP报头长度 表示该TCP报头有多少个32bit4字节所以TCP报头的最大长度是 4 ∗ 15 60 4*1560 4∗1560 字节。
6位标志位
URG 紧急指针是否有效。ACKAcknowledge character确认号是否有效。PSH 提示接收端应用程序立即从TCP缓冲区把数据读走。详细-PSH在流量控制的应用RSTReset连接重置标志位对方要求重新建立连接。收到该标志的主机要对异常连接重新释放重新建立。携带 RST标识的报文称为 复位报文段SYNSynchronize Sequence Numbers请求建立连接。携带 SYN标识的报文称为 同步报文段FIN 通知对方本端口要关闭了。携带 FIN标识的报文称为 结束报文段
16位窗口大小 标识了自己接收缓冲区的接收能力。详细- 点击跳转
16位校验和 发送端填充CRC校验接收端判断校验不通过则认为数据有问题。校验不仅包含TCP头部也包含TCP数据
16位紧急指针 标识哪部分数据是紧急数据 实际应用非常少 。详细-点击跳转
40字节头部选项 todo
1.1 面向字节流
在应用层创建一个TCP的socket在内核中会创建一个发送缓冲区和一个接收缓冲区。当调用write()时数据会先写入发送缓冲区中如果发送的数据太长数据就会被拆分成多个 TCP 数据包发出如果发送的数据太短就会先在发送缓冲区里等待等待发送缓冲区堆积得差不多了或等待其他合适的时机再发出去。
在接收数据时数据从网卡驱动程序到达内核的接收缓冲区再通过应用层调用 read() 从接收缓冲区拿走数据。
由于一个TCP连接既有发送缓冲区也有接收缓冲区。所以对于一个连接既可以读也可以写这个概念叫做全双工。
由于缓冲区的存在, TCP 程序的读和写不需要一一匹配。 写 100 100 100 个字节数据时,可以调用一次 write() 写 100 个字节也可以调用 100 次 write()每次写一个字节。 读 100 100 100 个字节数据时也完全不需要考虑写的时候是怎么写的既可以一次 read() 100 100 100 个字节, 也可以一次 read() 一个字节重复 100 100 100 次。 2. 确认应答ACK机制
我们在进行网络传输时数据有可能会非常庞大TCP 需要将一份数据拆分成多份使用多个报文向对方端传输数据。那么 TCP 就需要确认对方端成功接收到了其中的所有报文而没有丢失。
但由于报文在网络中传输需要时间主机双方不能实时确认每份TCP报是否被接收每份报文都是过去发送的。所以需要 确认应答机制来保证数据成功被接收。确认应答机制认为只要对方传来应答就判断报文 100% 收到。
TCP为每个需要传输的数据的字节都进行了编号主机向对方传输了多少数据只需要告诉对方编号的范围即可。 确认应答机制过程
主机A向主机 B 发送数据数据被拆分成多份 TCP 报分别发送。主机A发送第一份数据在报头中包含此次传输的数据字节序列号为 1-1000主机B接收到该TCP报后向A发送确认应答的TCP报且告知对方下一份数据的序列号应该从 1001 1001 1001 开始这是因为主机B要让主机 A 明确我应答的是 传输 1-1000 数据的TCP报因为实际传输中可能会有多个 TCP 报。 但是在网络通信中每一份发送端报文或接收端报文可能不会都安全地到达。如果发生了丢包TCP要如何应对呢
3. 超时重传机制
两个主机之间在进行网络传输时可能会因为网络拥堵等原因造成 TCP 报丢失。在上面确认应答的机制的情况下如果其中一份 TCP 报丢失了TCP 就需要对丢失的 TCP 报进行补发。但首先发送端要先知道接收端丢失了哪一份数据。
3.1 如何处理重传 如果丢失的是发送端的报文。主机B接收不到报文主机A迟迟等待不到应答就会认为发送的报文丢失了向主机B重新发送一个TCP报。 如果丢失的是应答报文那么主机 A 迟迟等到不到带有下一份应发序列号的应答就会认为先前发的报文丢失了主机A并不知道丢失的是自己的报文还是对方的应答报文向 B 主机重新发送一份与上一份相同的报文。 所以无论是发送端报文丢失还是接收端的应答报文丢失其处理方法都是一样的。都是发送端重新传一份没有正常经过确认应答机制的报文。
并且如果是应答报文丢失主机 B 可能会接收到多个相同的报文TCP 协议就需要具有去重的功能而这个去重的功能就是由上面确认应答机制提到的 TCP 为每个需要传输的数据的字节都进行了编号来实现的。
现在我们知道了超时重传的重传机制那么 TCP 又是如何判断、处理超时的呢
3.2 如何处理超时
最理想的情况是找到一个最小的时间保证 “确认应答一定能在这个时间内返回”。但这个时间的长短一定是动态的因为随着网络环境的不同网络通信的速度肯定也不同。如果超时时间设置得太长会影响整体重传的效率如果超时时间设置得太短有可能发送端会频繁发送重复报文。
Linux 中BSD Unix 和 Windows 也是如此超时以500ms为一个单位进行控制每次判断超时重传的超时时间都是 500ms 的整数倍。 如果判断超时重传一次后仍然得不到应答下次超时重传就是 2*500ms 以后再下次就是 4*500ms 以此类推。当重传累计到一定的次数后TCP 认为网络或者对端主机出现异常就会强制关闭连接。 4. 连接管理机制
TCP 是一个有连接的协议。那么 TCP 是如何保证连接是可靠的呢
TCP 的通信全过程 服务器端状态转换 [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] 客户端要等待一个2MSLMax Segment Life报文最大生存时间的时间才会进入 CLOSED 状态。 4.1 三次握手
TCP 在进行建立连接时假设是客户端主动向服务器建立连接 首先客户端向服务器进行 SYN 请求建立连接。服务器接收到客户端的请求后向客户端发送报文这个报文除了 SYN 以外还有客户端对服务器的 ACK 。客户端在接收到服务器的 SYNACK 后此时对于客户端来说连接已经建立完成所以在下一个报文中对服务器的 SYN 的 ACK 报文中是可以携带数据的称为捎带报文下面细讲。服务器在接收到客户端的 ACK 后对于服务器来说此时连接也建立完成。 注意在三次握手中 accept() 函数并没有参与到三次握手当中当连接建立完成时accept() 才会返回。 服务器和客户端建立连接的时间有一个非常短暂的时间差客户端比服务器要早一个报文。那么假如最后一个 ACK丢包了怎么办
此时对于客户端来说连接已经建立完成ESTABLISHED是可以正常发数据的。客户端进入正常通信状态向服务器发送第报文随后服务器就受到报文此时服务器还没有进入 ESTABLISHED 状态但服务器端认得发送该报文的IP和端口就是刚才还没有完成连接建立对服务器来说的客户端。于是服务器对客户端报文的应答就不会是 ACK 而是 RSTreset连接重置标志位表示我服务器这里的三次握手还没有完成需要重新走三次握手的操作。 如果客户端是浏览器触发 RST 的效果是这样 TCP三次握手的目的 验证全双工——验证网络的连通性。 全双工即同时既能读又能写。之所以要握三次手就是因为双方都要验证自己既能对对方写发送 SYN又能对对方读接收 ACK。 建立双方通信的共识意愿。 并不是说客户端想要和服务器建立连接服务器就必须要与其建立连接。在访问网页时也能常常看到网页拒绝访问的错误。
4.2 四次挥手
四次挥手别名连接终止协议是TCP双方断开连接的一种机制。TCP的四次挥手是进行可靠断开连接的最小次数。 客户端主动退出连接FIN此时在客户端的本地体现为用 scokfd调用了 close()。但既然文件描述符都关闭了客户端为什么还能接收到服务器的 ACK呢
实际上调用close()表示应用层的数据已经发送完成缓冲区的内容也已经清理完毕了但是对于操作系统来说还需要维持一段时间的TCP可靠性所以连接并没有完全关闭。
为什么是四次挥手而不是三次挥手
在三次握手中使用了SYNACK的方式保证了最小建立可靠连接的次数。那么为什么断开连接不能使用 FINACK的方式使得四次挥手变成三次挥手呢
由于断开连接是由应用层调用的应用层此时并不知道底层的通信在进行着怎么样的数据传输如果服务器端一接收到客户端发来的 FIN就立马也准备关闭连接那么可能会导致一方可能在确认关闭连接之前未能发送完所有数据。因为虽然说客户端知道自己已经没有数据要发送但不代表服务器端就没有数据要发送给客户端所以四次挥手确保一方发送完数据后再关闭连接避免数据丢失和确保双方都完全关闭维持TCP的可靠性和有序性。 由于TCP连接是全双工的因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭而另一方执行被动关闭。 在套接字的库中提供了一个关闭sockfd的函数 shutdown()。
#include sys/socket.h
int shutdown(int sockfd, int how) shutdown() 与 colse() 的不同在于shutdown() 的 how 变量用于控制如何关闭文件。填入 SHUT_RD 表示关闭读填入 SHUT_WR 表示关闭写填入 SHUT_WDRW的效果与 cOlse()相同完全关闭文件。而且shutdown() 没有计数器机制一个进程使用shutdown() 对一个文件进行关闭操作其他所有进程都会受到影响。 4.3 TIME_WAIT状态
TCP协议规定使用TCP的程序如果使用 Ctrlc终止程序或正常退出属于主动关闭连接而主动关闭连接的一方要处于 TIME_WAIT 状态等待两个 MSLMaximum Segment Lifetime报文最长存活时间的时间后才能回到 CLOSED 状态。 MSL 在RFC1122中规定为两分钟,但是各操作系统的实现不同, 在 Centos7、Ubuntu上默认配置的值是 60s。 使用 cat /proc/sys/net/ipv4/tcp_fin_timeout 可以查看系统默认 MSL 的值。 之所以 TIME_WAIT 的时间是 2MSL是因为 MSL 是报文的理论上的最大存活时间在 2MSL 内能保证双方所有尚未被接收的或迟到的都已经消失。 否则服务器重启后可能会收到上一个进程的迟到报文虽然说这种数据很可能是错误的。 理论上能保证最后一个报文可靠到达。 假设最后一个 ACK 丢失, 那么服务器会再重发一个 FIN. 这时虽然客户端的进程不在了, 但是 TCP 连接还在, 仍然可以重发 LAST_ACK 。
TIME_WAIT状态在服务器关闭但客户端还保持连接时如果服务器想使用原来的端口号重启进程就会发生 bind 错误。但一个服务器端可能需要处理非常大量的客户端的连接服务器主动关闭连接就会产生大量的 TIME_WAIT 连接。如果新来的客户端连接的ip和端口号和 TIME_WAIT 占用的链接重复了就会出现问题。
要想跳过 TIME_WAIT 状态套接字也提供了一个函数接口来设置socket的具体属性
#include sys/types.h
#include sys/socket.h
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 将 optname 设置为 SO_REUSEADDR 值为1表示允许创建端口号相同但IP地址不同的多个 socket 描述符。 4.4 CLOSE_WAIT状态
如果TCP连接通信结束后没有 close() 套接字导致四次挥手无法完成。此时 TCP 连接就会处于 CLOSE_WAIT 状态。由于进程结束后TCP连接还要处理善后工作所以即使程序退出处于 CLOSE_WAIT 状态的 TCP 并不会退出造成内存泄漏。所以在进行编码时必须要防止没有 close() 套接字的错误如果系统中存在大量的 CLOSE_WAIT 连接也要进行释放。
5. 滑动窗口
在先前的确认应答机制中提到TCP 通信的双方对于每发的一个数据段都要有一个 ACK 应答。但真实的情况中不可能是“接收到一个 ACK 再发送下一个数据段”这样效率太低。 真实的情况一定是同时发送多个数据段再同时接受多个 ACK 确认应答 一次发送窗口大小的数据段然后等待接收 ACK等到接收到第一个 ACK 后窗口向后滑动继续发送第五段数据段这便是滑动窗口。操作系统内核为了维护这个滑动窗口需要使用 发送缓冲区 来记录当前有哪些数据没有被应答只有确认应答过的数据才能从缓冲区删除。 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值图示窗口大小为 4000 字节四个段。窗口越大则网络的吞吐率就越高。但窗口大小也不是不变的下面细谈。 窗口滑动示图 在线性表描述的滑动窗口中表中的下标表示字节序号下标。其中窗口左边表示已发送已接收应答的数据包窗口中表示已发送待接收应答的数据包窗口右边表示未发送的数据包。所以说滑动窗口只能向右边移动。
在实际通信中接收方是有接收缓冲区的那么接收方的接收能力就一定是动态的所以滑动窗口的大小也不是一成不变的那么发送方如何得知接收方的接收能力呢
5.1 滑动窗口丢包情况
在超时重传机制中提过无论是数据包丢失还是 ACK 丢失发送方都会补发一份数据包给彼端。那么在滑动窗口的机制下TCP又要如何处理丢包呢
5.1.1 ACK丢失
如果是应答部分丢失那么发送端无需重新补发因为根据后续 ACK 的情况已经可以知道对方收到了数据包只是对方的 ACK 丢失了而已。滑动窗口机制使得双方通信中由于 ACK 丢包引起的超时重传大大减少。 图中主机B 1001 的ACK丢失但主机A接收到了 2001 的ACK说明在主机A的视角中主机B确实收到了 1-1000 的数据包也就无需补发了。 5.1.2 数据包丢失高速重发控制
如果是数据包丢失接收端一次接收多个数据包固然知道数据包的序号在什么范围内有缺失。此时接收端会暂时放弃对正常接收到的数据包做 ACK 应答而是反复发送丢包序号的 ACK 告知发送端发送的数据包出现了丢包需要补发。接收端接收到三次相同的ACK时就知道自己的哪一个数据包发生了丢包需要补发。
虽然说这段时间里接收端没有对正常接收到的数据做 ACK 但这些数据确实被收到了存储在接收端的接收缓冲区中。接收端接收到补发的数据包后这个补发的数据包的 ACK 会跳过正常被接收到的数据包只要其连续直接告知发送端窗口滑动后的数据序号所以发送端也无需对接收端正常接收到的数据包补发因为根据上面 ACK 丢失的案例补发数据包的ACK能证明其他数据包已经被正常接收。
这种机制被称为高速重发控制也叫快重传。 图中发生数据包丢失时接收端会发送三次以上相同的 ACK 来提醒发送端我要的数据包是“1001-2000”的数据。接收端在接收到三次相同的 ACK 时从丢包后开始算就知道自己发送的数据包丢失了。不等接收到窗口大小数量的 ACK就对丢包的 1001 − 2000 1001-2000 1001−2000 的数据包进行补发。等待接收到补发数据包的ACK后再进行滑动窗口此时正常的数据包的 ACK 肯定到达或丢失而这个补发数据包的ACK是正常窗口滑动后的数据包的序号因为参考上面 ACK 丢失的案例这个 ACK 能证明其他的数据包已经正常被接收到了。 对于数据包的丢失无所谓丢包的是在滑动窗口的最左边、中间、最右边。因为滑动窗口的滑动在收到对应字节序号时进行无论是中间丢包还是最右边丢包随着时间推进窗口向右滑动都会变成最左边丢包的情况。 当最左边丢包时滑动窗口此时的左侧不再右移等待最左侧的数据包补发后才会更新位置。
高速重发机制是否和超时重传机制矛盾
高速重发机制是接收到三次以上相同的 ACK 才会触发的重发机制而超时重传的重发机制依据的是时间。在三次握手和四次挥手中高速重发机制并不参与也就没有矛盾的说法。
而在正常通信中如果数据包丢失触发了接收端的高速重发机制但告知需要重发的 ACK 丢失到不足三个那么发送端就无法触发高速重发来补发数据包此时就需要超时重传机制让发送方主动补发数据包。或者数据包发生了大量的丢失不足以触发三次以上的ACK应答机制也需要超时重传机制让发送方主动补发数据包。所以两钟机制是可以共存的。 注意数据包大量丢失会触发拥塞控制机制慢启动、拥塞避免等来缓解网络拥塞情况所以实际情况会更复杂。 6. 流量控制
滑动窗口解决了数据包如何快速发送的问题但接收端接收数据的速度是有限的如果发送端发送的太快导致接收端的接收缓冲区打满溢出此后继续接收到的数据包将全部丢包。流量控制则是要解决怎么控制滑动窗口发送速度的问题。
TCP支持根据接收端的处理能力来决定发送端的发送速度这个机制就做流量控制Flow Control
6.1 16位窗口大小
在TCP报头中16 位窗口大小用来描述自己的接收能力缓冲区剩余空间大小滑动窗口的窗口大小通过 ACK 应答的 16 位窗口大小计算出来发送方就能控制下一轮窗口大小不至于发送的数据包超过对方的接收能力造成丢包的问题。
窗口大小字段越大说明网络的吞吐量越大。而当接收端发现自己的接收缓冲区快满了就会将窗口大小设置成设置成一个更小的值发送给发送端让发送端调小自己的发送量。如果接收端缓冲区满了就会将窗口置为 0 。这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段使接收端把窗口大小告诉发送端。 16 位窗口大小中的变量 int win_start ack_seq 和 int win_end win_start win 分别记录了窗口大小的字节序范围。而窗口的向右移动实际就是对 win_start、win_end。所以想要控制滑动窗口的变大和变小只需要控制这两个变量的差值即可。 在三次握手的过程中双方发给对方的 ACK 中的窗口大小就已经交换了双方的接收能力所以在第一轮数据包中滑动窗口的大小就已经得知了。
其他 16位窗口大小是16位所以接收缓冲区的大小为 65535 65535 65535 字节。但其实在 TCP 报头中的选项中有一个窗口扩大因子将窗口扩大因子设置为 n窗口大小就为16位再左移 n 位。在发送方TCP报头中设置 PSH 标志位可以催促接收方尽快将接收缓冲区的数据读走以来扩大滑动窗口大小一次性发送更多数据包。如果我们想让对方尽快处理数据都可以设置 PSH 。 6.2 探测窗口
当窗口大小变为 0 此时接收端的接收缓冲区已经满载。发送端要暂时停止发送数据包接收端需要时间来给接收缓冲区腾出空间。当接收端的接收缓冲区腾出空间后会给发送端发送一个窗口更新通知的报文让发送端设置滑动窗口发送下一轮数据包。
但这个窗口更新通知有可能需要很长时间才能发出或在网络传输的过程中丢包。所以发送端会等待一个超时重传时间如果时间到了还没有收到窗口更新通知就会给接收端发一个窗口探测让接收端回应此时的窗口大小是多少。 7. 拥塞控制
TCP 有了滑动窗口来提升发送数据包的速度也有流量控制机制来控制发送数据包的速度但这两者只是解决了通信双方的效率和可靠性。在实际的网络中存在很多很多的计算机如果此时网络已经很拥堵它们在不清楚网络状态的情况下认为自己或对方的接收缓冲区还绰绰有余就进行大量的数据发送就有可能造成网络更加拥堵。
所以 TCP 还需要一个机制用来控制或缓解多个 TCP 同时通信时造成的拥塞问题这种机制就是拥塞控制。
7.1 慢启动
所谓慢启动就是在刚开始通信时先发送少量的数据探探网络的拥塞情况再决定按照多大的速度传输数据。 但是这种增长是指数级增长一直按 2 n 2^n 2n 增长下去也不合理且先前提到的窗口大小的概念也没用了。
所以这里还需要引入一个概念拥塞窗口。在发送数据的开始将拥塞窗口设为 1 发送一个数据包。每接收到一个 ACK 应答拥塞窗口就增加 1 。在发送新一轮的数据包时将此时的拥塞窗口大小和接收端反馈的窗口大小作比较取较小值作为实际发送的窗口大小。
同时慢启动的中后期增长幅度太大不能单纯让拥塞窗口加倍。所以这里需要引入一个慢启动的阈值当拥塞窗口超过这个阈值的时候拥塞窗口就不再指数级增长而是改为线性增长。 在每次超时重发的时候慢启动阈值会变成原来的一半同时拥塞窗口置回 1。少量的丢包我们仅仅是触发超时重传大量的丢包我们就认为是网络拥塞。
7.2 延迟应答
如果接收端在收到数据后立马返回 ACK 此时返回的窗口大小可能就会比较小因为接收缓冲区此时还什么数据都没读走所以 ACK 的时机可以迟那么一会儿。一般延迟应答有两个因素要考虑 数量限制每个N个包就应答一次 时间限制超过最大延迟时间就应答一次 对于数量限制和时间限制依据操作系统不同而不同但一般 N 取 2超时时间取 200ms 。
7.3 捎带应答
接收端不一定需要专门发一个空报头的 ACK 来应答有时候 ACK 可以搭其他带有数据内容的报文坐顺风车一起发给发送端。
8. TCP粘包问题
粘包指的是数据包之间没有明确的边界。
对于UDP来说由于UDP的报文有明确的长度数据也是一个一个交给上层的具有明确的数据边界。即UDP报数据要么是0个、1个、2个没有半个数据。故使用UDP无需在应用层解决粘包问题。
但是对于TCP来说虽然站在传输层角度TCP报文也是一个一个过来按照序号排好放在缓冲区中等待读取或发送。但站在应用层的角度看到的只是一串串连续的字节流在应用层无法区分哪个部分从哪开始是一个完整的应用层数据包。故使用TCP需要在应用层解决粘包问题规定两个包的边界。 对于定长的数据包保证每次都按固定大小读取即可。对于变长的数据包则需要在包头位置规定一个包总长度的字段从而知道整个包的大小也可以在包与包之间使用明确的分隔符。如序列化和反序列化 9. TCP异常情况 进程终止进程终止会释放文件描述符也会发送 FIN 对于TCP来说和正常关闭没有什么区别。 机器重启/关机正常关闭机器都需要等待操作系统处理善后工作此时就是操作系统在关闭程序也有关闭TCP连接的流程和正常关闭没有区别。 拔网线/死机这种情况TCP连接就无法走正常关闭流程没有四次挥手。此时对方端还认为这个TCP连接正常通信只是我们一直没有给对方发送数据。在TCP协议中即使双方没有什么通信往来TCP连接也要保持很久几十分钟到几个小时。 但是TCP也内置了一个保活定时器。当对方不再发报文给我们时会定期询问对方连接是否还在如果对方没有应答也会把连接释放掉。 另外应用层的某些协议也有一些这样的检测机制。例如 HTTP 长连接中也会定期检测对方的状态。例如 QQ在QQ断线之后也会定期尝试重新连接。
10. 全连接队列
全连接队列是服务器端在TCP连接的三次握手成功后临时存放已完成连接请求的一个队列。临时存放直到服务器调用accept() 将连接从队列中提取出来进行后续的处理操作。 三次握手建立连接的过程和用户是否调用 accept() 无关。实际上三次握手是 conncet() 发起的在服务器来不及调用accept()的时候底层的TCP listen sock允许用户继续进行三次握手建立连接连接成功后存储在全连接队列里。 服务器有可能是太忙而没有调用 accept()当服务器解除忙碌调用 accept() 后会立马去全连接队列里将新的 sockfd 提取出来。 每个服务器套接字每个监听的端口都有自己的全连接队列全连接队列并不是无限的具有一个上限这个上限就是 backlog 1。全连接队列的数量由 listen() 的第二个参数 int backlog 决定全连接队列的节点不能为空不能一个都没有所以调用listen()设置全连接队列的数量时其数量必是 backlog 1 。
全连接队列不能为空会增加服务的闲置率减少给用户提供服务的效率和体验也不能太长浪费空间用户体验不好。
11. 半连接队列
半连接队列是TCP针对存放TCP三次握手未完成的连接的队列这个队列里的连接一般只会存在几十秒到几分钟并不是很重要。
12. 其他
12.1 16位紧急指针
16位紧急指针标识了哪部分数据是紧急数据而TCP报头中的 URG 标志位标识了16位紧急指针是否有效。16位紧急指针的内容是报文的起始位置的偏移量标识了从报文开始的多少字节之后是紧急数据。 注意这里的“指针”并不是c语言中的地址单纯是一个具有指向性的概念。 TCP中的紧急数据只有一个字节。 一般之所以这么小是因为它通常用来告知对方突然的变更。如应用层在下载文件时传输层TCP在发送大量的数据包。此时用户突然进行“暂停下载”或“删除任务”操作携带紧急指针和有效URG的报头就会在接收缓冲区中“插队”进入上层直接中断传输。
在 recv() 和 send() 函数中它们的 flag 参数就可以设置为 MSG_OOBout-of-band带外数据进行传输紧急数据。