咱们知道Ip层包裹着tcp报文段把它从源Ip运送到目的Ip,若是过程当中出现差错(16位的Ip检验和错误),Ip协议会直接丢弃该数据报而且不生成差错报文。这种状况tcp会发现数据丢失并进行重传。
这篇文章想探讨一下TCP协议是经过什么方式作到这些的,曾经作过设计师的我忍不住抄起老本行画两张图。前端
URG: 紧急指针
ACK: 确认序号有效
PSH: 尽量快地将数据送往接收进程
RST: 重建链接
SYN: 同步序号用来发起一个链接
FIN: 发端完成发送任务
算法
我画了一张图来描述TCP链接的过程和状态变化: 缓存
服务器端默认处于 LISTEN
状态监听着某个端口。当客户端想要链接这个端口的时候,客户端首先会随机生成一个初始序号(图中的j),首部中的32位序号(如下简称序号)就变成j+1,同时首部中的SYN由0置为1。客户端发送完带有这些首部的TCP段后状态变为 SYN_SENT
。服务器
当服务器收到客户端第一个带有SYN的TCP段的时候,客户端由 LISTEN
状态变为 SYN_RCVD
状态,并发送一段数据,其中首部ACK置为1,32位确认号(如下简称确认号)为客户端发过来的序号(j)+1,含义就是客户端的数据已经收到。同时SYN也会置为1,序号为k。含义是我也要与你创建链接。网络
当客户端收到服务器的ACK后,状态变动为 ESTABLISLISHED
,同时发送数据,首部ACK为k+1,含义是服务器的数据已经收到。这时客户端已经创建链接,而服务器端是否能创建链接取决于它可否收到当前客户端发送的这段带有ACK的数据。若是服务器收到的话,它的状态也会变成 ESTABLISLISHED
。
到此,链接创建,能够继续交换数据。前端工程师
在不考虑断电等特殊状况下,主动关闭的一方(图中为客户端但不老是)发送FIN+序号m,状态变动为 FIN_WAIT_1
。并发
被动关闭的一方(图中为服务器但不老是)收到后状态变动为 CLOSE_WAIT
。同时里当即发送确认号m+1,头部ACK置为1,这时服务端状态变动为 LAST_ACK
,很容易理解,这是最后一次确认,客户端收到ACK后,状态变动为 FIN_WAIT_2
。tcp
服务器 LAST_ACK
后还可能继续作其它的操做,作完以后,服务器也会发送FIN+序号n。工具
客户端收到FIN后,状态变动为 TIME_WAIT
,同时发送确认信息ACK n+1,服务器收到ACK后,状态变动为 CLOSE
。
到此,链接断开。优化
挥手的时候有个问题,为何ACK和FIN不能一块儿发送呢?这是由TCP的半关闭(half-close)形成的。TCP链接是全双工(即数据在两个方向上能同时传递),所以每一个方向必须单独地进行关闭。
收到一个FIN只意味着对方不会再向我发送数据了,可是TCP仍然支持我继续向对方发送数据(固然,在实际的应用中,直有不多的程序这样作),若是咱们用Wireshark等工具抓包时也常常会看到挥手只有3次的状况。
TIME_WAIT
状态也称为2倍MSL等待状态。MSL是TCP的最大报文生存时间。当一个TCP主动关闭并发送最后一个ACK(图中右下角最后一条紫色线)后,必须在 TIME_WAIT
状态等待两倍的MSL时间,防止对方没有收到这个ACK而后重发FIN。因此一去一回最多就是两倍时间。
操做系统默认240s(也就是2倍的两分钟)后会关闭处于 TIME_WAIT
的链接,在这以前端口一直会被占用。
在Linux服务器上能够经过变动/etc/sysctl.conf文件去修改该缺省值(秒):net.ipv4.tcp_fin_timeout=30。但这一般也会出现一些问题。
两个应用程序彼此同时打开的可能性很小。这时双方都是客户端也都是服务器。具体流程和通常状况同样,只不过挥手的时候是4次而不是3次。TCP协议认为这种状况是创建一条链接而不是两条。
为了方便我把同时打开和同时关闭画在了一块儿,其实同时打开不必定同时关闭。不一样时打开也有可能同时关闭。
咱们先调用natstat命令看一下telnet服务器。-a:显示全部;-n:以点分十进制表示IP;-f inet:只显示TCP和UDP。首先,没有链接的时候它是这样的:
LISTEN
状态,监听着任意IP发来的请求。下面再看一下当请求过来时的状态:
LISTEN
状态的进程一直存在并接受SYN报文段,一旦握手成功,系统内核中的TCP模块就建立一个处于
ESTABLISHED
状态的进程。
若是服务器在同一时间收到大量请求,而服务器当前没有能力处理(或有更高优先级的进程)。TCP会先将这个请求缓存在一个固定长度的队列中(队列长度通常为5,称为积压值)。
应用层会不断消费该队列,一旦产生积压,服务器会中止接收SYN报文,而且不返回任何消息,这时客户端会显示超时。
一般TCP在接收到数据时并不当即发送ACK;而是推迟发送,以便将ACK与须要沿该方向发送的数据一块儿发送(这种现象称为数据捎带ACK)。绝大多数实现采用的时延为200ms。
200ms是最大时间,若是一直有数据等待发送,那么就不存在延时确认时间。
Nagle算法是为了解决小段问题。所谓“小段”,指的是小于最大MSS尺寸的数据块。例如应用程序一次产生一字节数据(例如交互式输入),这样会致使网络因为太多的包而过载。
Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。规则以下:
一、若是包长度达到MSS,则容许发送;
二、若是该包含有FIN,则容许发送;
三、设置了TCP_NODELAY选项,则容许发送;
四、未设置TCP_CORK选项时,若全部发出去的小包均被确认,则容许发送;
五、上述条件都未知足,但发生了超时(通常为200ms),则当即发送。
Nagle算法的优势是自适应:确认到达的越快,发送的就越快。
Nagle算法的缺点是发送存在延时。咱们能够经过TCP_NODELAY
套接字选项来关闭Nagle算法。
对于前端工程师来说,了解TCP协议可以帮助咱们更好的理解并使用HTTP协议。也可以帮助咱们对网络进行更加细致的优化。个人理解也颇有限,但愿可以和你们共同讨论。