一、三次握手

iOS 三次握手怎么交互 三次握手time_wait_TCP


客户端理解成一个人,服务器端理解成一个人,两个人要用电话通话:

----张三:你好,李四,我是张三 [syn] ,ip,端口

----李四:你好,张三,我是李四 [syn/ack]

----张三:你好,李四 [ack]

a)\客户端给服务器发送 了一个SYN标志位置位的无包体TCP数据包,SYN被置位,就表示发起TCP链接,协议就这么定

b)\服务器收到了这个SYN标志位置位的数据包,服务器给客户端返回一个SYN和ACK标志位都被置位的无包体TCP数据包,协议就这么定的;

c)\客户端收到服务器发送回来的数据包之后,再次发送ACK置位的数据包,服务器端收到这个数据包之后,客户端和服务器端的TCP链接就正式建立;

三次握手很大程度上是为了防止恶意的人坑害别人而引入的一种TCP连接验证机制;
问:就是说为什么是三次而不是两次?
答:
举例
网络诈骗:
----110:客户端 ,你就是服务器
----伪造来电:110
----TCP之所以要三次握手,原因可能很多,但不管多少 原因,都是为了确保数据稳定可靠的收发;
尽量减少伪造数据包对服务器的攻击

位码即tcp标志位,有6种标示:
SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)

举个例子
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

二、四次挥手

iOS 三次握手怎么交互 三次握手time_wait_TCP_02

1.主动关闭方开始关闭后,它的状态ESTABLISHED->FIN_WAIT1,此时主动关闭方的发送端将不再发送数据,即它的写端关闭了,但是读端是还在开启的,能够继续接收数据的。A->B,发完FIN包。
2.被动关闭方接收到A的FIN包后,ESTABLISHED->CLOSE_WAIT,它会将自己的接收端(读端)关闭,但是它的写端是开启的,所以被关闭方是可以继续在发数据出去的。
3.此时被动关闭方需要发送一个ACK包给主动关闭方,同时,被关闭方会通知应用程序,叫它把它里面还未发送完的数据(发送缓冲区的数据)发送完。
4.主动关闭方在接收到被动关闭方的ACK包后,FIN_WAIT1->FIN_WAIT2。在再回到被动关闭方上来,在发送缓冲区的数据被全部发送完之后,被动关闭方还会发送FIN的包给主动关闭方,并且会关闭自己的写端。此时,被动关闭方CLOSE_WAIT->LAST_ACK,在LAST_ACK状态中,被动关闭方会等待主动关闭方的ACK包。
5.主动关闭方收到被动关闭方发送的FIN包之后,会立即关闭自己的读端。状态从FIN_WAIT2->TIME_WAIT。此时主动关闭方还会回应被动关闭方等待的ACK包(4中最后说的)。被动关闭方收到ACK包,状态从LAST_ACK到CLOSED。
6.而主动关闭方在2msl时间之后,状态从TIME_WAIT到CLOSED。
7.可能在最后被动关闭方没有一次接收到主动关闭方的ACK包,那么被动关闭方会重复发FIN包,等待主动关闭方回应ACK包到来,才算真正的结束。

总结:

TCP状态转换图【11种状态】 是 针对“一个TCP连接【一个socket连接】”来说的;

----客户端: CLOSED ->SYN_SENT->ESTABLISHED【连接建立,可以进行数据收发】

----服务端: CLOSED ->LISTEN->【客户端来握手】SYN_RCVD->ESTABLISHED【连接建立,可以进行数据收发】

谁主动close连接,谁就会给对方发送一个FIN标志置位的一个数据包给对方;【服务器端发送FIN包给客户端】

----服务器主动关闭连接:ESTABLISHED->FIN_WAIT1->FIN_WAIT2->TIME_WAIT

----客户端被动关闭:ESTABLISHED->CLOSE_WAIT->LAST_ACK

iOS 三次握手怎么交互 三次握手time_wait_客户端_03

TIME_WAIT:

现象:
具有TIME_WAIT状态的TCP连接,就好像一种残留的信息一样;当这种状态存在的时候,服务器程序退出并重新执行会失败,会提示:
----bind返回的值为-1,错误码为:98,错误信息为:Address already in use
所以,TIME_WAIT状态是一个让人不喜欢的状态;
----四次挥手,主动发起关闭的那一方,就会产生TIME_WAIT状态。
----连接处于TIME_WAIT状态是有时间限制的(1-4分钟之间) = 2 MSL【最长数据包生命周期】;

引入TIME_WAIT状态【并且处于这种状态的时间为1-4分钟】 的原因:
可靠的实现TCP全双工的终止
---- 如果客户端最后发送的ACK【应答】包因为某种原因丢失了,那么服务器一定会重新发送FIN,这样因为客户端有TIME_WAIT的存在,客户端会重新发送ACK包给服务器,
---- 但是 如果没有TIME_WAIT这个状态 ,那么无论服务器有没有收到ACK包,客户端都已经关闭连接了,此时服务器重新发送FIN,客户端给回的就不是ACK包,而是 RST【连接复位】包 ,从而没有完成正常的4次挥手,不友好,而且有可能造成数据包丢失;也就是说,TIME_WAIT有助于可靠的实现TCP全双工连接的终止;

FIN【四次挥手】,是个优雅的关闭标志,表示正常的TCP连接关闭;
反观RST标志: 连接复位包,出现这个标志的包一般都表示 异常关闭;如果发生了异常,一般都会导致丢失一些数据包;如果将来用 setsockopt(SO_LINGER)选项要是开启;发送的就是RST包,此时发送缓冲区的数据会被丢弃;RST是异常关闭,是粗暴关闭,不是正常的四次挥手关闭,所以如果你这么关闭tcp连接,那么主动关闭一方也不会进入TIME_WAIT;

解决TIME_WAIT状态引发的问题

SO_REUSEADDR:主要解决TIME_WAIT状态导致 bind()失败的问题;

函数setsockopt(SO_REUSEADDR)用在服务器端,socket()创建之后,bind()之前
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR, (const void *) &reuseaddr,sizeof(reuseaddr))

SO_REUSEADDR的能力:
(1)SO_REUSEADDR允许启动一个监听服务器并捆绑其端口,即使以前建立的将端口用作他们的本地端口的连接仍旧存在;【即便TIME_WAIT状态存在,服务器 bind()也能成功】**这里
(2)允许同一个端口上启动同一个服务器的多个实例,只要每个实例捆绑一个不同的本地IP地址即可;
(3)SO_REUSEADDR允许单个进程捆绑同一个端口到多个套接字,只要每次捆绑指定不同的本地IP地址即可;
(4)SO_REUSEADDR允许完全重复的绑定:当一个IP地址和端口已经绑定到某个套接字上时,如果传输协议支持,同样的IP地址和端口还可以绑定到另一个套接字上;一般来说本特性仅支持UDP套接字[TCP不行];开两个TCP服务器失败。
****所有TCP服务器都应该指定本套接字选项,以防止当套接字处于TIME_WAIT时 bind()失败的情形出现;