- 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包的应答位
PSH:TCP包中有数据需要尽快传递给应用层使用,而不是将数据进行缓冲,等到缓冲区满了再投递给应用层。
RST:..........
SYN:TCP包的同步位
FIN:表示数据传输已经完成,释放连接。
- TCP三次握手
TCP三次握手过程:
测试代码,server端:
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 int main ( int argc, char *argv[] ) 8 { 9 int ret,rn; 10 int socketfd,acfd; 11 int socklen; 12 char buf[1024]; 13 14 struct sockaddr_in hostaddr; 15 struct sockaddr_in clientaddr; 16 17 socketfd = socket(AF_INET, SOCK_STREAM, 0); 18 if ( socketfd < 0 ) 19 { 20 perror("socket"); 21 return -1; 22 } 23 24 memset((void *)&hostaddr, 0, sizeof(hostaddr)); 25 hostaddr.sin_family = AF_INET; 26 hostaddr.sin_port = htons(6666); 27 hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); 28 29 ret = bind(socketfd, (struct sockaddr *)&hostaddr, sizeof(hostaddr)); 30 if ( ret < 0 ) 31 { 32 perror("bind"); 33 close(socketfd); 34 return -1; 35 } 36 37 ret = listen(socketfd, 5); 38 if ( ret < 0 ) 39 { 40 perror("listen"); 41 close(socketfd); 42 return -1; 43 } 44 45 socklen = sizeof(struct sockaddr); 46 acfd = accept(socketfd, (struct sockaddr *)&clientaddr, &socklen); 47 if ( acfd < 0 ) 48 { 49 perror("accept"); 50 close(socketfd); 51 return -1; 52 } 53 54 while (1) 55 { 56 memset(buf, 0x0, sizeof(buf)); 57 rn = read(acfd, buf, sizeof(buf)); 58 if ( rn < 0 ) 59 { 60 perror("read"); 61 continue; 62 } 63 printf("%s\n",buf); 64 } 65 close(socketfd); 66 return 0; 67 }
client端:
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 REQURE "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 17 socketfd = socket(AF_INET, SOCK_STREAM, 0); 18 if ( socketfd < 0 ) 19 { 20 perror("socket"); 21 return -1; 22 } 23 24 memset((void *)&hostaddr, 0, sizeof(hostaddr)); 25 hostaddr.sin_family = AF_INET; 26 hostaddr.sin_port = htons(6666); 27 hostaddr.sin_addr.s_addr = htonl(INADDR_ANY); 28 29 socklen = sizeof(struct sockaddr); 30 acfd = connect(socketfd, (struct sockaddr *)&hostaddr, sizeof(hostaddr)); 31 if ( acfd < 0 ) 32 { 33 perror("connect"); 34 close(socketfd); 35 return -1; 36 } 37 38 while (1) 39 { 40 sleep(10); 41 write(socketfd, REQURE, sizeof(REQURE)); 42 } 43 close(socketfd); 44 return 0; 45 }
这是CS间一对一的连接方式,一发一收。先运行server端,在accept下一行下断点,再运行client端,在connet的下一行下断点,使用wireshark抓包,过滤端口为6666的tcp包 tcp.port == 6666 ,运行,再断点处停下来时,可以看到抓到了3个包:
这就是TCP三次握手的3个包,TCP的握手是发生在client端进行connect时。对于握手包的Sequence number和Acknowledgment number我理解为三次握手的包序。
第一次握手:
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被置位,没有数据部分。