TCP三次握手和四次挥手
2016-06-04 19:08
671 查看
一、TCP 连接
TCP是一个面向连接的协议,是因为在一个应用进程开始向另一个进程发送数据时,这两个进程必须先建立连接,也称之为“握手”,即它们必须发送些预备报文段,以建立确保数据传输的参数。
TCP连接提供的是全双工服务,也就是单个发送方与单个接收方之间的连接。比如客户端的进程A和服务端的进程B存在一条TCP连接,那么A的数据可以发送到B,B的数据可以发送到A。
二、建立TCP连接(三次握手)
1.
![](http://img.blog.csdn.net/20160604192357952)
TCP三次握手
三次握手步骤如下:
(1)Client的TCP先向Server的TCP发送连接请求。报文段TCP SYN中不含应用层要发送的数据。报文段首部标志位SYN置1。Client随机选择一个初始化序号ISN, 本例为Client_ISN,将此序号放在报文段TCP SYN 32位序号字段中。Client TCP状态: CLOSED ->SYN_SENT。
(2)一旦包含TCP SYN报文段的的IP数据报到达Server(这里假设到达)。Server会从IP数据报提取出TCP SYN报文段,为该TCP连接分配TCP缓存和变量,并向该客户TCP发送允许连接的报文段。在该报文中也不包含应用层数据,SYN被置1,TCP报文段首部32位确认序号字段被置为Client_ISN + 1,并且ACK也被置为1,对Client发来的报文段进行确认。Server也会随机选择一个初始化序号ISN,本例为Server_ISN。该报文段被称为SYN
ACK报文段。Server TCP状态:Client TCP状态: CLOSED -> LISTEN -> SYN_RCVD。
(3)Client收到Server发送的SYN ACK报文段,Client TCP状态: SYN_SENT -> ESTABLISHED。Client也要为TCP连接分配缓存和变量。此时Client只需要确认Server发送的报文段,因此SYN 置0,ACK置1。报文段首部32位确认序号被置为Server_ISN + 1。此时TCP连接建立完成。Server TCP状态:Client TCP状态: SYN_RCVD -> ESTABLISHED.
1. 为什么需要三次握手,不是两次握手?
如果Client发送的请求连接报文段在某种情况下并没有到达Server,报文段并不是丢失了,而是在某个网络节点长时间滞留,以致延误到连接释放才到Server。这个报文段已经失效,但是Server却误认为是Client发送的请求连接报文。如果是两次握手,此时Server向Client发送ACK包,但Client不会理会Server发来的ACK包,也不会向Server发送ACK包。于是Server以为与Client已经建立连接,等待Client发送数据。此时会造成Server资源的浪费,只能等待Client发送数据。也就是说Client的TCP状态一开始为SYN_SENT状态,等待Server
的SYN + ACK 包进入ESTABLISHED状态。当连接失效时,Client处于CLOSED状态,尽管Server发来SYN + ACK包,Client也不会接收Server发来的包。如果在Server收到失效的SYN包,Client通过调用Connect变为SYN_SENT状态,此时接收到Server发送的SYN + ACK包,变为ESTABLISHED状态。但是Client重新调用Connect函数发送的SYN包会被Server丢弃。发送包由于seq是以被丢弃的SYN包的序号为准,而服务器接收序号是以那个延误旧连接SYN报文序号为准。
“三次握手”的目的是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
三、TCP连接终止(四次挥手)
1. 因为TCP是全双工,因此每个方向必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。这利用了TCP的半关闭。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
![](http://img.blog.csdn.net/20160604191438313)
TCP四次挥手
(1)Client执行主动关闭,FIN置1,向Server发送FIN包,FIN序号与SYN序号,也占用TCP首部32位序号字段。用来关闭Client到Server的数据传送。
(2)
Server接收到Client发送的FIN包,ACK置1,将FIN包32位序号Client_FIN + 1,放置到确认序号,向Client发送ACK包。
(3)
Server再向Client发送FIN包,FIN置1。用来关闭Server到Client的数据传送。
(4)
Client收到FIN包,ACK置1,并将FIN序号Server_FIN + 1,放置到确认序号字段。向Server发送ACK包。
1. 四次挥手的TCP状态
(1)FIN_WAIT1 当Client应用层想注动关闭连接,使用套接字通知TCP,让TCP发送一个FIN包给Server,此时Client TCP进入FIN_WAIT1状态,等待Server发送发送FIN包。
(2)CLOSE_WAIT 表示Server等待关闭。如果没有数据发送,向Client发送一个FIN包,关闭连接。如果Server还有数据发送,向Client发送一个ACK包,然后Server进入CLOSE_WAIT状态。
(3)FIN_WAIT2 此时Client收到Server发送的ACK包,此时处于半关闭状态,只有Client向Server主动关闭连接,Server还可以向Client发送数据。
(4)TIME_WAIT 也称为2MSL等待状态。每个具体的TCP实现必须选择一个最大生存时间MSL。它是任何报文段被丢弃前在网络内的最长时间。由于最后Server收到ACK包并不会向Client发送确认报文,因此需要等待一段时间希望Server收到ACK包,如果因为超时没有收到,Server会重发FIN包,此时Client还会发送ACK包。但之前的ACK包会被丢弃。由于处在TIME_WAIT状态下,实际TCP连接已经断开,但socket还会占用当前的端口,并不能被一个新的连接所使用。
2. 为什么TCP连接终止需要四次。
前面说过,三次握手是因为Client与Server都要确认对方发来的请求连接SYN包。并且Server向Client发送SYN + ACK包时,将SYN与ACK放在了同一个报文段。而连接终止时,Client主动关闭连接时,Server先回复ACK包,但可能还有一些数据没有向Client发送完,处于半连接,所以TCP状态 FIN_WAIT1 -> FIN_WAIT2。如果Server发送了FIN包,Server要与Client断开连接。
四、tcpdump获取TCP连接与终止状态
采用socket编写一个Client与Server,采用本地回环地址作为Client的IP。
Client
使用tcpdump观察
1. TCP连接状态
由以上测试结果可以很明显的观察到TCP的三次握手和四次挥手。
总结
1. 在三次握手阶段考虑了为什么不能进行两次握手,对两次握手进行了分析。在两次握手,一定要注意失效的报文段可能并没有丢弃,可能会到达服务端。
2. 四次挥手的时候重点在于为什么是四次。对TIME_WAIT状态的分析,可能还有没有分析到的地方,会在以后找出来。
TCP是一个面向连接的协议,是因为在一个应用进程开始向另一个进程发送数据时,这两个进程必须先建立连接,也称之为“握手”,即它们必须发送些预备报文段,以建立确保数据传输的参数。
TCP连接提供的是全双工服务,也就是单个发送方与单个接收方之间的连接。比如客户端的进程A和服务端的进程B存在一条TCP连接,那么A的数据可以发送到B,B的数据可以发送到A。
二、建立TCP连接(三次握手)
1.
TCP三次握手
三次握手步骤如下:
(1)Client的TCP先向Server的TCP发送连接请求。报文段TCP SYN中不含应用层要发送的数据。报文段首部标志位SYN置1。Client随机选择一个初始化序号ISN, 本例为Client_ISN,将此序号放在报文段TCP SYN 32位序号字段中。Client TCP状态: CLOSED ->SYN_SENT。
(2)一旦包含TCP SYN报文段的的IP数据报到达Server(这里假设到达)。Server会从IP数据报提取出TCP SYN报文段,为该TCP连接分配TCP缓存和变量,并向该客户TCP发送允许连接的报文段。在该报文中也不包含应用层数据,SYN被置1,TCP报文段首部32位确认序号字段被置为Client_ISN + 1,并且ACK也被置为1,对Client发来的报文段进行确认。Server也会随机选择一个初始化序号ISN,本例为Server_ISN。该报文段被称为SYN
ACK报文段。Server TCP状态:Client TCP状态: CLOSED -> LISTEN -> SYN_RCVD。
(3)Client收到Server发送的SYN ACK报文段,Client TCP状态: SYN_SENT -> ESTABLISHED。Client也要为TCP连接分配缓存和变量。此时Client只需要确认Server发送的报文段,因此SYN 置0,ACK置1。报文段首部32位确认序号被置为Server_ISN + 1。此时TCP连接建立完成。Server TCP状态:Client TCP状态: SYN_RCVD -> ESTABLISHED.
1. 为什么需要三次握手,不是两次握手?
如果Client发送的请求连接报文段在某种情况下并没有到达Server,报文段并不是丢失了,而是在某个网络节点长时间滞留,以致延误到连接释放才到Server。这个报文段已经失效,但是Server却误认为是Client发送的请求连接报文。如果是两次握手,此时Server向Client发送ACK包,但Client不会理会Server发来的ACK包,也不会向Server发送ACK包。于是Server以为与Client已经建立连接,等待Client发送数据。此时会造成Server资源的浪费,只能等待Client发送数据。也就是说Client的TCP状态一开始为SYN_SENT状态,等待Server
的SYN + ACK 包进入ESTABLISHED状态。当连接失效时,Client处于CLOSED状态,尽管Server发来SYN + ACK包,Client也不会接收Server发来的包。如果在Server收到失效的SYN包,Client通过调用Connect变为SYN_SENT状态,此时接收到Server发送的SYN + ACK包,变为ESTABLISHED状态。但是Client重新调用Connect函数发送的SYN包会被Server丢弃。发送包由于seq是以被丢弃的SYN包的序号为准,而服务器接收序号是以那个延误旧连接SYN报文序号为准。
“三次握手”的目的是为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
三、TCP连接终止(四次挥手)
1. 因为TCP是全双工,因此每个方向必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。这利用了TCP的半关闭。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
TCP四次挥手
(1)Client执行主动关闭,FIN置1,向Server发送FIN包,FIN序号与SYN序号,也占用TCP首部32位序号字段。用来关闭Client到Server的数据传送。
(2)
Server接收到Client发送的FIN包,ACK置1,将FIN包32位序号Client_FIN + 1,放置到确认序号,向Client发送ACK包。
(3)
Server再向Client发送FIN包,FIN置1。用来关闭Server到Client的数据传送。
(4)
Client收到FIN包,ACK置1,并将FIN序号Server_FIN + 1,放置到确认序号字段。向Server发送ACK包。
1. 四次挥手的TCP状态
(1)FIN_WAIT1 当Client应用层想注动关闭连接,使用套接字通知TCP,让TCP发送一个FIN包给Server,此时Client TCP进入FIN_WAIT1状态,等待Server发送发送FIN包。
(2)CLOSE_WAIT 表示Server等待关闭。如果没有数据发送,向Client发送一个FIN包,关闭连接。如果Server还有数据发送,向Client发送一个ACK包,然后Server进入CLOSE_WAIT状态。
(3)FIN_WAIT2 此时Client收到Server发送的ACK包,此时处于半关闭状态,只有Client向Server主动关闭连接,Server还可以向Client发送数据。
(4)TIME_WAIT 也称为2MSL等待状态。每个具体的TCP实现必须选择一个最大生存时间MSL。它是任何报文段被丢弃前在网络内的最长时间。由于最后Server收到ACK包并不会向Client发送确认报文,因此需要等待一段时间希望Server收到ACK包,如果因为超时没有收到,Server会重发FIN包,此时Client还会发送ACK包。但之前的ACK包会被丢弃。由于处在TIME_WAIT状态下,实际TCP连接已经断开,但socket还会占用当前的端口,并不能被一个新的连接所使用。
2. 为什么TCP连接终止需要四次。
前面说过,三次握手是因为Client与Server都要确认对方发来的请求连接SYN包。并且Server向Client发送SYN + ACK包时,将SYN与ACK放在了同一个报文段。而连接终止时,Client主动关闭连接时,Server先回复ACK包,但可能还有一些数据没有向Client发送完,处于半连接,所以TCP状态 FIN_WAIT1 -> FIN_WAIT2。如果Server发送了FIN包,Server要与Client断开连接。
四、tcpdump获取TCP连接与终止状态
采用socket编写一个Client与Server,采用本地回环地址作为Client的IP。
Client
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <strings.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include "InOut.h" #include "err.h" #define MAXLINE 4096 void err_sys(const char * fmt, ...) __attribute__((noreturn)); void err_quit(const char * fmt, ...) __attribute__((noreturn)); int main(int argc, char ** argv) { struct sockaddr_in ClientAddr; char RecvLine[MAXLINE + 1]; if (argc != 2) err_quit("usage: a.out <IPaddress>"); int ClientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* 定义套接字,使用TCP协议 */ if (ClientSocket < 0) err_sys("socket error"); bzero(&ClientAddr, sizeof(ClientAddr)); struct in_addr * addrptr = &ClientAddr.sin_addr; ClientAddr.sin_family = AF_INET; ClientAddr.sin_port = htons(8888); if (inet_pton(ClientAddr.sin_family, *(argv + 1), addrptr) < 0) err_quit("inet_pton error for %s", argv[1]); if (connect(ClientSocket, (struct sockaddr *)&ClientAddr, sizeof(ClientAddr)) < 0) /* 与服务端建立连接 */ err_sys("connect error"); scanf("%s", RecvLine); if (writen(ClientSocket, RecvLine, strlen(RecvLine)) < 0) /* 向服务端发送数据 */ err_sys("write error"); close(ClientSocket); exit(0); }Server
#include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <strings.h> #include <string.h> #include <arpa/inet.h> #include <unistd.h> #include "InOut.h" #include "err.h" #define MAXLINE 4096 #define LISTENQ 1024 void err_sys(const char * fmt, ...) __attribute__((noreturn)); int main(int argc, char ** argv) { struct sockaddr_in ServerAddr; struct sockaddr_in ClientAddr; char BuffSize[MAXLINE] = {0}; int SizeAddr = sizeof(ClientAddr); int ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* 定义监听套接字,使用TCP协议 */ bzero(&ServerAddr, sizeof(ServerAddr)); /* 套接字结构地址初始化0 */ bzero(&ClientAddr, sizeof(ClientAddr)); ServerAddr.sin_family = AF_INET; ServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); ServerAddr.sin_port = htons(8888); if (bind(ListenSocket, (struct sockaddr *)&ServerAddr, sizeof(ServerAddr)) < 0) /* 将监听套接字与端口,IP进行绑定 */ err_sys("bind error"); listen(ListenSocket, LISTENQ); /* 监听8888端口 */ // for(; ;) { int ConnectSocket = accept(ListenSocket, (struct sockaddr*)&ClientAddr, &SizeAddr); /* 接受客户端的请求连接 */ if (ConnectSocket < 0) err_sys("accept error"); printf("Client ip is %s\n", inet_ntoa(ClientAddr.sin_addr)); printf("Client port is %d\n", ntohs(ClientAddr.sin_port)); while (readn(ConnectSocket, BuffSize, MAXLINE) < 0) { /* 读取客户端发送的数据 */ err_sys("read error"); if (!strcmp(BuffSize, "quit")) break; } // } close(ConnectSocket); /* 关闭TCP连接 */ close(ListenSocket); exit(0); }
使用tcpdump观察
1. TCP连接状态
root@Ubuntu:~# tcpdump -i lo tcp port 8888 and host 127.0.0.1 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes 18:06:25.290805 IP localhost.36340 > localhost.8888: Flags [S], seq 1201714844, win 43690, options [mss 65495,sackOK,TS val 2372902 ecr 0,nop,wscale 7], length 0 18:06:25.290923 IP localhost.8888 > localhost.36340: Flags [S.], seq 2836719335, ack 1201714845, win 43690, options [mss 65495,sackOK,TS val 2372902 ecr 2372902,nop,wscale 7], length 0 18:06:25.290953 IP localhost.36340 > localhost.8888: Flags [.], ack 1, win 342, options [nop,nop,TS val 2372902 ecr 2372902], length 02. Client向Client发送数据
18:06:29.030547 IP localhost.36340 > localhost.8888: Flags [P.], seq 1:5, ack 1, win 342, options [nop,nop,TS val 2373837 ecr 2372902], length 43. TCP终止状态
18:06:29.030657 IP localhost.36340 > localhost.8888: Flags [F.], seq 5, ack 1, win 342, options [nop,nop,TS val 2373837 ecr 2372902], length 0 18:06:29.030715 IP localhost.8888 > localhost.36340: Flags [.], ack 5, win 342, options [nop,nop,TS val 2373837 ecr 2373837], length 0 18:06:29.030775 IP localhost.8888 > localhost.36340: Flags [F.], seq 1, ack 6, win 342, options [nop,nop,TS val 2373837 ecr 2373837], length 0 18:06:29.030788 IP localhost.36340 > localhost.8888: Flags [.], ack 2, win 342, options [nop,nop,TS val 2373837 ecr 2373837], length 0
由以上测试结果可以很明显的观察到TCP的三次握手和四次挥手。
总结
1. 在三次握手阶段考虑了为什么不能进行两次握手,对两次握手进行了分析。在两次握手,一定要注意失效的报文段可能并没有丢弃,可能会到达服务端。
2. 四次挥手的时候重点在于为什么是四次。对TIME_WAIT状态的分析,可能还有没有分析到的地方,会在以后找出来。