前面我们看到,建立一个TCP连接需要三次握手(SYN, SYN+ACK,
ACK)。而终止一个连接要经过4次握手,下面我们会看到,严格来讲,是两个两次握手,即A端告知B端它终止从A端到B端的连接,即A端不会再往B端发送
数据了(通过向B端发送一个FIN标志)。A端的关闭即告完成,此时,我们说A到B的这条TCP连接处于半关闭状态(half-close)。
但这时,B端还是可以向A端发送数据的,B端可以在将来的任一时间内向A端发送FIN来完成它这端的半关闭。此时,A端的socket可能已经不存在(超
时删除),但A主机的TCP/IP协议栈中有一个tcp control
socket会代为完成一个ACK动作,完成第二个两次握手,从而彻底断开这条TCP连接。
但在实际应用中,通常不可能一端执行半关闭后,另一端还继续发送数据,过一段时间后才完全关闭。通常是一端执行主动关闭后,另一方马上执行被动关闭。
为了清晰起见,我们建立一个实验环境.172.16.48.13(以下简称13)上运行一个TCP客户端进程,172.16.48.1(以下简称1)上运
行一个TCP的服务端进程,在端口5002上进行侦听(关于侦听,以后会介绍)。13连向1的5002端口,发送一个TCP数据报后,进程退出,执行主动
关闭,向1发送一个FIN,1进行应答,连接进入半关闭状态,但1的进程不关闭socket,直到人为关闭进程时,才向13发送FIN,13应答,TCP
连接完全关闭。
下面我们详细分析这整个流程,下面是13上的客户端进程的源代码:
#include
#include
#include
#include
#include "my_inet.h"
#include
int main(void)
{
int fd, n, r;
struct sockaddr_in srv, client;
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(5002);
inet_aton("172.16.48.1", &srv.sin_addr);
client.sin_family = MY_AF_INET;
client.sin_port = 0;
client.sin_addr.s_addr = inet_addr("172.16.48.13");
if( (fd = socket( MY_AF_INET, SOCK_STREAM, MY_IPPROTO_TCP) ) < 0 ){
perror("socket");
return -1;
}
if( bind(fd, (struct sockaddr *)&client, sizeof(client) ) < 0 ){
perror("bind: ");
return -1;
}
if( connect(fd, (struct sockaddr *)&srv, sizeof(srv)) < 0 ){
perror("connect: ");
return -1;
}
char send_buf[1024];
strcpy( send_buf, "my first tcp packet!/n" );
if( send(fd, send_buf, strlen(send_buf), 0 ) < 0 ){
perror("send: ");
return -1;
}
close(fd);
}
这里,客户端要执行一个地址绑定动作,因为13地址是本机eth0接口的一个从属地址,如果不执行绑定,系统会自动选择主地址172.16.48.2作为
发送地址,这样会跟内核中的TCP/IP协议栈造成冲突,而172.16.48.13地址对内核中的协议栈是不可见的(它由myinet模块专门设置)。
我们在send系统调用后,直接执行了close,然后进程就结束了。在进程结束前,close系统调用会调用到内核函数myinet_release,然后到具体协议中的close函数,对TCP协议来说,就是mytcp_close。
此时,socket处于TCP_ESTABLISHED状态,如果接收队列中还存在数据(struct
sk_buff队列),则先清空数据,并进行相应的处理(我们现在假设一种最简单的状态,所以不会有数据)。将自己的状态标为
TCP_FIN_WAIT1,表示进入关闭的第一个状态,即等待对端回应自己的FIN数据报,同时向1发送一个FIN数据报,告知发送任务已完成,进入关
闭状态。下面是13向1发送的第一个FIN数据报内容(已剥去数据链路层的以太网首部和网络层的IP首部)
数据内容 含义
基本TCP首部
80 26 16位源端口号(32806)
13 8a 16位目的端口号(5002)
00 00 07 d2 32位序号
7e 08 ba a0 32位确认序号
8 首部长度(8*4=32)
0 11 标志位,ACK=1, FIN=1
05 b4 16位窗口大小(1460)
9e 1c 16位校验和
00 00 16位紧急指针
TCP选项
01 填充
01 填充
08 0a 00 00 7e db 00 85 c7 30 时间戳
从数据内容来看,这不单单是一个FIN,同时,它也承担了应答1的上一个TCP数据报的功能(ACK)。本次连接过程中,172.16.48.1的
socket随机生成的初始序号(SIN)为0x7E08BA9F,通讯过程中,SYN占去一位(三次握手协义建立连接的时候),那此时,13的ACK序
号应该是0x7E08BAA0,即希望下次接收到来自1的数据报的第一个序号。
发完数据报,在函数mytcp_close中,socket进入一个超时等待队列,等待socket完全关闭。如果未设置超时时间,则直接关闭socket。
172.16.48.1收到这个FIN数据,进行响应:
数据内容 含义
基本TCP首部
13 8a 16位源端口(5002)
80 26 16位目的端口(32806)
7e 08 ba a0 32位序号
00 00 07 d3 32位确认序号(因为FIN占一位)
8 首部长度
0 10 标志位, ACK=1
05 a8 16位窗口大小(1448)
9d ff 16位校验和
00 00 16位紧急指针
TCP选项
01 填充
01 填充
08 0a 00 85 c7 59 00 00 7e db 时间戳
13收到这个响应后,会进行一系例处理,从而完成一个半关闭,这个容后续再介绍。
阅读(1932) | 评论(0) | 转发(1) |