TCP 的 三次握手
准备工作:服务器必须准备好接受外来的连接。这通常通过调用socket, bind和listen这三个函数来完成。我们称之为被动打开。 第一次握手:客户端通过调用connect发起主动打开。客户端向服务器发出连接请求的TCP报文段,其TCP首部中的同步比特SYN置为1, 并TCP首部中序号seq设置为x(TCP规定SYN报文段不能携带数据,但是要消耗一个序号),表明要转送数据时初始序列号是x。 通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部,一个TCP首部。 第二次握手:服务器收到数据报后,从TCP数据报首部的同步比特SYN位为1就知道这是一个建立连接的请求。 服务器如果同意,会发回确认。在确认报文段中把同步比特位SYN设置为1,确认比特位ACK设置为1,由于TCP请求报文段中的序号是x, 所以服务器在发送确认报文段中的确认号ack是x + 1,同时把确认报文段中的序号seq设置为y,表明服务器发送数据的初始序列号为y。 该报文段也不能携带数据(因为SYN = 1,所以不携带任何数据)。 第三次握手:客户端收到服务器端的报文段后,要对服务器端中的SYN进行确认。在确认报文段中把确认比特位ACK设置为1, 然后把确认号ack设置为y + 1,自身的序号seq设置x + 1。 注:客户的初始序列号为x, 服务器的初始序列号为y,那么确认报文段中的确认号ack就是所期待的对方要发送的下一个序列号。 客户端调用connect将激发TCP的三次握手,仅在连接建立成功或者出错时才返回,上面介绍了建立成功的情况,下面列出了出错返回可能有的几种情况。 (1)客户端发送的建立连接的SYN报文丢失或者服务器回复的ACK报文丢失。 客户端因为没有收到服务器的回复,会等待6s再发一次,若无响应则等待24s再发一次,总共等75s还未收到响应就返回本错误。 (2)服务器回复的TCP报文中复位标志RST置位1。表示服务器主机没有在指定的端口上监听或接受该连接或服务器程序根本没有运行。 (3)客户端发送的SYN报文不可达。还记IP数据报中有一个生存时间(TTL)的标志吗? 它每进过一个路由器都会减1,当它减到0时还没到达目的地,会发送一个ICMP错误。然后客户端会和第一种情况一样每隔一段时间重发一次。 可以把TCP连接时的三次握手换成两次握手吗?(假设客户端主动,服务器端被动) 假如客户端发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达服务器。 也就是说这是一个早已失效的报文段。但服务器端收到此失效的连接请求报文段后,就误认为是客户端再次发出的一个新的连接请求。 于是就向客户端发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要服务器端发出确认,新的连接就建立了。 由于现在客户端并没有发出建立连接的请求,因此不会理会服务器端的确认。经过三次握手,客户端和服务器都有应有答可以确保TCP正确连接。
TCP 的 四次挥手
数据传输完毕后,通信双方都可以释放连接。对于首先调用close的一端我们称该端为主动关闭,另外一端执行被动关闭。 第一次挥手:假设客户端执行主动关闭,那么它会向服务器端发出释放连接的报文段,这个TCP报文段中终止比特FIN置为1, 序号seq设置为u(假设上一个发的数据序号是u - 1)。并停止发送数据。主动关闭TCP连接。 等待服务器的确认,这里需要注意,因为TCP是全双工的,所以TCP连接上有两条数据通路,发送FIN的一端就不能发送数据, 也就是关闭了其中一条数据通路,对方还是可以继续发送数据。 第二次挥手:服务器端收到客户端的释放连接的报文段后会执行被动关闭,它要对客户端的数据报进行确认, 服务器端会发送一个确认的数据报,确认比特ACK设置为1,确认号为u + 1,自身的序号seq为v(假设上一个发的数据序号是v - 1)。 这个时候TCP处于半关闭状态,服务器依然可以向客户端发送数据(数据的序号为v + 1~ w - 1),客户端任要接受。 第三次挥手:服务器端已经没有要发送给客户端的数据,那么服务器端也会调用close关闭套接字, 这样服务器端也会发送一个FIN的TCP报文段,序号是w(假设上一个发的数据序号是w - 1)。 这个时候服务器端不会再向客户端发送数据了。 第四次挥手:客户端接受到这个最终的FIN的释放连接报文段后必须对报文段进行确认。 在确认的报文段中,ACK = 1, 确认序号ack = w + 1,自己的序号seq = u + 1 (他的上一个序号的数据报就是申请释放连接的数据报,序号是seq = u)。 为什么TCP握手是三次,挥手却是四次?(假设客户端主动,服务器端被动) 在TCP三次握手中,服务器端的SYN和ACK是放在一个TCP报文段中向客户端发送的, 而在断开连接的过程中,服务器端向客户单端发送的ACK和FIN是是分别在两个不同的TCP报文段中。 这是因为在服务器端接收到客户端的FIN后,服务器端可能还有数据要传输, 所以先发送ACK,服务器端把数据发完之后就可以发送FIN断开连接了。