当前位置:网站首页>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 60999func 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同时打开,它由四次握手产生。
参考资料

边栏推荐
- Easy to use map visualization
- Ks007 realizes personal blog system based on JSP
- shell脚本的替换功能实现
- Weima, which is going to be listed, still can't give Baidu confidence
- To sort out messy header files, I use include what you use
- 7 RSA Cryptosystem
- With an estimated value of 90billion, the IPO of super chip is coming
- Face_ Attendance statistics of recognition face recognition
- Rainfall warning broadcast automatic data platform bwii broadcast warning monitor
- Win32 API access route encrypted web pages
猜你喜欢

Superscalar processor design yaoyongbin Chapter 7 register rename excerpt

五千字讲清楚团队自组织建设 | Liga 妙谈

创业两年,一家小VC的自我反思

就在今天丨汇丰4位专家齐聚,共讨银行核心系统改造、迁移、重构难题

mysql5.7安装教程图文详解

Ks007 realizes personal blog system based on JSP

7 RSA Cryptosystem

Hidden corners of coder Edition: five things that developers hate most
12 - explore the underlying principles of IOS | runtime [isa details, class structure, method cache | t]

What is low code development?
随机推荐
CocosCreator事件派發使用
Flask lightweight web framework
With an annual income of more than 8 million, he has five full-time jobs. He still has time to play games
上网成瘾改变大脑结构:语言功能受影响,让人话都说不利索
VSCode修改缩进不成功,一保存就缩进四个空格
Offline and open source version of notation -- comprehensive evaluation of note taking software anytype
DB-Engines 2022年7月数据库排行榜:Microsoft SQL Server 大涨,Oracle 大跌
Image retrieval
Analysis of I2C adapter driver of s5pv210 chip (i2c-s3c2410. C)
[unity ugui] scrollrect dynamically scales the grid size and automatically locates the middle grid
Stars open stores, return, return, return
设置窗体透明 隐藏任务栏 与全屏显示
要上市的威马,依然给不了百度信心
补能的争议路线:快充会走向大一统吗?
Superscalar processor design yaoyongbin Chapter 6 instruction decoding excerpt
gatling 之性能测试
General environmental instructions for the project
"In Vietnam, money is like lying on the street"
Blood spitting finishing nanny level series tutorial - play Fiddler bag grabbing tutorial (2) - first meet fiddler, let you have a rational understanding
The top half and bottom half of the interrupt are introduced and the implementation method (tasklet and work queue)