3.SIGPIPE问题

人怕牺牲,我们写的程序也一样,人有死不瞑目,程序又何尝不是?程序跑着跑着,突然就崩掉了。好一点的牺牲前告诉你些打印,差点的也能用core文件等一些手段查出死在哪了,最惨不忍睹的就是程序没了,core也没了,这真是死得莫名其妙。我们在写socket程序时,也会有这种困扰。

下面我又要开始极尽构造之能事了,客户端代码如下:

 #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] = {};
struct sockaddr_in server; if (argc != )
{
printf("Usage:%s <IP Address>\n", argv[]);
exit();
} if ((sockfd=socket(AF_INET, SOCK_STREAM, )) == -)
{
printf("socket()error\n");
exit();
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -)
{
printf("connect()error\n");
exit();
} memset(szbuf, 'a', sizeof(szbuf));
while ()
{
printf("a send\n");
send(sockfd, szbuf, sizeof(szbuf), );
sleep();
} close(sockfd); return ;
}

client

然后是服务器代码:

 #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 + ] = {};
int num = ; if ((listenfd = socket(AF_INET, SOCK_STREAM, )) == -)
{
perror("Creating socket failed.");
exit();
} 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)) == -)
{
perror("Bind()error.");
exit();
}
if (listen(listenfd, BACKLOG) == -)
{
perror("listen()error\n");
exit();
} addrlen = sizeof(client);
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -)
{
perror("accept()error\n");
exit();
}
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 ()
{
memset(szbuf, , sizeof(szbuf));
if ((num = recv(connectfd, szbuf, MAXDATASIZE,)) == -)
{
printf("recv() error\n");
exit();
}
szbuf[num - ] = '\0';
printf("num = %d, Client Message: %s\n", num, szbuf);
} close(connectfd);
close(listenfd); return ;
}

server

对于客户端来说,任劳任怨地每隔10s给你服务器喂一次数据,够意思了吧。可服务器不是这么想的,“兄弟,我本来是不想拉你的,可如果你实在不行,就跟我一起去吧”!果然,这两个测试程序最后都挂了,具体该怎么操作呢?

首先,服务器在收到一条数据后,果断Ctrl+C掉,客户端在打印三个“a send”之后,也莫名地一命呜呼了,没有任何信息。奇哉!(这种情况还是很常见的,一个进程如果因为别的进程的异常而导致崩溃,这也是不合理的)

啊哈,这就是因为SIGPIPE了,就是这个信号最终导致我们客户端程序无端牺牲的。不信我们接着看。

SIGPIPE产生的原因也是很明显的,网上一搜一大堆。我们自己也可以抓包看一下(这里就不贴出来了)。服务器挂掉后,客户端的数据再次到达服务器(第二次send),这时服务器会产生一个RST应答,如果客户端再次向服务器发送数据时(第三次send),内核会向客户端进程发送一个SIGPIPE。再看一下系统对该信号的默认处理方式,“终止进程”!所以客户端不崩溃才是奇怪的。

回过头来看,在客户端程序中发生这种异常是不被允许的,怎么办呢?我们对客户端进行修改,代码如下:

 #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] = {};
struct sockaddr_in server; if (argc != )
{
printf("Usage:%s <IP Address>\n", argv[]);
exit();
} signal(SIGPIPE, SIG_IGN); if ((sockfd=socket(AF_INET, SOCK_STREAM, )) == -)
{
printf("socket()error\n");
exit();
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = inet_addr(argv[]);
if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -)
{
printf("connect()error\n");
exit();
} memset(szbuf, 'a', sizeof(szbuf));
while ()
{
printf("a send\n");
send(sockfd, szbuf, sizeof(szbuf), );
sleep();
} close(sockfd); return ;
}

