首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >客户端断连,服务端也断?

客户端断连,服务端也断?

作者头像
公众号guangcity
发布于 2019-09-20 09:39:28
发布于 2019-09-20 09:39:28
3.5K00
代码可运行
举报
文章被收录于专栏:光城(guangcity)光城(guangcity)
运行总次数:0
代码可运行

客户端断连,服务端也断?

0.导语

在socket网络编程中,如果此时客户端忽然由于某种原因断开连接或者崩溃,服务端没有处理好,便会同时崩溃掉,本篇文章将会从崩溃到问题分析,解决,一步步入手。

1.问题分析

问题分析可以结合TCP的"四次握手"关闭。

TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端的socket是调用了close还是shutdown。

对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出。

上述简化:SIGPIPE产生的原因是这样的:如果一个 socket 在接收到了 RST packet 之后,程序仍然向这个 socket 写入数据,那么就会产生SIGPIPE信号。

举例如下:当 client 连接到 server 之后,这时候 server 准备向 client 发送多条消息,但在发送消息之前,client 进程意外奔溃了,那么接下来 server 在发送多条消息的过程中,就会出现SIGPIPE信号。下面看一下 server 的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <string.h>
#include <csignal>

#define MAXLINE 120

int main(int argc, char *argv[]) {
    signal(SIGPIPE, SIG_IGN);  // 忽略 SIGPIPE 信号
    //1.创建一个侦听socket
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1) {
        std::cout << "create listen socket error." << std::endl;
        return -1;
    }

    //2.初始化服务器地址
    struct sockaddr_in bindaddr;
    bindaddr.sin_family = AF_INET;
    bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    bindaddr.sin_port = htons(8088);
    if (bind(listenfd, (struct sockaddr *) &bindaddr, sizeof(bindaddr)) == -1) {
        std::cout << "bind listen socket error." << std::endl;
        return -1;
    }

    //3.启动侦听
    if (listen(listenfd, SOMAXCONN) == -1) {
        std::cout << "listen error." << std::endl;
        return -1;
    }
    /**
     * 服务端连续写两次数据到客户端
     */
    while (true) {
        struct sockaddr_in clientaddr;
        socklen_t clientaddrlen = sizeof(clientaddr);
        //4. 接受客户端连接
        int clientfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clientaddrlen);
        if (clientfd != -1) {
            // 假设此时 client 奔溃, 那么 server 将接收到 client 发送的 FIN
            sleep(5);
            // 写入第一条消息
            char msg1[MAXLINE] = {"first message"};
            ssize_t n = write(clientfd, msg1, strlen(msg1));
            printf("write %ld bytes\n", n);
            // 此时第一条消息发送成功,server 接收到 client 发送的 RST
            sleep(1);
            // 写入第二条消息,出现 SIGPIPE 信号,导致 server 被杀死
            char msg2[MAXLINE] = {"second message"};
            n = write(clientfd, msg2, strlen(msg2));
            printf("%ld, %s\n", n, strerror(errno));
        }

        close(clientfd);
    }
    //关闭侦听socket
    close(listenfd);
    return 0;

我们可以使用 Linux 的 nc 工具作为 client,当 client 连接到 server 之后,就立即杀死 client (模拟 client 的意外奔溃)。这时可以观察 server 的运行情况:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$ gcc -o server server.c
$ ./server &              # 后台运行 server
$ nc localhost 8888       # 运行 nc 连接到 server
^C                        # Ctrl-C 杀死 nc
write 13 bytes
[1]+  Broken pipe             ./server

分析一下整个过程:

  • client 连接到 server 之后,client 进程意外奔溃,这时它会发送一个 FIN 给 server。
  • 此时 server 并不知道 client 已经奔溃了,所以它会发送第一条消息给 client。但 client 已经退出了,所以 client 的 TCP 协议栈会发送一个 RST 给 server。
  • server 在接收到 RST 之后,继续写入第二条消息。往一个已经收到 RST 的 socket 继续写入数据,将导致SIGPIPE信号,从而杀死 server。

2.如何解决

对 server 来说,为了不被SIGPIPE信号杀死,那就需要忽略SIGPIPE信号:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
int main(){    
    signal(SIGPIPE, SIG_IGN);  // 忽略 SIGPIPE 信号
    ...
}

重新运行上面的程序,server 在发送第二条消息的时候,write()会返回-1,并且此时errno的值为EPIPE,所以这时并不会产生SIGPIPE信号。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 光城 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Linux TCP通信出现CLOSE_WAIT后导致服务端进程挂掉
在前文中讲述了Linux服务端TCP通信出现CLOSE_WAIT状态的原因,这篇文章主要通过一个实例演示它个一个“恶劣”影响:直接使服务端进程Down掉。
typecodes
2024/03/29
5670
Linux TCP通信出现CLOSE_WAIT后导致服务端进程挂掉
epoll使用具体解释(精髓)
在linux的网络编程中,非常长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。 相比于select,epoll最大的优点在于它不会随着监听fd数目的增长而减少效率。由于在内核中的select实现中,它是採用轮询来处理的,轮询的fd数目越多,自然耗时越多。而且,在linux/posix_types.h头文件有这种声明:
全栈程序员站长
2021/12/23
5560
网络编程的三个重要信号(SIGHUP ,SIGPIPE,SIGURG)[通俗易懂]
  对于信号的介绍,我再前面的一篇博客中做过专门的总结,感兴趣的可以看看。本文主要介绍在网络编程中几个密切相关的函数:SIGUP,SIGPIPE,SIGURG。
