目录
简介
tcp协议是TCP/IP协议族的另一个重要的协议.和ip协议相比,tcp更靠近应用层,因此在应用层具有更强的可操作性.一些重要的socket都与TCP相关.
主要内容包括:
- TCP头部信息:tcp头部信息出现在每个TCP报文段,用于指定通信的源端口号,目的端端口号,管理TCP连接,控制两个方向的数据流.
- TCP状态转义方程.tcp连接的任一端都是一个状态机.在TCP连接从建立到端开的整个过程中,连接两端的状态机将精力不同的状态变迁.理解tcp状态转义对于调试网络应用程序有很大的帮助.
- TCP数据流.分析tcp数据流,我们可以从网络程序外部了解应用层协议和通信双方交换的应用程序数据.主要讨论两种类型的TCP数据流:交换数据流和成块数据流.紧急数据(TCP数据流中的一种特殊数据)
- TCP数据流的控制.为了保证可靠传输和提高网络通信质量,内核需要对TCP数据流进行控制.主要讨论两个方面超时重传和拥塞控制
tcp服务特定
传输层协议主要为TCP和UDP,TCP相对于UDP的忒电视:面向连接,字节流和可靠传输
- 使用tcp协议通信的双方必须先建立连接,然后才能开始数据的读写.双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输.
- tcp是全双工的,双方的数据读写可以通过一个连接进行.完成数据交换之后,通信双方都必须端口连接以释放资源.
tcp的这种连接时1对1,所以基于广播和多播(目标是多个主机地址)的应用是不能使用TCP服务的.无连接的UDP比较适合广播和多播.
字节流与数据报服务的区别:基于流的数据没有边界(长度)限制.源源不断地从通信的一端流入另一端.发送端可以逐个字节地向数据流种写入数据,接收端也可以逐个字节地将他们读出.UDP数据报都有长度,接收端必须以该长度为最小单位将其所有内容一次读出,不然数据被截断.对应到实际编程种,则体现为通信双方是否必须执行相同次数的读写操作(只是表现形式).
tcp的发送与接收
发送端程序连续地执行多次写操作时,TCP模块先将这些数据放入TCP发送缓冲区.当TCP模块真正开始发送数据时,发送缓冲区种这些等待发送的数据可能被封装成一个或多个TCP报文段发出.因此TCP模块发送出的TCP报文段的个数和应用程序执行的写操作次数之间没有固定的数量关系.
接收端接收到一个或多个TCP报文段之后,tcp模块将它们携带的应用程序数据按照TCP报文段的序号一次放入TCP接收缓冲区,并通知应用程序读取数据.接收端应用程序可以一次性将TCP接收缓冲区的数据全部读出,也可以分次读取,取决于用户指定的应用程序缓冲区的大小.
发送端执行的写操作次数和tcp模块接收到的TCP报文段个数没有固定数量关系。
tcp可靠性得保证
- 发送应答机制:发送端发送的每个tcp报文段,都必须得到接收方的应答,才认为tcp报文段传输成功
- 超时重传:发送端在发送出一个TCP报文段之后启动定时器,如果在定时器时间内没有收到应答,将会重新发送该报文段。
- tcp会对收到的报文段进行重排:tcp报文段通过ip数据报发送的,ip数据报是乱序的,因此tcp会对收到的数据进行重排,整理,再交给应用程序。
tcp头部
描述 | |
---|---|
16位源端口号(port number) | 告知主机报文段来自哪里(源端口),以及传给哪个上层协议或应用程序(目的端口).进行tcp通信时,客户端通常使用系统自动选择临时端口号,而服务器则使用知名服务端口号,知名服务端口号使用的定义在/etc/services 文件种 |
32位序号(sequence number) | 一次TCP通信(从tcp连接建立到断开)过程中某个传输方向上的字节流的每个字节的编号.a和b进行tcp通信,a发送给b的第一个tcp报文段中,序号值被系统初始化为某个随机值ISN(initial sequence number,初始号值).在该传输方向(a->b),后续的TCP报文段中序号值将被系统设置成ISN加上该报文段所携带数据的第一个字节在整个字节流中的偏移,假设某个TCP报文段传输的数据时字节流中1025-2048字节,那么该报文段序号值就是ISN+1025 |
32位确认号(acknowledgement number) | 用作对另一方发送的TCP报文段的响应.其值是收到的TCP报文段的序号值加1.a和b进行tcp通信,那么a发送出的TCP报文段不仅携带自己的序号,而且包含对b发送来的TCP报文段的确认号.繁殖b发送的tcp报文段也同时携带自己的序号和a发送的报文段的确认号 |
4位头部长度(header length) | 标识该tcp头部有多少个32bit字(4字节),因为4bit最大能表示15,因此tcp头部最长是60字节 |
6位标志位(flags) | URG,ACK,PSH,RST,SYN,FIN |
16位窗口大小(window size) | 是TCP控制流量的一个手段.窗口是指的是接收通告窗口(receiver window,RWND)它告诉对方本端的TCP接收端缓冲区还能接收多少字节的速度,这样对方就可以控制发送速度 |
16位检验和(TCP checksum) | 由发送端填充,接收端对TCP报文段执行CRC算法检验TCP报文段在传输过程中是否损坏,这个检验也包含数据部分,TCP可靠的重要保障 |
16位紧急指针(urgent pointer) | 正的偏移量.它和序号字段的值相加表示最后一个紧急数据的下一字节的序号.因此,确切地说,这个字段是紧急指针相对当前序号的偏移,不妨称之为紧急偏移.TCP的紧急指针式发送端向接收端发送紧急数据的方法. |
6位标志位说明:
- URG:表示紧急指针(urgent pointer)是否有效
- ACK:表示确认号是否有效.一般称携带ACK标志的TCP报文段为确认报文.
- PSH:提示接收端应用程序应该立即从TCP接收缓冲区读走数据,为接收后续数据腾出空间(如果应用程序不将数据读走,他们就会一致停留在TCP接收缓冲区)
- RST:表示要求对方重新建立连接.一般称携带RST标志报文的TCP报文段为复位报文段.
- SYN:表示请求建立一个连接.一般称携带SYN标志的tcp报文段为同步报文段.
- FIN:表示通知对方本端要关闭连接了.一般称携带FIN报文段的TCP报文为结束报文段.
tcp头部选项
TCP头部得最后一个选项字段(options)是可变长得可选信息.这部分最多包含40字节,因为TCP头部最长是60字节.典型得头部结构为:
1 | ------------------------------------------- |
选项得第一个字段kind说明选项的类型.有的TCP选项没有后面两个字段.仅包含1字节的kind字段.第二个字段(length)指定该选项的总长度,该长度包含kind字段和length字段占据的两字节.第三个字段info是选项的具体信息.常见的TCP options有7种
kind | length | length-describe | describe |
---|---|---|---|
kind=0 | length=2 | 选项表结束选项 | |
kind=1 | length=2 | 空操作(nop)选项 | 一般用于将TCP选项总长度填充为4字节的整数倍 |
kind=2 | length=4 | 最大segment长度(2字节) | 最大报文段长度选项. |
kind=3 | length=3 | 移位数(1字节) | 窗口扩大因子选项. |
kind=4 | length=2 | SACK | 选择性确认选项 Selective Acknowledgement |
kind=5 | length=N*8+2 | 第一块左边沿(4字节),第一块右边沿(4字节)……第n块左边沿,第n块右边沿 | sack实际工作的选项 |
kind=8 | length=10 | 时间戳值(4字节)时间戳回显应答(4字节) | 时间戳选项.该选项提供了较为准确的计算通信双发之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息.可以修改/proc/sys/net/ipv4/tcp_timestatmps 内核变量来启用或关闭时间戳选项 |
最大报文段长度选项
TCP连接初始化时,通信双方使用该选项来协商最大报文段(Max Segement Size,MSS).TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的tcp头部和20字节的ip头部),这样携带TCP报文段的ip数据包的长度就不会超过MTU(假设tcp头部和ip头部都不包含选项字段,并且这也是一般情况),从而避免本机发生ip分片..对以太网而言,MSS值为1460(1500-40)字节
窗口扩大因子
TCP连接初始化时,通信双方使用该选项来协商接受通告窗口的扩大银子.在TCP的头部中,接收通告窗口大小是用16位标识的,故最大为65535字节,窗口扩大银子解决了这个问题.假设tcp投不中的接收通告窗口为N,窗口扩大银子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N*2^M
,或者说N左移M位.M的取值是0~14.可以通过修改/proc/sys/net/ipv4/tcp_window_scaling
内核变量来启用或关闭窗口扩大因子选项.
和MSS一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略.但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小是该tcp报文段的实际接收通告窗口大小.当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了.
SACK确认
tcp通信时,如果某个tcp报文段丢失,则tcp模块会重传最后被确认的tcp报文段后续的所有报文段.这样原先已经正确传输的tcp报文段也可能重复发送,从而降低了tcp性能.sack技术是为了改善这种情况,它使tcp模块只重新发送丢失的tcp报文段,不用发送所有被确认的tcp报文段.选择性确认选项用在tcp连接初始化时,标识是否支持sack技术.我们可以通过修改/prco/sys/net/ipv4/tcp_sack
内核变量开启或关闭选择性确认选项
SACK实际工作选项
该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块.每个块边沿(edge of block)参数包含一个4字节的序号.因为一个块信息占用8字节,所以tcp头部选项实际上最多可以包含4个这样不连续数据块(考虑选项类型和长度占用2字节)
tcpdump观察tcp头部信息
1 | IP 127.0.0.1.37804 > 127.0.0.1.23: Flags [S], seq 1037143771, win 65495, options [mss 65495,sackOK,TS val 1696486497 ecr 0,nop,wscale 7], length 0 |
- Flags[S]:标识TCP报文段包含
SYN
,其表示为一个同步报文段.如果包含其他标志也会在flags后的[]
内表示出. - seq:序号值,这个是第一个该传输方向的tcp报文段,因此也是该传输方向的ISN值.因为是第一个报文段因此没有针对对方发送过来的TCp报文段的确认值.
- win:接收通告窗口大小.因为是同步报文段,所以win反映的是实际是接收通告窗口大小.
- options:是tcp options,具体内容就在括号中,mss发送端(客户端)通告最大报文段长度.通过ifconfig查看回路接口mtu.
- sackOK:表示同意使用sack选项.
- TS val:是发送端时间戳
- ecr是时间戳回显应答. 因为是第一次TCP通信的第一个TCP报文段,因此针对对方的时间戳应答为0.
- nop空操作选项
- wscale指出发送端使用的窗口扩大因子为6.
16进制 | 10进制 | 说明 |
---|---|---|
0x93ac | 37804 | 源端口号 |
0x0017 | 23 | 目的端口号 |
0x3dd18edb | 1037143771 | 序号 |
0x00000000 | 0 | 确认号 |
0xa | 10 | tcp头部长度为10个32位(40字节) |
0x002 | 2 | 设置了syn标志 |
0xffd7 | 65495 | 接收通告窗口大小 |
0xfe30 | 头部校验和 | |
0x0000 | 未设置urg标志,索引紧急指针无意义 | |
0x0204 | 最大报文段长度选项的kind值和length值 | |
0xffd7 | 65495 | 允许最大报文段长度 |
0x0402 | 允许ack选项 | |
0x080a | 时间戳选项的kind和length值 | |
0x651e5461 | 1696486497 | 时间戳 |
0x00000000 | 0 | 回显应答时间戳 |
0x01 | 1 | 空操作选项 |
0x0303 | 窗口扩大银子选项的kind值和length值 | |
0x07 | 7 | 窗口扩大因子为7 |
tcp连接的建立与关闭
使用tcpdump获取连接过程
1 | # tcpdump -i eth0 -nt '(src 49.233.197.89 and dst 172.31.92.125) or (src 172.31.92.125 and dst 49.233.197.89)' |
因为整个过程没有应用层数据交换,所以tcp数据部分长度(length)总是0.
- 第一个tcp包含
SYN
flag,因此是一个同步报文段,即客户端向服务器发起连接请求.同时,该报文段包含一个ISN值为53574930的序号 - 第二个也是TCP同步报文段,表示服务器同意与客户端建立连接.同时它发送自己的ISN的2159701207序号,并对第一个同步报文段进行确认,确认值是53574931,第一个中的值加1
- 第三个报文段是第二个报文段的确认,tcp连接就建立起来了.tcp的三次握手.从第三个报文段开始,tcpdump输出的序号值和确认值都是相对初始ISN值得偏移.可以开启tcpdump得-S来选择打印序号得绝对值.
- 第四个报文段包含FIN标志,因此是一个结束报文段,即客户端要求关闭连接,结束报文段与同步报文段一样,也要占用一个序号值.
- 服务端用报文段5来确认结束该报文段
- 服务端继续发送自己得结束报文段6.
- 客户端用报文段7基于确认.实际上,仅用于确认目的得确认报文段5是可以省略,因为结束报文段6也包含了该确认信息.确认报文段5是否出现在连接断开得过程中.取决于tcp延迟确认性.
三次握手与四次挥手
- 在谢希仁著《计算机网络》第四版中讲“三次握手”的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误”。在另一部经典的《计算机网络》一书中讲“三次握手”的目的是为了解决“网络中存在延迟的重复分组”的问题。这两种不用的表述其实阐明的是同一个问题。
- 谢希仁版《计算机网络》中的例子是这样的,“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。
半关闭状态
tcp是全双工的.它可以允许两方的数据传输被单独关闭.通信的一端可以发送报文段给对方.告诉它本端已经完成了数据的发送,但允许继续接收来自对方的数据,直到对方也发送结束报文段关闭连接.tcp连接的这种状态称为半关闭(half-close)
服务器和客户端应用程序判断对方是否关闭的方法是:read系统调用返回0.linux还提供其他检测连接是否关闭的方法.
socket网络接口通过shutdown函数提供了对半关闭的支持.使用半关闭的应用程序很少见.
连接超时
观察这些报文段,超时时间分别是1s,2s,4s,8s,16s.推断最后一个报文段超时时间是32s.因此tcp模块一共进行了5次重连,由/proc/sys/net/ipv4/tcp_syn_retries
内核变量定义.
每次重连时间都增加1倍,5次均失败的情况下tcp放弃连接,并通知应用程序.
tcp状态转移
服务器状态转移过程
- listen: 通过lisnten系统调用
- syn_rcvd: 监听到请求(同步报文段),该连接被放入内核等待队列,并向客户端发送带syn标志的确认报文段,进入syn_rcvd状态.
- established: 服务器接收到客户端发送的确认报文段.此状态代表双方能够进行双向通信的状态.
- close_wait:客户端主动关闭,服务器通过返回确认报文段来使连接进入close_wait状态.
- last_ack:服务器检测倒客户端关闭连接后,也会立即发送一个结束报文段来关闭连接.进入last_ack状态,等待客户端最后一次确认
客户端状态转移过程
- syn_sent: 客户端调用connect(发送一个同步报文段),主动建立连接,转入syn_sent状态
- 失败1: connect连接的目标端口不存在(未监听),或者端口处于time_wait,则服务器给客户端发送一个复位报文段,调用失败
- 失败2: 如果目标端口存在,connect超时时间内未收到确认报文段,则connect失败.
- close: connect失败则立即返回close状态.
- established: 客户端从恒功收到服务端的同步报文段和确认,则connect成功.进入established状态.
- fin_wait_1: 客户端主动执行关闭,向服务器发送一个结束报文段,进入fin_wait_1
- fin_wait_2: 客户端收到服务器用于专门确认目的的确认报文段,则转移至fin_wait_2
- 客户端处于fin_wait_2,儿服务器也处于close_wait状态,这一对状态时可能发生半关闭状态.此时服务器也发送关闭连接(结束报文段),客户端将予确认并进入time_wait状态.
fin_wait_1 直接进入 time_wait: 直接收到待确认信息的结束报文段.
fin_wait_2 需要收到服务器发送结束报文段,才能转移至time_wait状态,否则将一直停留在此状态.如果不是为了在半关闭下接收数据,连接长时间地停留在fin_wait_2状态并无益处.
- 连接停留在fin_wait_2状态可能发生在: 客户端执行半关闭,未等服务器关闭连接就强行退出了.此时客户端连接由内核接管,可称之为孤儿连接(类似孤儿进程).linxu为了防止孤儿连接长时间停留在内核,定义了两个内核变量:/proc/sys/net/ipv4/tcp_max_orphans和/proc/sys/net/ipv4/tcp_fin_timeout.前者指定内核能接管的孤儿连接数目,后者指定孤儿链接在内核中生存的时间.
time-wait
客户端在接收到服务器的结束报文段之后(报文段6),并没有进入closed状态,二十转移到time_wait状态,在这个状态,客户端要等待2MSL(maximum segment life,报文段最大生存时间)的时间,次啊能完全关闭.
MSL是TCP报文段在网络中最大生存时间,标准文档RFC1122建议值是2min
- 存在原因
- 可靠地终止TCP连接.如果7丢失,那么,服务器将重新发6,因此客户端需要停留在某个状态以处理重复收到的结束报文段(即向服务器发送确认报文段)
- 保证让迟来的tcp报文段有足够的时间被识别并丢弃.
- tcp端口号不饿能被同时打开多次,处于time_wait的端口,无法立即使用.如果能立即建立一个和刚关闭的连接类似的连接(相同ip,端口号),这个新的被称为原来连接的化身(incarnation),新连接,可能收到原来连接的报文段.
- 坚持2MSL的time_wait状态保证网络上两个传输方向尚未被接收到的,迟到的报文段都已经消失.之后一个连接的新的化身就可以在2MSL之后安全地建立.