修改后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)的更多相关文章

  1. socket网络编程快速上手(二)——细节问题(5)(完结篇)

    6.Connect的使用方式 前面提到,connect发生EINTR错误时,是不能重新启动的.那怎么办呢,是关闭套接字还是直接退出进程呢?如果EINTR前,三次握手已经发起,我们当然希望链路就此已经建 ...

  2. socket网络编程快速上手(二)——细节问题(4)

    5.慢系统调用及EINTR 还记得前面readn和writen函数么?里面有个EINTR,现在就来谈谈这个,这个很重要. Linux世界有个叫信号的东西,感觉他就像一位隐士,很少遇到他,而他又无处不在 ...

  3. socket网络编程快速上手(二)——细节问题(1)

    三.细节问题一个也不能少 Socket编程说简单也简单,程序很容易就能跑起来,说麻烦还真是麻烦,程序动不动就出问题.记得刚开始写网络代码的时候,那真是令人抓狂的经历,问题一个套一个,一会服务器起不来了 ...

  4. socket网络编程快速上手(一)

    工作以来,写了很多socket相关的代码.磕磕碰碰,走了很多弯路,也积累了一些东西,今天正好整理一下.为了证明不是从书上抄来的,逻辑会有点乱(借口,呵呵)!知识点的介绍也不会像书上说的那么详细和精准, ...

  5. socket网络编程快速上手(二)——细节问题(2)

    2.TCP数据包接收问题 对初学者来说,很多都会认为:客户端与服务器最终的打印数据接收或者发送条数都该是一致的,1000条发送打印,1000条接收打印,长度都为1000.但是,事实上并不是这样,发送打 ...

  6. Java网络编程快速上手(SE基础)

    参考资料:百度百科TCP协议 本文涉及Java IO流.异常的知识,可参考我的另外的博客 一文简述Java IO 一文简述JAVA内部类和异常 1.概述 计算机网络相关知识: OSI七层模型 一个报文 ...

  7. SOCKET网络编程细节问题(4)

    SOCKET网络编程快速上手(二)——细节问题(4) 5.慢系统调用及EINTR 还记得前面readn和writen函数么?里面有个EINTR,现在就来谈谈这个,这个很重要. Linux世界有个叫信号 ...

  8. SOCKET网络编程细节问题3

    SOCKET网络编程快速上手(二)——细节问题(3) 3.SIGPIPE问题 人怕牺牲,我们写的程序也一样,人有死不瞑目,程序又何尝不是?程序跑着跑着,突然就崩掉了.好一点的牺牲前告诉你些打印,差点的 ...

  9. SOCKET网络编程细节问题(2)

    SOCKET网络编程快速上手(二)——细节问题(2) 2.TCP数据包接收问题 对初学者来说,很多都会认为:客户端与服务器最终的打印数据接收或者发送条数都该是一致的,1000条发送打印,1000条接收 ...

随机推荐

  1. mysql_常用命令

    1: 以指定编码创建数据库 CREATE DATABASE `search_data` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci

  2. MAC使用小技巧(二)

    一.Safari-->广告数量不足 --原因:DNS被拦截,被恶意推送广告. ----------------------------- [ 思路 ] 修改hosts文件 $ cd /etc $ ...

  3. FPGA笔记-阅读.dat文件

    阅读.dat图像文件 .dat文件是matlab生成的图像文件 initial begin // Initialize Inputs CLK = 0; RST = 1; IMAGE_DATA = 0; ...

  4. Socket 学习(三).4 UDP 穿透 客户端与客户端连接

    效果图: 使用方法:  先 修改WinClient\bin\Debug  下面的 ip.ini,写上 服务器 IP地址. 客户端 与 客户端 通讯 之前 ,点击发送打洞消息 按钮,然后过一会再发送消息 ...

  5. SHDocVw, AxSHDocVw的引用

    原文:SHDocVw, AxSHDocVw的引用 SHDocVw的引用SHDocVw一定要在下面这个路径找: 类似 C:\Program Files\Microsoft Visual Studio 9 ...

  6. 多线程学习之三生产者消费者模式Guarded Suspension

    Guarded Suspension[生产消费者模式] 一:guarded suspension的参与者--->guardedObject(被防卫)参与者                1.1该 ...

  7. Serialization performance analysis

    Serialization performance analysis http://www.skyscanner.net/blogs/serialization-performance-analysi ...

  8. CSS3字体模块

    介绍 字体提供了包含字符的视觉表现的资源.在最简单的等级中,其包含由字符编码到表示这些字符的形状(被称为字形)的映射信息.根据一组标准字体属性被分入一个字体家族的字体共享一个通用设计风格.在一个家族中 ...

  9. 一步一步写算法(之n!中末尾零的个数统计)

    原文:一步一步写算法(之n!中末尾零的个数统计) [ 声明:版权所有,欢迎转载,请勿用于商业用途.  联系信箱:feixiaoxing @163.com] 在很多面试的题目中,求n!结果中零的个数也是 ...

  10. MPEG-DASH on IIS Practice in Action-attach

    1. 准备  IIS Media Service已经安装准备好(如上),注意需要在2008上安装,2003安装不了,或者就算能安装也很麻烦 一个或多个码率的媒体文件如MP4已经准备好 DASH MPD ...