全栈程序员站长
2022/09/02
5.8K0
网络编程的三个重要信号(SIGHUP ,SIGPIPE,SIGURG)[通俗易懂]
美团二面:TCP 四次挥手,可以变成三次吗?
上周有位读者面美团时,被问到:TCP 四次挥手中,能不能把第二次的 ACK 报文, 放到第三次 FIN 报文一起发送?
愿天堂没有BUG
2022/09/12
1.7K0
socket基本连接
基本操作 服务端 #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <string.h> int main(int argc, char* argv[]) { std::cout << "begin" << std::endl; //1.创建一个侦听socket int listenfd
opencode
2022/12/26
7230
linux网络编程之socket(九):使用select函数改进客户端/服务器端程序
一、当我们使用单进程单连接且使用readline修改后的客户端程序,去连接使用readline修改后的服务器端程序,会出现一个有趣的现象,先来看输出: 先运行服务器端,再运行客户端, simba@ub
s1mba
2017/12/28
3.9K0
linux网络编程之socket(九):使用select函数改进客户端/服务器端程序
(二)Reactor模式
最近一直在看游双的《高性能linux服务器编程》一书,下载链接: http://download.csdn.net/detail/analogous_love/9673008 书上是这么介绍React
范蠡
2018/04/04
1.7K0
(二)Reactor模式
网络通信基础重难点解析 12 :Linux epoll 模型
综合 select 和 poll 的一些优缺点,Linux 从内核 2.6 版本开始引入了更高效的 epoll 模型,本节我们来详细介绍 epoll 模型。
范蠡
2019/04/29
1.6K0
网络通信基础重难点解析 12 :Linux epoll 模型
UNPv13:#第5章#TCP客户/服务器程序示例
Code github //server.c #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <signal.h> #include <wait.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> void sig_chld(int signo)
_gongluck
2018/03/09
7840
C++搭建集群聊天室(十五):客户端
直接塞一个文件里面。 client.cpp #include "json.hpp" #include <iostream> #include <thread> #include <string> #include <vector> #include <chrono> #include <ctime> #include <unordered_map> #include <functional> using namespace std; using json = nlohmann::json; #includ
看、未来
2021/09/18
9060
Linux系统开发: 学习Linux下网络编程
创建网络套接字,用于网络通信使用,类似于文件操作的open函数。该函数在服务器和客户端都会用到。
DS小龙哥
2022/01/27
3.8K0
Linux系统开发: 学习Linux下网络编程
Linux SIGPIPE信号产生原因与解决方法
SIGPIPE信号产生的原因: 简单来说,就是客户端程序向服务器端程序发送了消息,然后关闭客户端,服务器端返回消息的时候就会收到内核给的SIGPIPE信号。 TCP的全双工信道其实是两条单工信道,client端调用close的时候,虽然本意是关闭两条信道,但是其实只能关闭它发送的那一条单工信道,还是可以接受数据,server端还是可以发送数据,并不知道client端已经完全关闭了。 以下为引用: ”’对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.”’
全栈程序员站长
2022/09/02
1.8K0
Linux SIGPIPE信号产生原因与解决方法
监测网络状态(AFNetworking) 服务端 客户端
//监测网络状态(AFNetworking) import "ViewController.h" //引入第三方框架 import "AFNetworking/AFNetworking.h" @interface ViewController () @end @implementation ViewController (void)viewDidLoad { [super viewDidLoad]; //检查网络状态 [self checkNetworkStates]; } #pragma m
用户7108768
2021/10/29
1.2K0
简单的客户机服务器投射模拟
下面模拟了,简单的客户机服务器投射模拟的过程。客户机像服务器发送数据,服务器接受到数据后,发送回给客户机。再由客户机打印出来。 需要的函数: 网络方面 服务器 socket(AF_INET,SOCK_STREAM,0);  AF_INET表示IPV4,SOCK_STREAM表示基于字节流的,0表示协议由前面两个参数组合而成。返回描述符 bind(sockdf,(struct sockaddr*)servaddr,sizeof(servaddr));   用于把描述符与本地协议地址联系起来。 listen
用户1154259
2018/01/17
1K0
Linux TCP客户端出现CLOSE_WAIT后进入死循环
在前文中讲述了Linux服务端TCP的某个链路变成CLOSE_WAIT状态,然后由于客户端已经关闭了(发送了RST标志的报文),那么服务端如果继续向这个链路中写入数据的话就会收到SIGPIPE信号而终止,这篇文章主要通过客户端进入CLOSE_WAIT后由于收到服务端产生的RST标志报文进入死循环的情况。注:RST表示复位,用来关闭异常的连接。
typecodes
2024/03/29
7240
Linux TCP客户端出现CLOSE_WAIT后进入死循环
socket基于select的连接
select操作 服务端 #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <string.h> #include <sys/time.h> #include <vector> #include <errno.h> //自定义代表无效fd的值 #define INVALID_FD -1 int main(in
opencode
2022/12/26
9430
SIGPIPE[通俗易懂]
当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。
全栈程序员站长
2022/08/23
5860
网络编程-一个简单的echo程序(0)
在上一篇《网络编程-从TCP连接的建立说起》中简单介绍了TCP连接的建立,本文暂时先抛开TCP更加详细的介绍,来看看如何实现一个简单的网络程序。
编程珠玑
2019/07/12
6190
网络编程-一个简单的echo程序(0)
TCP中两种保活方式
在很多情况下,连接的一端需要一直感知连接的状态,如果连接无效了,应用程序可能需要报错,或者重新发起连接等。
穿过生命散发芬芳
2025/01/10
2960
关于close和shutdown
我们知道TCP是全双工的,可以在接收数据的同时发送数据。 假设有主机A在和主机B通信,可以认为是在两者之间存在两个管道。就像这样: A ---------> B A <--------- B
xcywt
2022/05/09
1.5K0
相关推荐
Linux TCP通信出现CLOSE_WAIT后导致服务端进程挂掉
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档