面向连接意味着两个使用TCP的应用在彼此交换数据之前必须先建立一个TCP连接
TCP可靠性:
1、 数据被分割成TCP认为最适合发送的数据块
2、 当TCP发出一个报文段后,它启动一个定时器,等待对端确认。超时重传
3、 TCP收到另一端来的数据,将发送一个确认,不是立即发送,通常会推迟
4、 检测检验和,出错则丢弃且不发送确认,等待重传
5、 对数据进行重新排序, 丢弃重复数据
6、 流量控制,防止缓冲区溢出
TCP首部:
TCP被封装在IP数据报中:
对于TCP首部:
序号用来标识从TCP发端向TCP收端发送数据字节流,表示在这个报文段中的第一个数据字节(SYN和FIN都要消耗一个序号)
确认序号是上次已成功接收到数据字节序号加1。只有ACK标识为1时确认序号才有效。发送ACK无需代价,一旦一个链接建立起来,这个字段总被设置,ACK标识总被置1.
4位首部长决定了TCP首部最多60字节,若没有任何选项,正常长度为20
URG: 紧急指针, ACK : 确认序号, PSH : 接收方应尽快将这个报文段交给应用层
RST: 重建连接, SYN , FIN
TCP检验和,包含首部和数据,是强制的,也使用和UDP类似的伪首部
只有设置了URG标识紧急指针才有效
为什么建立连接要三次握手,而终止连接却需要四次握手?
这是由TCP的半关闭造成的。既然一个TCP连接时全双工的,因此每个方向必须单独进行关闭。即当一方完成了数据传输任务后就可以发送一个FIN来终止这个连接。是使用shutdown函数。
TCP选项,最大报文段长度,MSS
当一个连接建立时,连接的双方都要通告各自的MSS。
如果一方不接受来自另一方的MSS值,则MSS默认为536(这样就允许20字节IP首部和20字节TCP首部适合576字节的IP数据报),对于一个以太网,MSS值可达1460字节
MSS让主机限制另一端发送数据报的长度,加上主机能控制它发送数据报的长度,这将使以较小MTU连接到一个网络上的主机避免分段
但是,如果两段的主机都连接到以太网上,都采用536的MSS,但中间网络采用296的MTU,也将会出现分片,使用路径上的MTU发现机制可以处理这个问题。
TCP状态转换图
关于这里的每个状态:
CLOSED:
这个没什么好说的了,表示初始状态。
LISTEN:
这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_RCVD:
这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
SYN_SENT:
这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
ESTABLISHED:
这个容易理解了,表示连接已经建立了。
FIN_WAIT_1:
这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
FIN_WAIT_2:
FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING:
这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT:
这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK:
这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
TIME_WAIT:
提出主动关闭连接的一端往往会进入TIME_WAIT状态。
TIME_WAIT状态存在的意义是什么呢?
我们知道,四分组终止连接序列中,发起连接中断的一端也是发送最后一个ACK的那段,正因为这样,我们有以下两个解释:
1、本端发出的最后一个ACK,对端不一定接收到。如果这个ACK丢失了,那么对端会重复发出最后一个FIN,因此本端要维持一个状态以保证将来能重复发送最后一个ACK,如果我们不维持这个状态,而是处于CLOSED状态,那么本端将回复RST而不是ACK,另一端则会将此解释为一个错误。
2、允许之前在网络中迷失的分组消逝在网络中。可能会在双方连接的过程中有分组‘走失’了,那么最终它找到目的地址后,发现我们依旧是这个状态在等待它,然后正确的处理它。可是如果我们不是这个状态,而是立刻又开启了一个连接,那么老分组就会被新的连接接收到了,这会引发系列错误。
对于TIME_WAIT状态,我们知道往往是执行主动关闭的一方会进入这个状态。如果我们终止一个客户程序,并立即重启它,那么它将不会重用相同的本地端口,只是这并没关系,因为我们不关心客户使用的哪个端口。
但是对于服务器来说,服务器使用的是熟知端口,重启往往会被禁止,因为端口正处于TIME_WAIT状态。这时候我们可以使用SO_REUSEADDR,尽管如此,TCP还是会禁止一个新的连接建立在一个处于TIME_WAIT状态的五元组(协议、源地址、源端口、目的地址、目的端口)上。
到不存在的端口的连接请求
对于UDP来说,目的端口没有在使用,它将产生一个ICMP不可达信息
对于TCP,则使用复位(RST),例子如下
tcpdump结果如下:
异常终止一个连接(可通过SO_LINGER设置)
有两个优点:
1、 丢弃任何待发数据,立即发送复位
2、 RST的接收方会区分另一端发来的是RST还是正常终止
需要注意的是,RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止连接,通知应用层连接复位
例子如下:(-L 0 的意思就是在发送数据后立即发送RST报文段,而不是FIN)
部分tcpdump结果如下:
服务器端则显示:
检测半打开连接
一方已关闭或异常终止连接,而另一方却不知道。这种情况下,只要不在连接上传输数据,仍处于连接的一方就不会检测到另一方已发生了异常
现在模拟这种情况,在客户服务器连接后,服务器主机拔掉网线,这样就不会发送FIN到客户端去,然后重启,再从客户端发送来数据,查看结果:
Tcpdump观察到:
可以看到,之前维护的连接已不复存在,当客户发来数据后,服务器无法识别,随发送RST报文
同时打开
两个应用程序同时彼此执行打开,发生可能性小
每一端既是服务器又是客户端。
同时打开交换了4个报文段,比正常的三路握手多一个
Tcpdump观察到:
同时关闭
查看上面对CLOSING状态的解释,加上这里的图示,就能理解了
我们也发现双方都进入了TIME_WAIT状态
TCP服务器也可以像UDP服务器一样限制本IP口和远端IP
关于TCP连接请求队列,即Listen函数的第二个参数,这里提一下
1、在TCP监听的套接字连接队列中还有空间,TCP模块将对新来的请求进行SYN确认并完成连接的建立,然而应用层则需要在三次握手结束后才知道这个新连接。
当客户主动打开成功,而服务器应用进程还不知道这个新连接,客户端就认为服务器已经能接收数据了,假设此时客户端立即发送数据,那么服务器的TCP仅将数据放入缓冲队列
2、这其中有个地方,大多TCP/IP协议如此实现,就是如果服务器连接队列未满,TCP将接收传入的连接请求,但并不让应用层了解到该连接源于何处。
这样一来,当一个新连接传递到服务器应用程序时,TCP的三次握手已经结束!客户主动打开已完全成功。此时若应用进程看到该源地址且不打算给其服务,能做的就是发送FIN或RST,无论如何,客户进程都认为一切正常,因为它的主动打开已完成,并已经向服务器发送过请求。