当前位置:首页 > 技术文章 > 正文内容

应用层如何强制发送 RST 报文进行断开连接

zonemu2个月前 (07-14)技术文章26

在 TCP 协议中,默认情况下,当我们调用 close() 函数关闭套接口时,TCP 走四次挥手进行断开链路,但是要是若缓冲区还有数据未发送到对端时,系统将尝试把这些数据发送给对端。四次挥手的过程导致我们在 TIME_WAIT 状态下无法复用端口。有些情况下我们不需要 TIME_WAIT, 而是想快速断开连接,从而避免 socket 的堆积。

这个时候我们可以使用 SO_LINGER 套接字选项

struct linger {
int l_onoff;
int l_linger;
}

1) 若 l_onoff 为0, 表示关闭该选项。l_linger 值被忽略,也即是走TCP 的默认设置。

2)若 l_onoff 为非 0 且 l_linger 为 0,那么当 close 某个连接时 TCP 将终止该连接。也即是TCP将丢弃保留在套接字发送缓冲区中的任何数据,并发送RST报文给对端,不再走四次挥手,从而避免了 TCP 的 TIME_WAIT 状态。但是依然存在以下可能性:在 2 MSL 秒内创建该连接的另一个化身,导致来自刚被终止的连接上的旧的重复分节被不正确的传递到新的化身上。

3)若 l_onoff 为非 0 值且 l_linger 也为非 0 值,那么当套接字关闭时内核将拖延一段时间关闭,也即是若在套接字的发送缓冲区中还有残留数据,那么进程将投入睡眠,直到数据发送完且均被对端确认或者滞留时间到。若套接字被设置成非阻塞型,那么它将不等待 close 完成,即是滞留时间不为 0 也是如此。当使用 SO_LINGER 选项时,应用程序检查 close 的返回值很重要,因为若在数据发送完并被确认前延滞时间到的话,close 将返回 EWOULDBLOCK 错误,且套接字发送缓冲区中的任何残留数据都被丢弃。

通过下面实现进行验证。

首先 server 端使用 nc 进行监听一个TCP 指定端口。

客户端使用如下代码

#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>


int main(int argc, char *argv[])
{
struct sockaddr_in peer;
struct linger linger;
int ret;
int sock = socket(AF_INET, SOCK_STREAM, 0);

memset(&peer, 0, sizeof(peer));

peer.sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &peer.sin_addr);
peer.sin_port = htons(atoi(argv[2])); 

memset(&linger, 0, sizeof(linger));
linger.l_onoff = 1;
linger.l_linger = 0;

ret = setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger));
if (ret) {
printf("Fail to set linger\n");
exit(1);
}

ret = connect(sock, (const struct sockaddr *)&peer, sizeof(peer));

if (ret) {
printf("Fail to connect.\n", strerror(errno));
exit(1);
}

printf("Connect successfully\n");



close(sock);

printf("Done\n");

return 0;
}

通过抓包分析来看,调用 close 后,客户端直接发送了 RST 报文端开了连接。

19:22:13.101476 IP 17.15.220.199 > localhost.localdomain : Flags [S], seq 12771346 ..
19:22:13.101509 IP localhost.localdomain > 17.15.220.199 : Flags [S .], seq 1277234 ..
19:22:13.101732 IP 17.15.220.199 > localhost.localdomain : Flags [.], ack ...
19:22:13.101912 IP 17.15.220.199 > localhost.localdomain : Flags [R .] ...

在 tcp_close 中查看具体实现

/*
内核并并不关心有多少数据未被用户进程读取,内核关心的是有没有数据未被读取,
若有数据未被读取而丢弃(data_was_unread>0),则给对方发送rst报文
若没有数据未被用户进程读取,也即是全部数据都被用户进程读取了(data_was_unread==0),则相对对端发送fin报文
*/
if (data_was_unread) {
/* Unread data was tossed, zap the connection. */
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONCLOSE);

/*发送rst报文前设置状态为TCP_CLOSE,这时没有TIME_WAIT状态,没有FIN_WAIT_1状态,说明此时时不正常关闭的。
所以可得,在编写程序时,在关闭连接前,一定要保证所有接收到的数据被读取,否则连接会不正常关闭*/
tcp_set_state(sk, TCP_CLOSE); 
//发送rst报文,之所以不是fin报文,是因为关闭时还有未读的数据属于异常情况,fin表示一切正常情况
tcp_send_active_reset(sk, GFP_KERNEL);
} else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
/* Check zero linger _after_ checking for unread data. */
/*调用tcp_disconnect断开、删除并释放已建立连接但未被accept的传输控制块,同时
删除并释放已接收在接收队列(包括失序队列)上的段以及发送队列上的段*/
sk->sk_prot->disconnect(sk, 0);// tcp_disconnect
NET_INC_STATS_USER(LINUX_MIB_TCPABORTONDATA);
} else if (tcp_close_state(sk)) { //若未读字节数为0,则调用tcp_close_state根据sk当前状态来设置sk下一状态,比如当前状态为TCP_ESTABLISHED,则下一状态为TCP_FIN_WAIT1,该方法的返回确定是否发送fin报文给对方
/*

从上面的代码段可以看到,当有数据还未读取时,说明是异常关闭,直接发送 RST 报文给对端。若接收缓冲区中数据都已经读取完了,判断 SOCK_LINGER 套接字选项,若 l_linger 为 0,则调用 tcp_disconnect 给对端发送 RST 报文,同时释放接收和发送队列上的数据。

相关文章

Vue3 中,父子组件如何传递参数?(vue父子组件传递数据方法)

在 Vue3 中,组件化开发是非常重要的特征,那么组件之间传值就是开发中常见的需求了。组件之间的传值三种方式:父传子、子传父、非父子组件传值。一、父传子( defineProps )父组件主要通过使用...

「2022」打算跳槽涨薪,必问面试题及答案——VUE篇

1、为什么选择VUE,解决了什么问题?vue.js 正如官网所说的,是一套构建用户界面的渐进式框架。与其它重量级框架不同的是,vue 被设计为可以自底向上逐层应用。vue 的核心库只关注视图层,不仅易...

Vue2的16种传参通信方式(vue传参数)

前言先直入主题列出有哪些传参方式,下面再通过事例一一讲解。props(父传子)$emit与v-on (子传父)EventBus (兄弟传参).sync与update: (父子双向)v-model (父...

Vue3 中有哪些值得深究的知识点?(vue3例子)

众所周知,前端技术一直更新很快,这不 vue3 也问世这么久了,今天就来给大家分享下vue3中值得注意的知识点。喜欢的话建议收藏,点个关注!1、createAppvue2 和 vue3 在创建实例时,...

最快认知什么才是HTML5广告!(h5广告设计是什么)

H5广告似乎是自UI风靡之后,又一个热度极高的词儿。他是什么?一个字母加一个数字是个什么意思? 为什么如此受欢迎?金色号角会议室,创作事业部赵阳同学就HTML5广告做了详尽生动的分享,带大家一起用手机...

小白友好型Windows优化工具BoosterX使用教程 一键提升游戏性能

一款集系统优化和游戏优化为一体的Windows友好型优化工具,BoosterX得到过众多游戏玩家的推荐,它能简单、快速、安全地优化Windows,就算设置出错还能通过备份还原设置。它还提供了一款精简版...