TCP协议详解
文章平均质量分 81
详细阐述TCP协议在Linux内核中的实现
Remy1119
专注于技术
展开
-
12.2 拥塞控制简介
12.1.1 拥塞控制的作用 网络的带宽是有限的,如果到达通信子网中某一部分的包数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿, 这种现象就是网络拥塞。如果把网络中的数据传输比作交通运输的话,拥塞就是交通堵塞。交通堵塞是无法彻底避免的,但可以减少和缓解。遵守交通规则是减少交通堵塞的有效方法,而拥塞控制算法则是网络通原创 2015-04-19 14:52:57 · 1650 阅读 · 0 评论 -
12.1 抵达鱼人岛
现在我们要进入TCP协议中最复杂也是最关键的部分——拥塞控制。如果TCP是一个巨人,我们之前所研究的只是这个巨人的外形、肌肉和骨骼,拥塞控制是他的内脏,是真正决定其生机与活力的部分;若把TCP探秘之旅比作海贼王(航海王)中Monkey·D·Luffy一伙的冒险旅程,那么之前的探索只是伟大航路(Grand Line)的前半段——乐园。我们现在是Grad Line的中点——鱼人岛,即将进入更加新奇、更原创 2015-04-15 09:36:01 · 1198 阅读 · 0 评论 -
11.3 TCP内核同步
11.3.1 锁的结构 由2.1 Socket系统调用我们知道,一个TCP socket在内核有一个数据结构,这个数据结构是不能被两个及其以上的使用者同时访问的,否则就会由于数据不一致导致严重的问题。在Linux中,TCP socket的使用者有两种:进程(线程)和软中断。同一时间可能会有两个进程(线程),或位于不同CPU的两个软中断,或进程(线程)与软中断访问同一个socket原创 2015-04-14 12:27:37 · 1696 阅读 · 0 评论 -
11.2 epoll系统调用
在完成listen系统调用后,作为TCP Server的进程就可以等待接受连接请求了。当请求到来时,进程需要调用accept系统调用生成一个新的socket,并用之与客户端传输数据。这时进程需要管理的socket有两类:1)等待请求到来并与之建立连接的socket;2)已经与客户端建立的一对一的连接并与之进行数据传输的socket。当这些socket的数量很多时,如何及时获知哪些socket有可读原创 2015-04-14 11:03:13 · 1436 阅读 · 1 评论 -
10.3 接收缓存管理
TCP收到对端发送的数据后,通常不能立即交付应用进程。在应用进程取走数据之前,数据需要保存在接收缓存之中。如果应用进程取数据的速度比TCP从对端收数据的速度慢,则接收缓存中的数据会越来越多。因此在skb被放入接收缓存之前必须检查接收缓存能容纳的内存数,如果超出限制则必须丢弃skb。10.3.1 缓存占用原创 2015-04-13 17:06:31 · 2059 阅读 · 0 评论 -
10.2 发送缓存管理
应用进程使用TCP发送的数据会先放入发送缓存中,TCP的发送缓存是一个skb队列。这个队列存在的意义是:保证应用进程交付TCP的数据能够可靠地交付目的端。在收到对端的ACK之前,发送缓存中的数据不能删除。10.2.1 使用缓存 对发送缓存的使用是从tcp_sendmsg函数开始的: 1016 int tcp_sendmsg(struct kiocb *iocb,原创 2015-04-13 13:55:45 · 1612 阅读 · 0 评论 -
10.1 缓存管理简介
TCP原创 2015-04-12 23:35:12 · 794 阅读 · 0 评论 -
9.10 TIME_WAIT定时器
9.10.1 Why 当socekt进入TIME_WAIT状态后,TIME_WAIT定时器启动。在超时之前,替代socket的tw sock会处理旧连接中的包,阻止其危害新连接。定时器超时后,tw sock被删除,并释放其占用的端口号。原创 2015-04-10 16:48:26 · 1372 阅读 · 2 评论 -
9.9 FIN_WAIT2定时器
9.9.1 Why 如果应用进程调用close系统调用关闭socket,但此时socket与对端的通信尚未完成,则这个socket被称为“孤儿socket”。如果孤儿socekt进入FIN_WAIT2状态(或socket进入FIN_WAIT2状态后再成为孤儿socket),会等待对端发送FIN以彻底结束连接。但如果对端一直不发送FIN则这个孤儿socekt会一直存在,从而一直占用原创 2015-04-10 16:06:08 · 1468 阅读 · 0 评论 -
9.8 延迟ACK定时器
9.8.1 Why TCP在收到数据后必须发送ACK给对端,但如果每收到一个包就给一个ACK的话会使得网络中被注入过多报文。TCP的做法是在收到数据时不立即发送ACK,而是设置一个定时器,如果在定时器超时之前有数据发送给对端,则ACK会被携带在数据中捎带过去;超时则由定时器发送ACK。这样就减少了报文的发送,提高了协议的效率。9.8.2 When 设置延迟A原创 2015-04-10 14:30:36 · 2194 阅读 · 0 评论 -
9.7 保活(Keepalive)定时器
9.7.1 Why 当单工模式下TCP数据发送方发送了一些数据后就不再发数据,数据接收方也不会发送报文,这时TCP连接处于静止状态(比如Telnet应用)。保活功能可以使用保活定时器向对端发送探测报文来确定对端的连接是否正常,如果对端有回应则继续维持连接,否则关闭连接,释放资源。开启保活功能需要使用SO_KEEPALIVE socket选项。9.7.2 When原创 2015-04-10 13:21:23 · 1587 阅读 · 0 评论 -
9.6 坚持(Persist)定时器
9.6.1 Why 数据发送方收到接收方的通告窗口为0时,就不能再发送数据,一直等到对方发送窗口更新为止。但对端发送的窗口更新报文可能会丢失,如果发送方只是等待的话会导致数据传输会一直停滞,最后连接会被断开。这时坚持定时器闪亮登场!数据发送方可以设置坚持定时器定时发送1个探测报文,对端收到后会对这个报文发送ACK报文,这样发送方就能及时得知窗口更新事件了。一旦窗口非0则数据传输原创 2015-04-10 10:35:12 · 1523 阅读 · 1 评论 -
9.5 尾部丢失探测(Tail Loss Probe)定时器
9.5.1 Why 在9.4节中,我们了解到如果拥塞窗口较小且数据的最后一段数据丢失时,快速重传算法会因为无法收到足够数量的ACK而无法及时重传丢失的报文。尾部丢失探测(Tail Loss Probe)定时器就是为了解决这个问题而设计的。9.5.2 When TLP在tcp_schedule_loss_probe函数中安装:1913 bool tcp_sc原创 2015-04-10 08:44:56 · 3463 阅读 · 0 评论 -
9.4 ER(Early Retransmit)定时器
9.4.1 Why原创 2015-04-09 17:04:18 · 1767 阅读 · 0 评论 -
9.3 重传定时器
9.3.1 Why TCP在发送SYN、FIN以及数据包时为了保证可靠传输,会先将它们放入发送队列再发送副本到网络中,一旦发现数据丢失(比如连续收到多个ack_seq号相同的ACK)则重传发送队列中的skb。如果丢失发现机制失效了呢(比如ACK丢失),这时就需要重传定时器在指定的时间内重传数据,否则数据传输就可能会阻塞。9.3.2 When 设置重传定时器的原创 2015-04-09 10:49:50 · 2455 阅读 · 0 评论 -
9.2 SYN-ACK定时器
9.2.1 Why TCP服务器在收到SYN请求后发送SYN|ACK响应,然后等待对端的ACK到来以完成三次握手。如果没有收到ACK,TCP应该重传SYN|ACK,这个功能由SYN-ACK定时器完成。由于SYN|ACK发送后并没有放入发送队列中,故重传时必须重新构建SYN|ACK报文。9.2.2 When TCP在发送SYN|ACK响应后设置SYN-ACK定时器: 1465 i原创 2015-04-09 08:57:32 · 1983 阅读 · 0 评论 -
9.1 定时器初始化
TCP为每条连接先后设置9个定时器,分别为:重传定时器、丢失探测(LOSS_PROBE)定时器、早期重传(EARLY_RETRANS)定时器、SYN-ACK定时器、延迟ACK定时器、坚持定时器、保活定时器、FIN_WAIT2定时器、TIME_WAIT定时器。由于一些定时器不能同时设置,故TCP使用了5个定时器结构完成全部9个定时器的功能。相关数据结构如下: 285 struct sock原创 2015-04-09 08:43:20 · 1936 阅读 · 0 评论 -
8.5 MD5选项
8.5.1 选项功能 TCP MD5选项用于强化BGP协议的安全性,其基本原理是在TCP报文段的选项中携带MD5摘要。这个摘要的行为类似于这个报文的签名,其中包含这只有通信双方才能理解的信息。如果BGP协议使用TCP作为其传输层协议,使用MD5选项会有效减少安全隐患。8.5.2 协议规范 TCP MD5选项的规范由RFC 2385和RFC 4278提出。 每一个TCP原创 2015-04-08 16:26:08 · 4427 阅读 · 0 评论 -
8.4 选择确认(SACK)选项
TCP收到乱序数据后,会将其放入乱序队列中,然后发送重复ACK给对端。对端收到多个重复的ACK后,就会推测到可能发生了数据丢失,再重传数据。如果乱序的数据比较零散,则这种机制的效率会很低。使用SACK选项可以告知发包方收到了哪些数据,发包方收到这些信息后就会知道哪些数据丢失,然后立即重传缺失部分即可。这就大大提高了数据重传的速度。 SACK选项分为两类:SACK-Permitte原创 2015-04-07 21:10:23 · 9497 阅读 · 0 评论 -
8.3 时间戳(Time Stamp)选项
在时间戳选项诞生之前,TCP有三个问题难以解决:(1)通信延迟RTT(Round Trip Time)测量 RTT对于拥塞控制是十分重要的(比如计算多长时间重传数据)。通常,测量RTT的方法是发送一个报文,记录发送时间t1;当收到这个报文的确认时记录时间t2,t2 - t1就可以得到RTT。但TCP使用延迟确认机制,而且ACK可能会丢失,使得收到ACK时也无法确定是对哪个报文的回应。原创 2015-04-07 12:31:45 · 19956 阅读 · 4 评论 -
8.2 窗口扩大(WScale)选项
TCP发送端在发送一个满窗口长度(最大65535字节)的数据后必须等待对端的ACK更新窗口后才能继续发送数据。在广域网中传输数据时,由于往返时间较长,发送端等待的时间也会较长,这样会使得TCP数据交互的速度大大降低(长肥管道现象)。使用窗口扩大选项可以使得发送端得到更大的通告窗口,这样就可以在ACK到来前发送更多的数据,减少了等待的时间,提高了数据传输效率。 窗口扩大因子(shift.原创 2015-04-07 10:43:53 · 12234 阅读 · 0 评论 -
8.1 最大报文段(MSS)选项
MSS选项用于通知对端本端能接受的每个TCP分段中的最大数据长度,发送端TCP用接收到的MSS值作为决定所发送分段的最大大小的最大值。MSS选项的总长度为4字节,MSS值为16bit,最大为65535。MSS选项只能在有SYN标记的包中携带。 在初始化socket的时候就需要设置MSS相关信息,connect时: 2752 void tcp_connect_init(struct s原创 2015-04-07 08:36:23 · 3830 阅读 · 0 评论 -
7.6 TIME_WAIT状态处理
TCP在以下情况下可能会进入TIME_WAIT状态:(1)在TCP_FIN_WAIT2状态时调用close系统调用时;(2)TCP_FIN_WAIT2收到对端的FIN时(一定会进入TIME_WAIT状态);(3)成为orphan sock并且在TCP_FIN_WAIT1状态下收到ACK时;(4)TCP_CLOSING状态下收到ACK时(一定会进入TIME_WAIT状原创 2015-04-06 22:38:10 · 1400 阅读 · 0 评论 -
7.5 RST的发送与接收
前文说过,发送RST会快速关闭一条TCP连接。RST的发送由tcp_send_active_reset函数完成:2586 void tcp_send_active_reset(struct sock *sk, gfp_t priority)2587 {2588 struct sk_buff *skb;2589 2590 /* NOTE: No TCP options a原创 2015-04-06 16:09:46 · 2147 阅读 · 0 评论 -
7.4 FIN及其ACK的接收
TCP在收到FIN时,会调用tcp_data_queue函数进行处理: 4300 static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)4301 {4302 const struct tcphdr *th = tcp_hdr(skb);4303 struct tcp_sock *tp = tcp_s原创 2015-04-06 12:36:53 · 4644 阅读 · 3 评论 -
7.3 Close系统调用
应用进程如果不再想使用socket,可以使用close系统调用将其关闭。close系统调用的基本功能是关闭文件描述符并释放其对应的内核资源。如果在调用close前调用了shutdown,则不会发送FIN。如果没有调用shutdown就调用了close,TCP会发送FIN。 close系统调用的原型为:int close(int fd); fd就是socket文件原创 2015-04-06 06:40:10 · 2901 阅读 · 0 评论 -
7.2 Shutdown系统调用
当应用进程不想再接收数据时,就可以关闭TCP连接。关闭的方式有两种:如果进程既不想发送数据,也不想接收数据,则可以选择完全关闭;如果进程不想发送数据,但仍可以接收数据,可以执行“半关闭”。 关闭TCP连接可以使用shutdown系统调用:int shutdown(int sockfd, int how); sockfd是要关闭的TCP socket的文件描述符,原创 2015-04-05 22:00:51 · 1386 阅读 · 0 评论 -
7.1 连接关闭流程
TCP是全双工协议,关闭连接时两个方向必须单独进行。当一端发送数据完毕后,应用进程就可以调用shutdown或close系统调用发送一个FIN来终止该方向上的连接。另一端收到FIN后,必须告知应用进程对端已经终止了数据连接。得到通知后应用进程可以调用shutdown或close系统调用关闭自己向对端发送数据的连接,这时TCP连接才会被真正关闭。 一端收到FIN,只意味着对端不会再原创 2015-04-05 21:57:00 · 785 阅读 · 0 评论 -
6.5 TCP Connection Repair
TCP Connection Repair即TCP连接修复,这个功能是通过TCP_REPAIR socket选项实现的。当这个选项开启时,socket就会切换到修复模式,这个模式下对socket的任何操作都不会导致实质的协议行为(发送TCP报文),但会使socket直接进入成功完成动作的状态。6.5.1 相关选项 与TCP修复模式有关的socket选项有4个:TCP_REPA原创 2015-04-05 16:51:53 · 2888 阅读 · 0 评论 -
6.4 Nagle算法与CORK算法
6.4.1 算法目的 当发送端应用进程产生数据很慢(比如Telnet应用),就会使应用进程间传送的TCP有效载荷很小(可能只有1个字节),而传输开销有40字节(20字节的IP头+20字节的TCP头) 这种现象就叫糊涂窗口综合症(Silly Windw Syndrome)。如果有大量的小TCP报文在网络中传输会导致网络拥塞。Nagle算法是为了解决TCP数据发送端“糊涂窗口综合症”原创 2015-04-04 15:25:54 · 1833 阅读 · 0 评论 -
6.3 紧急模式
通常情况下,TCP数据由连接的一端发送到另一端,数据的所有字节都是精确排序的,后写入的字节绝不会早于先写入的字节到达。然而socket API提供一种功能可以使得一些数据无阻的先于早写入的数据到达接收端,这种功能就是所谓的发送带外数据 。在TCP中这样的功能是通过紧急数据模式完成的。 一个TCP流通常希望顺序地发送数据字节,那么乱序的发送数据就似乎与流的概念相违背。那么为什么要提供带外数据的原创 2015-04-03 13:48:25 · 1330 阅读 · 0 评论 -
6.2 TCP滑动窗口
TCP滑动窗口的功能是实现流量控制。数据接收方只接收seq落入窗口范围内的数据;发送方也不会发送窗口之外的数据,一旦发现窗口太小则会停止发送直到窗口变大,这样TCP数据接收方就能通过窗口通告来控制数据发送方发送数据的速度。窗口的值存储在TCP报文段的window字段中,大小为16bit,即窗口的最大值是65535。如果使用窗口扩大选项(后续讨论),则通告窗口的值为window左移窗口扩大因子个位数原创 2015-04-03 08:26:05 · 2160 阅读 · 0 评论 -
6.1 序列号与确认号
由前文可知,TCP的序列号(seq)与确认号(ack_seq)都是无符号32的整数。seq用于为每一字节TCP数据编号(SYN和FIN标记位也会占用一个seq);ack_seq用于告知数据发送端:“接收端已经收到了ack_seq - 1号数据,请发送ack_seq号数据”。下面我们重温一下TCP连接建立和数据传输过程,在这个过程中探究一下seq和ack_seq是如何使用的。首先来介绍一下tcp_s原创 2015-04-02 13:13:13 · 3119 阅读 · 1 评论 -
5.7 TCP receive copy offload
TCP receive copy offload是一种异步IO技术,这种技术使用网络协议栈中的DMA引擎代替CPU来实现copy-to-user的操作,节约了CPU周期。DMA引擎是指数据移动加速(Data Movement Acceleration)引擎,它将某些传输数据的操作从CPU转移到专用硬件,从而可以进行异步传输并减轻CPU负载。 支持这个功能需要开启CONFIG_NET_DMA内原创 2015-04-02 08:51:43 · 1498 阅读 · 0 评论 -
5.6 TCP prequeue
TCP中用于接收skb的缓存除了sk->sk_receive_queue之外,还有prequeue。TCP prequeue中的包会在进程上下文中处理,而非软中断上下文。TCP prequeue特性会给带来较大的延迟,其优点在于这个特性在理论上给与了别的进程以及别的socket连接更多的公平性,但实际情况如何不得而知。开启这个功能的条件是net.ipv4.tcp_low_latency内核选项为0原创 2015-04-01 21:42:41 · 2621 阅读 · 0 评论 -
5.5 收包系统调用
收到内核发出的“有数据可读”的事件通告后,进程就可以使用read、readv、recv、recvfrom、recvmsg系统调读取TCP数据。这些函数在内核都会调用__sock_recvmsg函数:原创 2015-03-31 23:51:26 · 1244 阅读 · 0 评论 -
5.4 ACK发送与接收
tcp_send_challenge_acktcp_incr_quickack ACK标记的处理由tcp_ack函数完成: 3325 static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)3326 { 3327 struct inet_connection_soc原创 2015-03-30 14:16:22 · 4385 阅读 · 2 评论 -
5.2 快速路径处理
处理TCP数据段的唯一入口函数是tcp_rcv_established。在Liinux中,有两种方法来处理传入TCP数据段:快速路径(Fast Path)和慢速路径(Slow Path)。使用快速路径只进行最少的处理,如处理数据段、发生ACK、存储时间戳等。使用慢速路径可以处理乱序数据段、PAWS、socket内存管理和紧急数据等。Linux通过预测标志来区分这两种处理模式。见tcp_rcv_es原创 2015-03-29 06:37:07 · 2402 阅读 · 0 评论 -
5.1 GRO(Generic Receive Offload)
GRO(Generic Receive Offload)的功能将多个 TCP 数据聚合在一个skb结构,然后作为一个大数据包交付给上层的网络协议栈,以减少上层协议栈处理skb的开销,提高系统接收TCP数据包的性能。这个功能需要网卡驱动程序的支持。合并了多个skb的超级 skb能够一次性通过网络协议栈,从而减轻CPU负载。 GRO是针对网络收包流程进行改进的,并且只有NAPI类型的驱动才原创 2015-03-27 14:19:33 · 7600 阅读 · 1 评论 -
4.4 TCP Small Queue(TSQ)
TCP发送的数据经过IP层添加IP包头后,会被发送到IP数据栈和网卡之间的队列中,当网卡能够发送数据时会到这个队列中去取skb。当TCP发送数据过快时,或一个带宽很大或者包速率很大的非TCP数据流会把队列里的所有空间占满,造成数据包丢失和延时的问题。更糟的是,这样很可能会使另一个缓存产生,进而产生一个静止队列(standing queue),造成更严重的延时并使TCP的RTT和拥塞窗口的计算出现问原创 2015-03-26 09:38:13 · 4512 阅读 · 0 评论