1.握手
说明:
下面涉及 FIN,SYN,ACK之类数据时,都是由TCP服务收发,
涉及 accept, listen 之类api,都是 应用进程 完成。
都统一使用 客户端,服务端描述,请自行分辨。
(1)首先描述下3次握手,TCP协议做了什么。
客户端,主动打开,发送自己的序列号SYNj,并期待对方回复ACKj+1
服务端,被动打开,接送自己的序列号SYNk和ACKj+1,并期待对方回复ACKk+1
客户端,接收对方ACK,己方打开完成,接收对方SYN,发送ACKk+1
服务端,接收对方ACK,己方打开完成。
分析下:
首先打开分为,被动打开和主动打开。
当接收到对方的ACK,则己方打开完成,则可以使用socket进行读写操作,但并不保证正常。
(2)结合 系统调用分析三次握手
客户端,listen,进行被动打开
服务端,connect ,进行主动打开,发送SYNj(如果乙方此时没有listen 完成,则connect 失败)
客户端,回复SYNk + ACKj+1, 应用层无任何变化。
服务端,收到 ACKj+1 + SYNk,并回复 ACKk+1, 甲方打开完成,connect 返回。
客户端,收到ACKj+1,可以进行 read,write。
服务端,accept,如果乙方没有收到 SYNj 则会阻塞,否则返回
服务端,收到ACKk+1,可以进行 read, write
总结下:
从应用层角度,三次握手的开始,
主动方,始于 connect,结束于 connect
被动方,始于 listen ,结束于 listen
accept 是被动方处以已经完成握手的主动方。
(3)实验
client
if (0 > (connect(fd, (struct sockaddr *)&addr, sizeof(addr)))) { perror("connect"); exit(-1); } printf("success to connect\n"); write(fd, "hello world", strlen("hello world")); printf("write over\n"); close(fd);
server
if (0 > listen(fd, 10)) { perror("listen"); exit(-1); } #if 1 printf("sleep for delay accept...\n"); sleep(5); printf("sleep over\n"); #endif if (0 > (c_fd = accept(fd, (struct sockaddr *)&c_addr, &len))) { perror("accept"); exit(-1); } printf("success to accept\n"); while ((nbytes = read(c_fd, msg, MAX_READ))) { msg[nbytes] = 0; printf("%s", msg); } printf("\n"); if (0 > (nbytes = write(c_fd, "bye", 3))) { perror("write"); exit(-1); } printf("i have see bye\n"); close(fd); close(c_fd);
实验结果
sleep for delay accept... sleep over // 这里客户端已经通信结束,程序退出 success to accept hello world i have see bye
可以发现,客户端虽然已经挥手,但是服务端仍然视为握手完成,并正常的进行通信。
用netstat查看
可以发现,客户端进程虽然死掉,但打开的TCP服务并没有关闭,他在等待四次挥手完成。
因此服务端,虽然延迟处理,却依旧能正常处理通信。、
2.四次挥手
客户端,主动关闭,close,发送 FINm
服务端,被动关闭,接收FINm,发送ACKm+1
一段时间后,服务端,调用close,发送FINn
客户端,接收FINn,发送ACKn+1
总结下:
当服务端接收到FIN时,注意,所有的TCP数据都由TCP服务处理,服务端的TCP收到FIN,并向服务端进程发送文件结束符EOF。
EOF按照顺序在通信数据后面。
当服务端进程read获得EOF,适时主动调用close,以发送 FINn。
在客户端close,服务端没有close的阶段,称为 半关闭。
这时,服务端进程可以发送数据,数据会交给客户端TCP服务,但不会上交给应用层。所以客户端进程是否存在,没关系。
来源:https://www.cnblogs.com/yangxinrui/p/12199407.html