SOCKET网络编程细节问题3
SOCKET网络编程快速上手(二)——细节问题(3)
3.SIGPIPE问题
人怕牺牲,我们写的程序也一样,人有死不瞑目,程序又何尝不是?程序跑着跑着,突然就崩掉了。好一点的牺牲前告诉你些打印,差点的也能用core文件等一些手段查出死在哪了,最惨不忍睹的就是程序没了,core也没了,这真是死得莫名其妙。我们在写socket程序时,也会有这种困扰。
下面我又要开始极尽构造之能事了,客户端代码如下:
client
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORT 1234
#define MAXDATASIZE 1000
int main(int argc, char *argv[])
{
int sockfd, num;
char szbuf[MAXDATASIZE] = {0};
struct sockaddr_in server; if (argc != 2)
{
printf("Usage:%s <IP Address>\n", argv[0]);
exit(1);
} if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("socket()error\n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
printf("connect()error\n");
exit(1);
}
memset(szbuf, 'a', sizeof(szbuf));
while (1)
{
printf("a send\n");
send(sockfd, szbuf, sizeof(szbuf), 0);
sleep(10);
} close(sockfd); return 0;
}
client
然后是服务器代码:
server
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000
int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char szbuf[MAXDATASIZE + 1] = {0};
int num = 0; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Creating socket failed.");
exit(1);
} int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
perror("Bind()error.");
exit(1);
}
if (listen(listenfd, BACKLOG) == -1)
{
perror("listen()error\n");
exit(1);
} addrlen = sizeof(client);
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)
{
perror("accept()error\n");
exit(1);
}
printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));
while (1)
{
memset(szbuf, 0, sizeof(szbuf));
if ((num = recv(connectfd, szbuf, MAXDATASIZE,0)) == -1)
{
printf("recv() error\n");
exit(1);
}
szbuf[num - 1] = '\0';
printf("num = %d, Client Message: %s\n", num, szbuf);
} close(connectfd);
close(listenfd); return 0;
}
server
对于客户端来说,任劳任怨地每隔10s给你服务器喂一次数据,够意思了吧。可服务器不是这么想的,“兄弟,我本来是不想拉你的,可如果你实在不行,就跟我一起去吧”!果然,这两个测试程序最后都挂了,具体该怎么操作呢?
首先,服务器在收到一条数据后,果断Ctrl+C掉,客户端在打印三个“a send”之后,也莫名地一命呜呼了,没有任何信息。奇哉!(这种情况还是很常见的,一个进程如果因为别的进程的异常而导致崩溃,这也是不合理的)
啊哈,这就是因为SIGPIPE了,就是这个信号最终导致我们客户端程序无端牺牲的。不信我们接着看。
SIGPIPE产生的原因也是很明显的,网上一搜一大堆。我们自己也可以抓包看一下(这里就不贴出来了)。服务器挂掉后,客户端的数据再次到达服务器(第二次send),这时服务器会产生一个RST应答,如果客户端再次向服务器发送数据时(第三次send),内核会向客户端进程发送一个SIGPIPE。再看一下系统对该信号的默认处理方式,“终止进程”!所以客户端不崩溃才是奇怪的。
回过头来看,在客户端程序中发生这种异常是不被允许的,怎么办呢?我们对客户端进行修改,代码如下:
修改后client
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <signal.h>
#define PORT 1234
#define MAXDATASIZE 1000
int main(int argc, char *argv[])
{
int sockfd, num;
char szbuf[MAXDATASIZE] = {0};
struct sockaddr_in server; if (argc != 2)
{
printf("Usage:%s <IP Address>\n", argv[0]);
exit(1);
}
signal(SIGPIPE, SIG_IGN); if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
printf("socket()error\n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[1]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
printf("connect()error\n");
exit(1);
}
memset(szbuf, 'a', sizeof(szbuf));
while (1)
{
printf("a send\n");
send(sockfd, szbuf, sizeof(szbuf), 0);
sleep(10);
} close(sockfd); return 0;
}
修改后client
没错,就加一句话signal(SIGPIPE, SIG_IGN)就可以了。
最后测试一下,同样的操作,客户端程序没有终止,而是一直傻不啦叽地在那“苦苦相守”啊!
TCP链路是全双工的,我们不能保证谁向谁发数据。所以,在编写TCP程序时,不管是客户端还是服务器进程,都需要加入对SIGPIPE的处理,这也是必不可少的。
4.recv和recvfrom返回值要注意(这一点没有详细关注过,记录一下,有问题请指出来)
如果拿3中的代码进行测试,很容易发现,服务器和客户端运行正常时,将客户端Ctrl+C掉,这时服务器就陷入了死循环之中。很显然,recv没有返回-1。
纵观所有的套接字函数,一般来说,返回值为-1表示异常或者链路已经断开,像accept或者send等。我们也喜欢使用这些函数的返回值来作为应用层断开链接或者重新初始化的重要依据。为何recv不是呢?另外,对比之前的readn函数和writen函数,readn中有这样一句:
33 else if (nread == 0)
34 {
35 break;
36 }
Recv返回0有什么特殊的含义呢?
只能通过man的方式查看函数的说明:
These calls return the number of bytes received, or -1 if an error occurred. The return value will be 0 when the peer has performed an orderly shutdown.网络断开时,返回0,应该是表示对端已经关闭。
为什么Recv需要返回0这种特殊的状态呢?想到一个解释,只能做参考。希望知道确定答案的人能分享一下。
对于send来说,只需要区分网络正常和异常两种状态,正常就发送,异常就关闭,无需过多地处理。但recv不一样,收到的数据,得保证它们的完整性。一般来说,对端正常关闭的,本地收到的数据是完整的,之前收到的数据可以正常取用。可是对于网络异常终结的,之前收到的数据很有可能是不完整的,所以需要丢弃。这两种都表示对端的关闭,可对于本地接收任务来说是截然不同的。正常结束的,返回0,表示结尾,认为正常;异常结束的,返回-1,认为异常。
通过到这里的学习,我们可以推断,当调用readn返回值与期望值不等时,我们就可以认定对端已经“优雅”地关闭了,我们也不用再跑下去了,释放资源重新开始吧!
不管结论怎样,这个细节上的差异,需要引起我们的注意。稍有不慎,就像3中的代码一样,永远进入了一个死循环。
SOCKET网络编程细节问题3的更多相关文章
- SOCKET网络编程细节问题(4)
SOCKET网络编程快速上手(二)——细节问题(4) 5.慢系统调用及EINTR 还记得前面readn和writen函数么?里面有个EINTR,现在就来谈谈这个,这个很重要. Linux世界有个叫信号 ...
- SOCKET网络编程细节问题(2)
SOCKET网络编程快速上手(二)——细节问题(2) 2.TCP数据包接收问题 对初学者来说,很多都会认为:客户端与服务器最终的打印数据接收或者发送条数都该是一致的,1000条发送打印,1000条接收 ...
- SOCKET网络编程细节问题1
SOCKET网络编程快速上手(二)——细节问题(1) 三.细节问题一个也不能少 Socket编程说简单也简单,程序很容易就能跑起来,说麻烦还真是麻烦,程序动不动就出问题.记得刚开始写网络代码的时候,那 ...
- SOCKET网络编程5
SOCKET网络编程快速上手(二)——细节问题(5)(完结篇) 6.Connect的使用方式 前面提到,connect发生EINTR错误时,是不能重新启动的.那怎么办呢,是关闭套接字还是直接退出进程呢 ...
- Py西游攻关之Socket网络编程
新闻 管理 Py西游攻关之Socket网络编程 知识预览 计算机网络 回到顶部 网络通信要素: A:IP地址 (1) 用来标识网络上一台独立的主机 (2) IP地址 = 网络地址 + 主机 ...
- Python面向对象进阶和socket网络编程-day08
写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...
- Python面向对象进阶和socket网络编程
写在前面 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __init__(self ...
- Linux Socket 网络编程
Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...
- Python Socket 网络编程
Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...
随机推荐
- mac github工具将命令当下来的代码拖入macgithub中就可以
mac github工具将命令当下来的代码拖入macgithub中就可以,刚開始傻傻的就知道点击那个加入button,总是在当下来的文件夹下创建个文件夹.并且代码不能同步
- Boost Lockfree
Boost Lockfree flyfish 2014-9-30 为了最大限度的挖掘并行编程的性能考虑使用与锁无关的数据结构来编程 与锁无关的数据结构不是依赖于锁和相互排斥来确保线程安全. Lockf ...
- angularJS之使用指令封装DOM操作
angularJS之使用指令封装DOM操作 创建指令 指令也是一种服务,只是这种服务的定义有几个特殊要求: 必须使用模块的directive()方法注册服务 必须以对象工厂/factory()方法定义 ...
- 兔子--gradle安装和配置
1.下载gradle,下载--all的这个 点击进入下载页 2.下载下来后,解压.配置环境变量. 编辑path , ....;G:\soft\gradle-2.2.1-all\gradle-2.2.1 ...
- php中utf8 与utf-8
原文:php中utf8 与utf-8 相信很多程序员刚开始也会有这样的疑惑,如题,我也是. 其实,他们可以这样来区分. 一.在php和html中设置编码,请尽量统一写成“UTF-8”,这才 ...
- APP 半自适应 WEB页面
特别赶,响应式纯自适应的,有空写了新的发. (在手机上看,页面上看一定乱) <!DOCTYPE html><html> <head> <meta http-e ...
- ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
原文:ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上 ASP.NET MVC默认采用基于标准特性的Model验证机制,但是只有应用在Model ...
- C语言 链表
原文:C语言 链表 最近在复习数据结构,想把数据结构里面涉及的都自己实现一下,完全是用C语言实现的. 自己编写的不是很好,大家可以参考,有错误希望帮忙指正,现在正处于编写阶段,一共将要实现19个功能. ...
- web设计师和前端设计师的互动—前端工程师应该具备的三种思维
如果你是一个天才工程师(马上可以离开),可以独立完成一个很多事情,你可以是一个怪咖,因为我相信没有一个人不会不佩服你.但现实归现实,多数人都不是天才,而我们在职场上也不是单打独斗,我们需要团队合作,需 ...
- 简洁vim配置方案Janus(1)
最近不想在编辑器上花太多的精力,所以找到个不错的解决方案. 在不懂vim配置的前提下也能用的很开心. 1,下载安装Janus(https://github.com/carlhuda/janus) 安装 ...