Linux下当服务器端断开,客户端会发生管道破裂

1、为什么会出现管道破裂呢?

因为TCP协议是端到端的传输控制协议,之所以是“端到端”的协议,是因为“路由”是有IP协议负责的,TCP协议负责为两个通信端点提供可靠性保证,这个可靠性不是指一个端点发送的数据,另一个端点肯定能收到(这显然是不可能的),而是指,数据的可靠投递或者故障的可靠通知。

2、客户端和服务器时如何实现数据的互传的呢?

当调用send()或者write()发送数据时,并不是直接将数据发送到网络中,而是先将待发送的数据放到socket发送缓冲区(对应的TXBuf)中,然后由TCP协议执行数据的网络发送。send()/write()函数不保证数据能够通过网络成功发送出去,只要能够将数据写入到socket发送缓冲区,send()/write()就可以成功返回。
在这里插入图片描述

3、当服务器调用了close后发生了什么呢?

TCP是全双工的信道,可以看作两条单工信道,TCP连接两端的两个端点各负责一条,当对端调用close时,虽然本意是关闭整个两条信道,但本端只是收到FIN包,按照TCP协议的语义,表示对端只是关闭了其所负责的那一条单工信道,仍然可以继续接收数据,也就是说,因为协议的限制,一个端点无法获知对端的socket是调用了close还是shutdwn(半关闭状态),当此时的服务器时调用了close关闭了整个TCP会话,两条信道都无法使用。如下图:
在这里插入图片描述
此时server close后,client收到了来自server的FIN包,但是如上所说,client只是认为server只关闭了服务器所负责那一条信道,仍然可以进行接收数据。所以第一次client调用write发送了数据到发送缓冲区后,返回的是发送成功,但是发送的报文会导致server会送一个RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 内核会会生成并发送SIGPIPE信号个该客户端进程, 导致进程退出,因为SIGPIPE默认的动作是程序终止。
所以由此导致了管道破裂,client也跟着退出程序。

TCP报文解析(补充)

三次握手
TCP是主机对主机层的传输控制协议,提供可靠的连接服务,采用三次握手确认建立一个连接:
位码即tcp标志位,有6种标示:
SYN(synchronous建立联机)
ACK(acknowledgement 确认)
PSH(push传送)
FIN(finish结束)
RST(reset重置)
URG(urgent紧急)
Sequence number(顺序号码)
Acknowledge number(确认号码)
客户端TCP状态迁移:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
服务器TCP状态迁移:
CLOSED->LISTEN->SYN收到->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
各个状态的意义如下:
LISTEN - 侦听来自远方TCP端口的连接请求;
SYN-SENT -在发送连接请求后等待匹配的连接请求;
SYN-RECEIVED - 在收到和发送一个连接请求后等待对连接请求的确认;
ESTABLISHED- 代表一个打开的连接,数据可以传送给用户;
FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;
FIN-WAIT-2 - 从远程TCP等待连接中断请求;
CLOSE-WAIT - 等待从本地用户发来的连接中断请求;
CLOSING -等待远程TCP对连接中断的确认;
LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;
TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认;
CLOSED - 没有任何连接状态;

4、解决方法

4.1 安装信号

为了避免进程退出, 可以捕获SIGPIPE信号,给它设置SIG_IGN忽略信号或者设置处理函数:

设置忽略信号:
signal(SIGPIPE, SIG_IGN);/* 设置了SIG_IGN 忽略处理 /

安装信号处理函数:
//头文件
#include <signal.h>
1、
signal(SIGPIPE, handle_pipe);
void handle_pipe(int sig)
{
    if(sig == SIGPIPE)
    {
            
    }/* 表示捕获到SIGPIPE信号时,什么事都不做 */
}
2struct sigaction action;
action.sa_handler = handle_pipe;
sigemptyset(&action.sa_mask); /* SIGPIPE put in the signal mask,until function return */
action.sa_flags = 0; /* not perform other operations */
sigaction(SIGPIPE,&action,NULL);
函数如下:
/* SIGPIPE handle empty function */
void handle_pipe(int sig)
{
    ; / 表示捕获到SIGPIPE信号时,什么事都不做 */
}

其中signal在程序中,只是第一次有效,如果再次发生管道破裂就不会再起作用了,而sigaction一直会有用。
但是这以上方法都是被动的,也就是说我们不能提前知道与服务器的连接是否已经断开,必须继续write才能收到SIGPIPE信号,而且必须执行两次,这导致第一次发送的数据即发不到服务器,也没有存入本地数据库,导致这一条数据丢失。所以以上方法都不够完美,下面将介绍一种主动监听的。

4.2 getsockopt()

调用该函数可以主动的检测出是否与服务器断开连接,如果断开就重新连接,如果再连接不上,就存入本地数据库。直到与服务器再次建立连接,再把数据发送到服务器。

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

struct tcp_info info;
int len = sizeof(info);
getsockopt(connect_fd,IPPROTO_TCP,TCP_INFO,&info,(socklen_t *)&len);
if(info.tcpi_state != TCP_ESTABLISHED) //则说明对方断开连接,否则则表明已连接

参考:https://blog.csdn.net/weixin_45880057/article/details/124224881?spm=1001.2014.3001.5502

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值