TCP三次握手,数据传输,四次挥手
- TCP包结构
一个TCP包结构如下:
一个TCP包主要由TCP包头和数据部分组成,包头固定部分为20字节,选项和数据部分根据实际情况设置为4N(N可以为0)字节。
1.16bit源端口和目的端口号,它可以确认数据的传输方向(暂不考虑更底层的包)
2.32bit序号,它是为TCP包中数据部分进行编号的部分。假设要发送的数据有100M,由于受MSS( Maximum Segment Size 最大报文段长度)限制,一个TCP包是不可能传输完这100M的数据,于是需要将数据拆分,为了确保拆分传输后的数据能在接收端正确的拼接,就需要对每个拆分的数据包进行编号来传输。这样,这个32位的序号指的就是本包数据部分第一个字节是这个100M数据中的第多少个字节。例如:假设发送第一个包时,先取出这100M数据的前面1024个字节发送,这时这个包中32位序号就是1,然后取下一个1024字节传输,这个时候的数据部分的第一个字节是这100M数据的第1025个字节,所以这第二TCP包中32位的序列号就应该为1025。当序号超过2^32时,进行一个轮回,重新从0开始计数。
3.32bit确认序号,和上面的32位序号类似,只不过它指的是期望收到的下一个包的数据部分的编号。
4.4bit首部长度,单位为4字节,指的是一个TCP包中除去数据部分的长度,也就是包头固定部分+选项部分的长度,2^4 -1 = 15, 15*4字节=60字节,即包头固定部分为20字节,选项最多可以为40字节。
5.标识位:
URG:.........
ACK:TCP包的Acknowledgement number有效位,1时表明Acknowledgement number有效,0表示Acknowledgement number无效,忽略Acknowledgement number字段。自tcp连接建立后,ACK必须为1。
PSH:TCP包中有数据需要尽快传递给应用层使用,而不是将数据进行缓冲,等到缓冲区满了再投递给应用层。
RST:..........
SYN:TCP包的同步位
FIN:表示这个方向的带数据的包传输已经完成,即发送FIN包的端没有带数据的包过来了,需要释放这个方向的连接。
- TCP三次握手
TCP三次握手过程:
测试代码,server端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <string.h> 3 #include <sys/types.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 7 #define ECHO "yes,i see you!" 8 9 int main ( int argc, char *argv[] ) 10 { 11 int ret,rn; 12 int socketfd,acfd; 13 int socklen; 14 char buf[1024]; 15 16 struct sockaddr_in hostaddr; 17 struct sockaddr_in clientaddr; 18 19 socketfd = socket(AF_INET, SOCK_STREAM, 0); 20 if ( socketfd < 0 ) 21 { 22 perror("socket"); 23 return -1; 24 } 25 26 memset((void *)&hostaddr, 0, sizeof(hostaddr)); 27 hostaddr.sin_family = AF_INET; 28 hostaddr.sin_port = htons(6666); 29 hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); 30 31 ret = bind(socketfd, (struct sockaddr *)&hostaddr, sizeof(hostaddr)); 32 if ( ret < 0 ) 33 { 34 perror("bind"); 35 close(socketfd); 36 return -1; 37 } 38 39 ret = listen(socketfd, 5); 40 if ( ret < 0 ) 41 { 42 perror("listen"); 43 close(socketfd); 44 return -1; 45 } 46 47 socklen = sizeof(struct sockaddr); 48 acfd = accept(socketfd, (struct sockaddr *)&clientaddr, &socklen); 49 if ( acfd < 0 ) 50 { 51 perror("accept"); 52 close(socketfd); 53 return -1; 54 } 55 56 while (1) 57 { 58 memset(buf, 0x0, sizeof(buf)); 59 rn = read(acfd, buf, sizeof(buf)); 60 printf("%s\n",buf); 61 } 62 //write(acfd, ECHO, sizeof(ECHO)); 63 64 close(acfd); 65 close(socketfd); 66 67 return 0; 68 }
client端:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #include <sys/types.h> 5 #include <sys/socket.h> 6 #include <netinet/in.h> 7 8 #define HI "hi,can you see me?" 9 10 int main ( int argc, char *argv[] ) 11 { 12 int ret; 13 int socketfd,acfd; 14 int socklen; 15 struct sockaddr_in hostaddr; 16 char buf[1024]; 17 18 socketfd = socket(AF_INET, SOCK_STREAM, 0); 19 if ( socketfd < 0 ) 20 { 21 perror("socket"); 22 return -1; 23 } 24 25 memset((void *)&hostaddr, 0, sizeof(hostaddr)); 26 hostaddr.sin_family = AF_INET; 27 hostaddr.sin_port = htons(6666); 28 hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); 29 30 socklen = sizeof(struct sockaddr); 31 acfd = connect(socketfd, (struct sockaddr *)&hostaddr, sizeof(hostaddr)); 32 if ( acfd < 0 ) 33 { 34 perror("connect"); 35 close(socketfd); 36 return -1; 37 } 38 39 write(socketfd, HI, sizeof(HI)); 40 41 //memset(buf, 0x0, sizeof(buf)); 42 //read(socketfd, buf, sizeof(buf)); 43 //printf("%s\n",buf); 44 45 close(socketfd); 46 return 0; 47 }
这是CS间一对一的连接方式,各自完成一次收发。先运行server端,在accept下一行下断点,再运行client端,在connet的下一行下断点,使用wireshark抓包,过滤端口为6666的tcp包 tcp.port == 6666 ,运行,再断点处停下来时,可以看到抓到了3个包:
这就是TCP三次握手的3个包,TCP的握手是发生在client端进行connect时。
对于握手包的Sequence number和Acknowledgment number我理解为三次握手的包序。这里有RFC 793的解释:
Sequence Number: 32 bits The sequence number of the first data octet in this segment (except when SYN is present). If SYN is present the sequence number is the initial sequence number (ISN) and the first data octet is ISN+1.
Acknowledgment Number: 32 bits If the ACK control bit is set this field contains the value of the next sequence number the sender of the segment is expecting to receive. Once a connection is established this is always sent.
第一次握手:
client:"server,我要找你了!(SYN == 1),这是我第一次和你说话(Seq num == 0)" ,TCP包如下:
可以看到Source port:33335,Destination port:6666,说明是C--->S这个方向传输的,Sequence为0,SYN被置位,没有数据部分。
第二次握手:
server:"client我听到你叫我了(ACK = = 1),你听到我的回应了吗(SYN == 1)?,这是我第一次和你说话(Seq num == 0),我等你第二次和我说话(Ack num == 1)",TCP包如下:
可以看到Source port:6666,Destination port:33335,说明是S--->C这个方向传输的,Sequence num为0,Ack num为 1,SYN,ACK被置位,没有数据部分。
第三次握手:
clinet:"server,知道你听到我了(ACK == 1),我们可以开聊了,这是我第二次和你说话(Seq num == 1),我等你和我第二次说话(Ack num == 1)"
可以看到Source port:33335,Destination port:6666,说明是C--->S这个方向传输的,Sequence num为1,Ack num为 1,ACK被置位,没有数据部分。
- 为什么要三次握手
结合网上搜索,我觉得这是由于TCP是可靠的全双工的传输协议。首先,为什么要握手?因为需要确保可靠。为什么要3次,因为3次是理论上确保全双工握手成功的最少次数。对于单工,确保一次通信可以需要:1发送方把数据发送出去(SYN),2接收方回应发送方接收成功(ACK),这样形成了一个闭环。看看三次握手怎么做的:
第一次: CLIENT----syn----->SERVER
第二次: SERVER----ack,syn---->CLIENT
第三次: CLIENT----ack----->SERVER
这样,从client---->server, server---->client各自通过一次syn,ack形成闭环,理论上形成了一个近似可靠的通信。为什么说是近似可靠?因为完全可靠的通信是不存在的,见谢希仁<<计算机网络>>里面讲的红团蓝团问题。
- TCP数据的传输
握手成功后,开始传输第一个分节的数据,一次数据的传输需要2个TCP包。数据发送包,接收方应答包。
1.Client---data--->Server:
2.Server---ack--->Client
- TCP的四次挥手
四次挥手的时序
一般来说,网络编程的CS模型中,服务端可以认为是一直不退出的,并且它是被动的一放,因此一般需要断开连接都是由客户端主动提出。
类似于TCP的三次握手,四次挥手的四步:
Client---fin,ack--->Server
Server---ack---->Clinet
Server---fin,ack--->Client
Client----ack----->Server
先把2次发送FIN的包中的ACK(未着色的)忽略,因为TCP建立连接后ACK必须置位。类似于三次握手的分析,一对FIN--ACK包的往来,形成一个闭环,保证了单侧通信的可靠。对比于三次握手,为什么不把四次挥手中间2个包合并呢?它们都是S-->C为什么要单独拆分来发送呢?注意到FIN的意义:表示这个方向的带数据的包传输已经完成,即发送FIN包的端没有带数据的包过来了,需要释放这个方向的连接。。那么接收到FIN包的这一方虽然不再接收对端的带数据的包,但是它仍然可以发带数据的包过去。
以后补充