TCP/IP
Posted 大寒123
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP/IP相关的知识,希望对你有一定的参考价值。
建立连接:
理解:窗口和滑动窗口
TCP的流量控制
TCP使用窗口机制进行流量控制
什么是窗口?
连接建立时,各端分配一块缓冲区用来存储接收的数据,并将缓冲区的尺寸发送给另一端
接收方发送的确认信息中包含了自己剩余的缓冲区尺寸
剩余缓冲区空间的数量叫做窗口
2. TCP的流控过程(滑动窗口)
TCP(Transmission Control Protocol) 传输控制协议
三次握手
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:
位码即tcp标志位,有6种标示:
SYN(synchronous建立联机)
ACK(acknowledgement 确认)
PSH(push传送)
FIN(finish结束)
RST(reset重置)
URG(urgent紧急)
Sequence number(顺序号码)
Acknowledge number(确认号码)
客户端TCP状态迁移:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服务器TCP状态迁移:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
各个状态的意义如下:
LISTEN - 侦听来自远方TCP端口的连接请求;
SYN-SENT -在发送连接请求后等待匹配的连接请求;
SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;
ESTABLISHED- 代表一个打开的连接,数据可以传送给用户;
FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;
FIN-WAIT-2 - 从远程TCP等待连接中断请求;
CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
CLOSING -等待远程TCP对连接中断的确认;
LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;
TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认;
CLOSED - 没有任何连接状态;
TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接,如图1所示。
(1)第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。
(2)第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。
(3)第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。
确认号:其数值等于发送方的 发送序号 +1 (即接收方期望接收的下一个序列号)。
图1 TCP三次握手建立连接
TCP的包头结构:
源端口 16位
目标端口 16位
序列号 32位
回应序号 32位
TCP头长度 4位
reserved 6位
控制代码 6位
窗口大小 16位
偏移量 16位
校验和 16位
选项 32位(可选)
这样我们得出了TCP包头的最小长度,为20字节
第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。
第二次握手:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。
第三次握手.
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1
下面是具体的例子截图:
1. 此图包含两部分信息: TCP 的三次握手 ( 方框中的内容) ( SYN, (SYN+ACK), ACK)
- TCP 的数据传输 ( [TCP segment of a reassembled PUD]) 可以看出, server 是将数据 TCP 层对消息包进行分片传输
(1)Server 端收到 HTTP 请求如 GET 之后,构造响应消息,其中携带网页内容,在 server 端的 HTTP 层发送消息 200 OK->server 端的 TCP 层;
(2)server 端的 TCP 层对消息包进行分片传输;
(3)client 端的 TCP 层对接收到的各个消息包分片回送响应;
(4)client 端的 TCP 层每次收到一部分都会用 ACK 确认,之后 server 继续传输, client 继续确认,直到完成响应消息的所有分片之后, Server 发送组合 HTTP 响应包 200 O K ,此时在 client 端的消息跟踪中才可以显示 HTTP 200 OK 的消息包
关闭连接:
由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
CP 的连接的拆除需要发送四个包,因此称为四次挥手 (four-way handshake) 。客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。
( 1 )客户端 A 发送一个 FIN ,用来关闭客户 A 到服务器 B 的数据传送。
( 2 )服务器 B 收到这个 FIN ,它发回一个 ACK ,确认序号为收到的序号加 1 。和 SYN 一样,一个 FIN 将占用一个序号。
( 3 )服务器 B 关闭与客户端 A 的连接,发送一个 FIN 给客户端 A 。
( 4 )客户端 A 发回 ACK 报文确认,并将确认序号设置为收到序号加 1 。
TCP 采用四次挥手关闭连接如图 2 所示。
图2 TCP四次挥手关闭连接
参见wireshark抓包,实测的抓包结果并没有严格按挥手时序。我估计是时间间隔太短造成。
深入理解TCP连接的释放:
由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
TCP 协议的连接是全双工连接,一个 TCP 连接存在双向的读写通道。
简单说来是 “ 先关读,后关写 ” ,一共需要四个阶段。以客户机发起关闭连接为例:
- 服务器读通道关闭
- 客户机写通道关闭
- 客户机读通道关闭
- 服务器写通道关闭
关闭行为是在发起方数据发送完毕之后,给对方发出一个 FIN ( finish )数据段。直到接收到对方发送的 FIN ,且对方收到了接收确认 ACK 之后,双方的数据通信完全结束,过程中每次接收都需要返回确认数据段 ACK 。
详细过程:
第一阶段 客户机发送完数据之后,向服务器发送一个 FIN 数据段,序列号为 i ;
服务器收到 FIN(i) 后,返回确认段 ACK ,序列号为 i+1 ,关闭服务器读通道;
客户机收到 ACK(i+1) 后,关闭客户机写通道;
(此时,客户机仍能通过读通道读取服务器的数据,服务器仍能通过写通道写数据)
第二阶段 服务器发送完数据之后,向客户机发送一个 FIN 数据段,序列号为 j ;客户机收到 FIN(j) 后,返回确认段 ACK ,序列号为 j+1 ,关闭客户机读通道;
服务器收到 ACK(j+1) 后,关闭服务器写通道。
这是标准的 TCP 关闭两个阶段,服务器和客户机都可以发起关闭,完全对称。
FIN 标识是通过发送最后一块数据时设置的,标准的例子中,服务器还在发送数据,所以要等到发送完的时候,设置 FIN (此时可称为 TCP 连接处于半关闭状态,因为数据仍可从被动关闭一方向主动关闭方传送)。如果在服务器收到 FIN(i) 时,已经没有数据需要发送,可以在返回 ACK(i+1) 的时候就设置 FIN(j) 标识,这样就相当于可以合并第二步和第三步。读《 Linux 网络编程》关闭 TCP 连接章节,作以下笔记:
TCP 的 TIME_WAIT 和 Close_Wait 状态
面试时看到应聘者简历中写精通网络, TCP 编程,我常问一个问题, TCP 建立连接需要几次握手? 95% 以上的应聘者都能答对是 3 次。问 TCP 断开连接需要几次握手, 70% 的应聘者能答对是 4 次通讯。再问 CLOSE_WAIT , TIME_WAIT 是什么状态,怎么产生的,对服务有什么影响,如何消除?有一部分同学就回答不上来。不是我扣细节,而是在通讯为主的前端服务器上,必须有能力处理各种 TCP 状态。比如统计在本厂的一台前端机上高峰时间 TCP 连接的情况,统计命令:
netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’
结果:
除了ESTABLISHED,可以看到连接数比较多的几个状态是:FIN_WAIT1, TIME_WAIT, CLOSE_WAIT, SYN_RECV和LAST_ACK;下面的文章就这几个状态的产生条件、对系统的影响以及处理方式进行简单描述。
TCP状态
TCP状态如下图所示:
可能有点眼花缭乱?再看看这个时序图
下面看下大家一般比较关心的三种 TCP 状态
SYN_RECV
服务端收到建立连接的 SYN 没有收到 ACK 包的时候处在 SYN_RECV 状态。有两个相关系统配置:
1 , net.ipv4.tcp_synack_retries : INTEGER
默认值是 5
对于远端的连接请求 SYN ,内核会发送 SYN + ACK 数据报,以确认收到上一个 SYN 连接请求包。这是所谓的三次握手 ( threeway handshake) 机制的第二个步骤。这里决定内核在放弃连接之前所送出的 SYN+ACK 数目。不应该大于 255 ,默认值是 5 ,对应于 180 秒左右时间。通常我们不对这个值进行修改,因为我们希望 TCP 连接不要因为偶尔的丢包而无法建立。
2 , net.ipv4.tcp_syncookies
一般服务器都会设置 net.ipv4.tcp_syncookies=1 来防止 SYN Flood 攻击。假设一个用户向服务器发送了 SYN 报文后突然死机或掉线,那么服务器在发出 SYN+ACK 应答报文后是无法收到客户端的 ACK 报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送 SYN+ACK 给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为 SYN Timeout ,一般来说这个时间是分钟的数量级(大约为 30 秒 -2 分钟)。
这些处在 SYNC_RECV 的 TCP 连接称为半连接,并存储在内核的半连接队列中,在内核收到对端发送的 ack 包时会查找半连接队列,并将符合的 requst_sock 信息存储到完成三次握手的连接的队列中,然后删除此半连接。大量 SYNC_RECV 的 TCP 连接会导致半连接队列溢出,这样后续的连接建立请求会被内核直接丢弃,这就是 SYN Flood 攻击。
能够有效防范 SYN Flood 攻击的手段之一,就是 SYN Cookie 。 SYN Cookie 原理由 D. J. Bernstain 和 Eric Schenk 发明。 SYN Cookie 是对 TCP 服务器端的三次握手协议作一些修改,专门用来防范 SYN Flood 攻击的一种手段。它的原理是,在 TCP 服务器收到 TCP SYN 包并返回 TCP SYN+ACK 包时,不分配一个专门的数据区,而是根据这个 SYN 包计算出一个 cookie 值。在收到 TCP ACK 包时, TCP 服务器在根据那个 cookie 值检查这个 TCP ACK 包的合法性。如果合法,再分配专门的数据区进行处理未来的 TCP 连接。
观测服务上 SYN_RECV 连接个数为: 7314 ,对于一个高并发连接的通讯服务器,这个数字比较正常。
CLOSE_WAIT
发起 TCP 连接关闭的一方称为 client ,被动关闭的一方称为 server 。被动关闭的 server 收到 FIN 后,但未发出 ACK 的 TCP 状态是 CLOSE_WAIT 。出现这种状况一般都是由于 server 端代码的问题,如果你的服务器上出现大量 CLOSE_WAIT ,应该要考虑检查代码。
TIME_WAIT
根据 TCP 协议定义的 3 次握手断开连接规定 , 发起 socket 主动关闭的一方 socket 将进入 TIME_WAIT 状态。 TIME_WAIT 状态将持续 2 个 MSL(Max Segment Lifetime), 在 Windows 下默认为 4 分钟,即 240 秒。 TIME_WAIT 状态下的 socket 不能被回收使用 . 具体现象是对于一个处理大量短连接的服务器 , 如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于 TIME_WAIT 状态的 socket , 甚至比处于 Established 状态下的 socket 多的多 , 严重影响服务器的处理能力,甚至耗尽可用的 socket ,停止服务。
为什么需要 TIME_WAIT ? TIME_WAIT 是 TCP 协议用以保证被重新分配的 socket 不会受到之前残留的延迟重发报文影响的机制 , 是必要的逻辑保证。
和 TIME_WAIT 状态有关的系统参数有一般由 3 个,本厂设置如下:
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_fin_timeout ,默认 60s ,减小 fin_timeout ,减少 TIME_WAIT 连接数量。
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将 TIME-WAIT sockets 重新用于新的 TCP 连接,默认为 0 ,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启 TCP 连接中 TIME-WAIT sockets 的快速回收,默认为 0 ,表示关闭。
为了方便描述,我给这个 TCP 连接的一端起名为 Client ,给另外一端起名为 Server 。上图描述的是 Client 主动关闭的过程, FTP 协议中就这样的。如果要描述 Server 主动关闭的过程,只要交换描述过程中的 Server 和 Client 就可以了, HTTP 协议就是这样的。
描述过程:
Client 调用 close() 函数,给 Server 发送 FIN ,请求关闭连接; Server 收到 FIN 之后给 Client 返回确认 ACK ,同时关闭读通道(不清楚就去看一下 shutdown 和 close 的差别),也就是说现在不能再从这个连接上读取东西,现在 read 返回 0 。此时 Server 的 TCP 状态转化为 CLOSE_WAIT 状态。
Client 收到对自己的 FIN 确认后,关闭 写通道,不再向连接中写入任何数据。
接下来 Server 调用 close() 来关闭连接,给 Client 发送 FIN , Client 收到后给 Server 回复 ACK 确认,同时 Client 关闭读通道,进入 TIME_WAIT 状态。
Server 接收到 Client 对自己的 FIN 的确认 ACK ,关闭写通道, TCP 连接转化为 CLOSED ,也就是关闭连接。
Client 在 TIME_WAIT 状态下要等待最大数据段生存期的两倍,然后才进入 CLOSED 状态, TCP 协议关闭连接过程彻底结束。
以上就是 TCP 协议关闭连接的过程,现在说一下 TIME_WAIT 状态。
从上面可以看到,主动发起关闭连接的操作的一方将达到 TIME_WAIT 状态,而且这个状态要保持 Maximum Segment Lifetime 的两倍时间。为什么要这样做而不是直接进入 CLOSED 状态?
原因有二:
一、保证 TCP 协议的全双工连接能够可靠关闭
二、保证这次连接的重复数据段从网络中消失
先说第一点,如果 Client 直接 CLOSED 了,那么由于 IP 协议的不可靠性或者是其它网络原因,导致 Server 没有收到 Client 最后回复的 ACK 。那么 Server 就会在超时之后继续发送 FIN ,此时由于 Client 已经 CLOSED 了,就找不到与重发的 FIN 对应的连接,最后 Server 就会收到 RST 而不是 ACK , Server 就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致 TCP 协议不符合可靠连接的要求。所以, Client 不是直接进入 CLOSED ,而是要保持 TIME_WAIT ,当再次收到 FIN 的时候,能够保证对方收到 ACK ,最后正确的关闭连接。
再说第二点,如果 Client 直接 CLOSED ,然后又再向 Server 发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达 Server ,由于新连接和老连接的端口号是一样的,又因为 TCP 协议判断不同连接的依据是 socket pair ,于是, TCP 协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以 TCP 连接还要在 TIME_WAIT 状态等待 2 倍 MSL ,这样可以保证本次连接的所有数据都从网络中消失。
各种协议都是前人千锤百炼后得到的标准,规范。从细节中都能感受到精巧和严谨。每次深入都有同一个感觉,精妙。
以上是关于TCP/IP的主要内容,如果未能解决你的问题,请参考以下文章