当前位置:网站首页>TCP两次挥手,你见过吗?那四次握手呢?
TCP两次挥手,你见过吗?那四次握手呢?
2022-07-04 16:08:00 【InfoQ】

TCP四次挥手

FIN一定要程序执行close()或shutdown()才能发出吗?
如果机器上FIN-WAIT-2状态特别多,是为什么

主动方在close之后收到的数据,会怎么处理
- 如果当前连接对应的socket的接收缓冲区有数据,会发RST。
- 如果发送缓冲区有数据,那会等待发送完,再发第一次挥手的FIN。

【文章福利】另外小编还整理了一些C++后端开发面试题,教学视频,后端学习路线图免费分享,需要的可以自行添加:
学习交流群点击加入~
群文件共享
小编强力推荐C++后端开发免费学习地址:
C/C++Linux服务器开发高级架构师/C++后台开发架构师

第二第三次挥手之间,不能传输数据吗?
int shutdown(int sock, int howto);
- SHUT_RD:关闭读。这时应用层不应该再尝试接收数据,内核协议栈中就算接收缓冲区收到数据也会被丢弃。
- SHUT_WR:关闭写。如果发送缓冲区中还有数据没发,会将将数据传递到目标主机。
- SHUT_RDWR:关闭读和写。相当于close()了。

怎么知道对端socket执行了close还是shutdown

int send( SOCKET s,const char* buf,int len,int flags);
- 如果上一次主动关闭方调用的是shutdown(socket_fd, SHUT_WR)。那此时,主动关闭方不再发送消息,但能接收被动方的消息,一切如常,皆大欢喜。
- 如果上一次主动关闭方调用的是close()。那主动方在收到被动方的数据后会直接丢弃,然后回一个RST。
- 如果是读,则会返回RST的报错,也就是我们常见的Connection reset by peer。
- 如果是写,那么程序会产生SIGPIPE信号,应用层代码可以捕获并处理信号,如果不处理,则默认情况下进程会终止,异常退出。
- 第一次send(), 一般会成功返回。
- 第二次send()时。如果主动方是通过 shutdown(fd, SHUT_WR) 发起的第一次挥手,那此时send()还是会成功。如果主动方通过 close()发起的第一次挥手,那此时会产生SIGPIPE信号,进程默认会终止,异常退出。不想异常退出的话,记得捕获处理这个信号。
如果被动方一直不发第三次挥手,会怎么样
- 如果是 shutdown(fd, SHUT_WR) ,说明主动方其实只关闭了写,但还可以读,此时会一直处于 FIN-WAIT-2, 死等被动方的第三次挥手。
- 如果是 close(), 说明主动方读写都关闭了,这时候会处于 FIN-WAIT-2一段时间,这个时间由 net.ipv4.tcp_fin_timeout 控制,一般是 60s,这个值正好跟2MSL一样 。超过这段时间之后,状态不会变成 `TIME-WAIT`,而是直接变成`CLOSED`。
# cat /proc/sys/net/ipv4/tcp_fin_timeout
60

TCP三次挥手

如果有数据要发,就不能是三次挥手了吗

TCP两次挥手

一个socket能建立连接?
# cat /etc/os-release
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
# nc -p 6666 127.0.0.1 6666
# netstat -nt | grep 6666
tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED



【文章福利】另外小编还整理了一些C++后端开发面试题,教学视频,后端学习路线图免费分享,需要的可以自行添加:
学习交流群点击加入~
群文件共享
小编强力推荐C++后端开发免费学习地址:
C/C++Linux服务器开发高级架构师/C++后台开发架构师

