TCP报文格式
一、6个控制位中 URG和PSH的区别:
(1)紧急URG(URGent), 当URG=1时, 表明紧急指针字段有效。它告诉操作系统此报文中有紧急数据,应尽快传输(相当于高优先级数据),而不要按照原来的排队顺序来传送。例如,已经发送了很长的一个程序要在远地的主机上运行,但后来发现了一些问题,需要取消该程序的运行。因为用户从键盘发送中断指令(Control+c),如果不使用紧急数据,那么两个字符将存储在接收TCP的缓存末尾。只有在所有的数据被处理完毕后,这两个字符才会被交付到接收方的应用进程。这样做浪费了很长时间。
当URG置1时,发送应用进程应该告诉对方发送方的TCP有紧急数据要传送。于是发送方TCP就将紧急数据插入到本报文段数据的最前面,而在紧急数据后面的数据仍然是普通数据。这时要与首部中的紧急指针字段配合使用。
(2)推送PSH(PuSH) 当两个进程在进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能够收到对方的响应。在这种情况下,TCP就可以使用推送(push)操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快的(即”推送“向前)交付给接收方应用进程,而不再等到整个接收缓存都填满了后再向上交付。
区别:
1.URG表示不进入缓存buffer直接上交应用application;
2.PSH表示每一个bit都独立成包(TCP基于流需要等待后续输入)进行传输,不需要等待后续字符。如telnet。TCP在收到PSH置位的报文后,不管后续再有无报文,先将缓存中的提交给应用程序进程。
二、3次握手
1、3次握手过程
TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答 SYN+ACK[1] ,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。
TCP三次握手的过程如下:
(1) 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
(2) 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
(3) 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。 服务端收到ACK后,也进入Established状态。
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。
握手 和 挥手 是需要一方 主动来发起建立的
2、为什么 是3次 握手? 为什么不是2次,或4次?
原因: 在网络发送中,有一个原则:对当前的自己发送的消息,自己是 不确认对方是否接收到,但可以确认的是 ,当自己接收到对方发的消息后, 自己是可以确认,自己之前发送的消息对方是收到的。
握手 是 双方互相确认 对方已经收到自己的消息 所以至少要3次
2次的话 如
client向server发送了消息, client接收到了server的消息 但是 只有client可以确认, 他发送的消息1server接收到了 ,但server发送的消息他是不确认client收到了
例如:
场景1:(消息在中途丢失)【假设两次握手】client 向 server 发送 一个请求 ,server回复, 但是 这次回复在中途丢失了。server认为这次的握手成功了。 但是 client 认为失败了,所以client重新发送握手新的连接,但是 server认为连接已经建立,他在等client的消息而不是client 的连接请求, client收不到请求的回复,不断的发请求。造成死锁。
场景2:(消息滞后到达)【假设两次握手】
正常情况:clinet发送一个请求连接丢失,请求server没收到,client又发送了一个连接请求,连接建立,数据传送完,连接释放。这个过程中client共发送了2个连接请求。
异常情况:client发送的第一个请求连接报文段并没有丢失,而是因为网络的问题滞留了,而是在连接已经释放后的某个点到达server,而server以为是client发送的一个请求连接报文,于是向client发送一个确认报文,server认为连接已经建立,而client并没有向server发送请求建立连接,就不会理会server发来的确认。而server则在一直等待client发送报文,将会引起死锁。若采用三次握手,就上述情况,client不会向server的确认发出确认,当server没有收到client的确认,就认为client并没有要求建立连接
总结:其实4次5次6次...握手都是可以的, 只要3次以及3次以上, 双方都能确认对方收到了自己发送的消息。
3次握手 A多一次确认是为了防止已经失效的请求报文段由重新传送到B, 产生错误【场景2】
三、4次挥手过程
1、建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。具体过程如下图所示。[1]
TCP连接的终止
(1) 某个应用进程首先调用close,称该端执行“主动关闭”(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕。
(2) 接收到这个FIN的对端执行 “被动关闭”(passive close),这个FIN由TCP确认。
注意:FIN的接收也作为一个文件结束符(end-of-file)传递给接收端应用进程,放在已排队等候该应用进程接收的任何其他数据之后,因为,FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收。
(3) 一段时间后,接收到这个文件结束符的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN。
(4) 接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN。
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。
注意:
(1) “通常”是指,某些情况下,步骤1的FIN随数据一起发送,另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。
(2) 在步骤2与步骤3之间,从执行被动关闭一端到执行主动关闭一端流动数据是可能的,这称为“半关闭”(half-close)。
此时,client到server的连接已经被释放,client不能向server发送数据,但是server到client的连接并没有释放。它还可以继续发送。这种状态称为"半关闭"状态。这种状态将持续一段时间。client在收到server发送的ACK报文之后进入"FIN-WAIT-2"状态,server进入"CLOSE-WAIT"状态。
(3) 当一个Unix进程无论自愿地(调用exit或从main函数返回)还是非自愿地(收到一个终止本进程的信号)终止时,所有打开的描述符都被关闭,这也导致仍然打开的任何TCP连接上也发出一个FIN。
无论是客户还是服务器,任何一端都可以执行主动关闭。通常情况是,客户执行主动关闭,但是某些协议,例如,HTTP/1.0却由服务器执行主动关闭。
2、 为什么4次挥手?
client确认自己发送的“我要断开的消息”server已经收到 (两次)
server确认自己发送的“我要断开的消息”client已经收到 (两次)
四、TIME_WAIT状态
1、TIME_WAIT状态是四次挥手中服务器向客户端发送FIN终止连接后进入的状态,TIME_WAIT状态是主动关闭一方特有的状态【下面以客户端主动关闭 服务器被动关闭为例】。
可以看到TIME_WAIT状态存在于客户端收到服务器FIN并返回ACK时的状态。
当处于TIME_WAIT状态时,我们无法创建新的连接,因为端口被占用。
2、 TIME_WAIT状态作用
原因如下两点:
(1) 可靠的终止TCP连接
若处于TIME_WAIT的客户端发送给服务器确认报文段丢失的话,服务器将在此重新发送FIN报文 段,那么客户端必须处于一个可接收的状态就是TIME_WAIT而不是close状态。
(2) 保证迟来的TCP报文段有足够的时间被识别并丢弃
Linux中有一个TCP端口不能打开两次或者两次以上,当客户端处于TIME_WAIT状态时,我们将无 法使用此端口建立新连接,如果不存在TIME_WAIT状态,新连接可能会收到旧连接的数据。 TIME_WAIT持续时间是2MSL(MSL最长报文寿命),保证旧的数据可以丢弃,因为网络中数据最大存 在MSL。
3、 如何避免TIME_WAIT状态占用资源?
处于TIME_WAIT状态的连接占用的资源不会被内核释放。
当TCP处于TIME_WAIT时,必须等待2个MSL超时才能收回端口,一旦TCP的连接服务器异常退出,没有释放端口号,就会出现bind error:Address already in use。如何解决?
问题描述:服务器与 客户端连接后 马上 kill掉服务器 然后又启动服务器 出现 Address already in use的错误 linux中等上半分钟 就可以启动服务器了
[bozi@localhost tcp]$ ./server_1_simple bind and listen success, wait accept... get connect, ip is :13.0.0.0 port is : 1032 ^C [bozi@localhost tcp]$ ./server_1_simple bind error, error code is : 98, errno string is :Address already in use
解决方法:
通过socksetopt设置socket描述符的选项SO_REUSEADDR为1,即使sock处于time_wait状态,与之绑定的socket地址也可以立即被重用。
五、端口号
TCP与UDP段结构中端口地址都是16比特,可以有在0---65535范围内的端口号。对于这65536个端口号有以下的分类:
(1)众所周知的端口号为0-1023.这些端口号有IANA分配和控制。可能时,相同的端口号就分配给TCP和UDP的同一给定的服务。例如80是TCP和UDP协议 Web服务器使用
(2)经注册的端口为1024-49151 ,这些端口不受IANA控制,但由IANA登记并提供他们的使用情况清单,以方便整个群体。可能时,相同的端口号就分配给TCP和UDP的同一给定的服务。
(3)49152-65535是动态的或私有的端口。IANA不管这些端口,他们就是我们平时所说的临时端口。
注意:Unix系统有保留端口的概念,他是小于1024的任意端口,只能分配给超级用户进程的套接字。所以,众所周知的端口都为保留端口,这些端口的服务器启动时必须有管理员的特权。