热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

为什么即使Linux服务器的socket关闭,客户端仍能调用一次send函数?

要弄清这个问题,首先需要知道调用send()发送数据时,发生了什么。当调用send()发送数据时,并不是直接将数据发送到网络中,而是先将待发送的数据放到socket发送缓冲区中,然

要弄清这个问题,首先需要知道调用send()发送数据时,发生了什么。

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

 当服务器socket突然关闭时,客户端并不知情,此时客户端调用send()依然可以将数据放置到socket发送缓冲区中,所以send()成功返回。直到TCP协议执行发送动作的时候才发现不对劲~~~~这时客户端才拿到服务器socket已关闭的信息,由于TCP协议无法继续网络数据的传输,所以系统自动关闭客户端socket发送缓冲区的读端。这时,再次调动send()函数,就会导致程序终止。原因是收到了SIGPIPE信号,这说明socket发送缓冲区应该是一个特殊的管道。

以下程序模拟了这个过程,

/* server.c */
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVERIP "192.168.2.134"
int main()
{
int ret;
//创建套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
if (sfd == -1)
{
perror("socket");
return -1;
}
//设置地址复用
int opt = 1;
setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//填充服务器地址信息
struct sockaddr_in seraddr;
memset(&seraddr, 0, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(8999);
seraddr.sin_addr.s_addr = inet_addr(SERVERIP);
//绑定
ret = bind(sfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if (ret == -1)
{
perror("bind");
return -1;
}
//监听
listen(sfd, 5);
//等待连接
struct sockaddr_in cliaddr;
memset(&cliaddr, 0, sizeof(cliaddr));
socklen_t len = sizeof(cliaddr);
int cfd = accept(sfd, (struct sockaddr*)&cliaddr, &len);
//处理连接事务
printf("欢迎%s:%d访问!\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
char buffer[64] = {0};
while (1)
{
recv(cfd, buffer, sizeof(buffer), 0);
fputs(buffer, stdout);
}
return 0;
}

/* client.c */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVERIP "192.168.2.134"
#define SERVERPORT 8999
typedef void (*sighandler_t)(int);
void sig_handler(int signum)
{
printf("果然裂开了!!!!\n");
exit(0);
}
int main()
{
int ret;
char buffer[64];
//捕获信号
signal(SIGPIPE, sig_handler);
//创建套接字
int cfd = socket(AF_INET, SOCK_STREAM, 0);
if (cfd == -1)
{
perror("socket");
return -1;
}
//填充目标服务器地址信息
struct sockaddr_in cliaddr;
memset(&cliaddr, 0, sizeof(cliaddr));
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(SERVERPORT);
cliaddr.sin_addr.s_addr = inet_addr(SERVERIP);
//请求连接
ret = connect(cfd, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
if (ret == -1)
{
perror("connect");
return -1;
}
//处理与服务器的交互事务
while (1)
{
fgets(buffer, sizeof(buffer), stdin);
send(cfd, buffer, strlen(buffer)+1, 0);
}
return 0;
}

 如上,成功发送了"hello"、"dont leave"后,强制停止服务器进程,这时客户端继续发送"are you here?",send()成功返回,"are you here?"被成功写到发送缓冲区,但是无法通过TCP协议发送到网络中,当下一次发送"????"时,就收到了SIGPIPE信号,被自定义的信号处理函数捕获到。


推荐阅读
  • CF:3D City Model(小思维)问题解析和代码实现
    本文通过解析CF:3D City Model问题,介绍了问题的背景和要求,并给出了相应的代码实现。该问题涉及到在一个矩形的网格上建造城市的情景,每个网格单元可以作为建筑的基础,建筑由多个立方体叠加而成。文章详细讲解了问题的解决思路,并给出了相应的代码实现供读者参考。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
author-avatar
Perz
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有