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. 玩转Vim-札记

    玩转Vim-札记 距上篇博文已有一周有余,上次主要介绍了编辑器之神Vim的起源.安装并介绍了两种模式以及一些简单的操作.本次将继续对Vim的使用进行介绍. 登堂入室 首先接着说移动吧: 1 0 → 数 ...

  2. java OutOfMorryError (replaceAll)

    最近在使用string类中的replaceAll函数时碰到这个错误,由于string长度比较长,文本文档9M多,可以增加jvm的内存大小解决. 下面是一篇对OutOfMorryError错误的一些处理 ...

  3. jquery 重复事件

    采用jquery时刻.其他活动均达到. 相反1对1的. 例如:onclick="$(document).keypress(function (key) {  if (key.keyCode ...

  4. js 利用iframe和location.hash跨域解决的方法,java图片上传回调JS函数跨域

    奶奶的:折腾了我二天,最终攻克了!网上有非常多样例. 但跟我的都不太一样,费话不多说了,上图   上代码: IE ,firefix,chrome 測试通过 js :这个主页面,部分代码, functi ...

  5. 完整具体解释GCD系列(二)dispatch_after;dispatch_apply;dispatch_once

    原创Blog,转载请注明出处 本文阅读的过程中,如有概念不懂,请參照前专栏中之前的文章,假设还有疑惑,请留言. 这是我关于GCD专栏的地址 http://blog.csdn.net/column/de ...

  6. [CLR via C#]1.4 执行程序集的代码

    原文:[CLR via C#]1.4 执行程序集的代码 1. 托管程序集同时包含元数据和IL.IL是与CPU无关的机器语言.可将IL是为一种面向对象的机器语言. 2. IL也是能使用汇编语言来写的,M ...

  7. API帮助页面

    ASP.NET Web API 2:创建API帮助页面        当你新建了一个web API服务之后,再建一个API帮助页面是很有好处的,这样其他开发人员就会很清楚地知道如何调用你的API接口. ...

  8. SOD框架的数据容器,打造最适合DDD的ORM框架

    SOD框架的数据容器,打造最适合DDD的ORM框架 引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DD ...

  9. 转载:Eclipse+Spket插件+ExtJs4修改版提供代码提示功能[图]

    转载:Eclipse+Spket插件+ExtJs4修改版提供代码提示功能[图] ExtJs是一种主要用于创建前端用户界面,是一个基本与后台技术无关的前端ajax框架.功能丰富,无人能出其右.无论是界面 ...

  10. 利用svn自动同步更新到网站服务器 -- 网摘

    首先在服务器上安装VisualSVN Server ,根据提示选好安装的路径,一路确定.安装好后运行VisualSVN Server ,在Repositories上点击右键,选择create New ...