一端发出第一次握手后,如果又收到了第一次握手的SYN包,TCP连接状态会怎么变化?
// net/ipv4/tcp_input.c
static int tcp_rcv_synsent_state_process()
{
// SYN_SENT状态下,收到SYN包
if (th->syn) {
// 状态置为 SYN_RCVD
tcp_set_state(sk, TCP_SYN_RECV);
}
}
一端发出第二次握手后,如果又收到第二次握手的SYN+ACK包,TCP连接状态会怎么变化?
// net/ipv4/tcp_input.c
int tcp_rcv_state_process()
{
// 前面省略很多逻辑,能走到这就认为肯定有ACK
if (true) {
// 判断下这个ack是否合法
int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH | FLAG_UPDATE_TS_RECENT) > 0;
switch (sk->sk_state) {
case TCP_SYN_RECV:
if (acceptable) {
// 状态从 SYN_RCVD 转为 ESTABLISHED
tcp_set_state(sk, TCP_ESTABLISHED);
}
}
}
}
一端第一次挥手后,又收到第一次挥手的包,TCP连接状态会怎么变化?
// net/
static void tcp_fin(struct sock *sk)
{
switch (sk->sk_state) {
case TCP_FIN_WAIT1:
tcp_send_ack(sk);
// FIN-WAIT-1状态下,收到了FIN,转为 CLOSING
tcp_set_state(sk, TCP_CLOSING);
break;
}
}
代码复现自连接
# strace nc -p 6666 127.0.0.1 6666
// ...
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
fcntl(3, F_GETFL) = 0x2 (flags O_RDWR)
fcntl(3, F_SETFL, O_RDWR|O_NONBLOCK) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
connect(3, {sa_family=AF_INET, sin_port=htons(6666), sin_addr=inet_addr("127.0.0.1")}, 16) = -1 EINPROGRESS (Operation now in progress)
// ...
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <string.h>
#include <strings.h>
int main()
{
int lfd, cfd;
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
char buf[BUFSIZ];
int n = 0, i = 0, ret = 0 ;
printf("This is a client \n");
/*Step 1: 创建客户端端socket描述符cfd*/
cfd = socket(AF_INET, SOCK_STREAM, 0);
if(cfd == -1)
{
perror("socket error");
exit(1);
}
int flag=1,len=sizeof(int);
if( setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &flag, len) == -1)
{
perror("setsockopt");
exit(1);
}
bzero(&clie_addr, sizeof(clie_addr));
clie_addr.sin_family = AF_INET;
clie_addr.sin_port = htons(6666);
inet_pton(AF_INET,"127.0.0.1", &clie_addr.sin_addr.s_addr);
/*Step 2: 客户端使用bind绑定客户端的IP和端口*/
ret = bind(cfd, (struct sockaddr* )&clie_addr, sizeof(clie_addr));
if(ret != 0)
{
perror("bind error");
exit(2);
}
/*Step 3: connect链接服务器端的IP和端口号*/
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(6666);
inet_pton(AF_INET,"127.0.0.1", &serv_addr.sin_addr.s_addr);
ret = connect(cfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr));
if(ret != 0)
{
perror("connect error");
exit(3);
}
/*Step 4: 向服务器端写数据*/
while(1)
{
fgets(buf, sizeof(buf), stdin);
write(cfd, buf, strlen(buf));
n = read(cfd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, n);//写到屏幕上
}
/*Step 5: 关闭socket描述符*/
close(cfd);
return 0;
}
# gcc client.c -o client && ./client
This is a client
# netstat -nt | grep 6666
tcp 0 0 127.0.0.1:6666 127.0.0.1:6666 ESTABLISHED
自连接的解决方案
# cat /proc/sys/net/ipv4/ip_local_port_range
32768 60999
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) {
// 如果是自连接,这里会重试
for i := 0; i < 2 && (laddr == nil || laddr.Port == 0) && (selfConnect(fd, err) || spuriousENOTAVAIL(err)); i++ {
if err == nil {
fd.Close()
}
fd, err = internetSocket(net, laddr, raddr, deadline, syscall.SOCK_STREAM, 0, "dial", sockaddrToTCP)
}
// ...
}
func selfConnect(fd *netFD, err error) bool {
// 判断是否端口、IP一致
return l.Port == r.Port && l.IP.Equal(r.IP)
}
四次握手

复现TCP同时打开
while true; do nc -p 2224 127.0.0.1 2223 -v;done
while true; do nc -p 2223 127.0.0.1 2224 -v;done
# netstat -an | grep 2223
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:2224 127.0.0.1:2223 ESTABLISHED
tcp 0 0 127.0.0.1:2223 127.0.0.1:2224 ESTABLISHED

总结
- 四次挥手中,不管是程序主动执行close(),还是进程被杀,都有可能发出第一次挥手FIN包。如果机器上FIN-WAIT-2状态特别多,一般是因为对端一直不执行close()方法发出第三次挥手。
- Close()会同时关闭发送和接收消息的功能。shutdown() 能单独关闭发送或接受消息。
- 第二、第三次挥手,是有可能合在一起的。于是四次挥手就变成三次挥手了。
- 同一个socket自己连自己,会产生TCP自连接,自连接的挥手是两次挥手。
- 没有listen,两个客户端之间也能建立连接。这种情况叫TCP同时打开,它由四次握手产生。
参考资料

边栏推荐
- Weima, which is going to be listed, still can't give Baidu confidence
- DB engines database ranking in July 2022: Microsoft SQL Server rose sharply, Oracle fell sharply
- 【每日一题】871. 最低加油次数
- What if Kaili can't input Chinese???
- Electronic pet dog - what is the internal structure?
- 居家打工年入800多万,一共五份全职工作,他还有时间打游戏
- [HCIA continuous update] WLAN overview and basic concepts
- Flask lightweight web framework
- [test development] software testing - Basics
- 为啥有些线上演唱会总是怪怪的?
猜你喜欢
Five thousand words to clarify team self-organization construction | Liga wonderful talk
Firewall basic transparent mode deployment and dual machine hot standby
【华为HCIA持续更新】SDN与FVC
Superscalar processor design yaoyongbin Chapter 5 instruction set excerpt
解决el-input输入框.number数字输入问题,去掉type=“number“后面箭头问题也可以用这种方法代替
DB engines database ranking in July 2022: Microsoft SQL Server rose sharply, Oracle fell sharply
雨量预警广播自动化数据平台BWII 型广播预警监测仪
KS007基于JSP实现人个人博客系统
使用3DMAX制作一枚手雷
Superscalar processor design yaoyongbin Chapter 6 instruction decoding excerpt
随机推荐
Rainfall warning broadcast automatic data platform bwii broadcast warning monitor
78 year old professor Huake impacts the IPO, and Fengnian capital is expected to reap dozens of times the return
Flask lightweight web framework
[test development] software testing - Basics
内核中时间相关的知识介绍
项目通用环境使用说明
简单易用的地图可视化
【209】go语言的学习思想
居家打工年入800多万,一共五份全职工作,他还有时间打游戏
R language plot visualization: plot visualization of multiple variable violin plot in R with plot
五千字讲清楚团队自组织建设 | Liga 妙谈
中断的顶半部和底半部介绍以及实现方式(tasklet 和 工作队列)
Offline and open source version of notation -- comprehensive evaluation of note taking software anytype
Analysis of I2C adapter driver of s5pv210 chip (i2c-s3c2410. C)
国产数据库TiDB初体验:简单易用,快速上手
【每日一题】556. 下一个更大元素 III
Hidden corners of coder Edition: five things that developers hate most
2022 national CMMI certification subsidy policy | Changxu consulting
7 RSA Cryptosystem
Internet addiction changes brain structure: language function is affected, making people unable to speak neatly