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 ...
随机推荐
- shell变量赋值进阶
首先,要理解shell中变量的3种赋值情况: unset 例子. unset a 空字符串, null 例子. a='' 非空,即不是unset,并且不是空字符串 例子: a=1 or a=b等 然后 ...
- oracle_job 清空冗余数据 ,每一分钟执行一次
参照这个例子:http://cherryqq.iteye.com/blog/855022 思路: data表中有4条数据 ,relation有3条数据,通过data_id 对应,需要定时删除 data ...
- app后端设计(0)--总文件夹
原文:http://blog.csdn.net/newjueqi/article/details/19003775 做了接近两年app相关的系统架构,api设计,先后在两个创业公司中工作,经历过手机网 ...
- sql server 辅助工具
sql Prompt 智能感知, 也是需要注册机. 效果如图: 这里最好改下:
- 关于WebBrowser.DocumentCompleted事件
原文:关于WebBrowser.DocumentCompleted事件 今天发现使用WebBrowser时载入一个页面后DocumentCompleted事件会被调用2次,后来发现这两次WebBrow ...
- Introducing Visual Studio’s Emulator for Android
visual studio 2015支持Android开发了. Microsoft released Visual Studio 2015 Preview this week and with it ...
- The Swift Programming Language-官方教程精译Swift(1)小试牛刀
通常来说,编程语言教程中的第一个程序应该在屏幕上打印“Hello, world”.在 Swift 中,可以用一行代码实现: println("hello, world") 如果你 ...
- 吐槽一下项目中的代码坏味道:滥用java常量
我们的项目中是否充斥着类似以下的代码呢?定义一个专门存放常量的java类(接口),非常多其它类依赖该常量类. public interface IConstant { int ZERO = 0; St ...
- 七牛云存储C#例用小例子 C#笔记
最近有需求要把网站的图片传到云存储上去.于是就找到了七牛.看上面的SDK...看了才发现里面注释很少.当时看的头大.在网上搜一下,基本上没有好的例子.给初学者带来很大的不便!不过最后还是做出来了.在这 ...
- 第4章2节《MonkeyRunner源码剖析》ADB协议及服务: ADB服务SERVICES.TXT翻译参考(原创)
天地会珠海分舵注:本来这一系列是准备出一本书的,详情请见早前博文“寻求合作伙伴编写<深入理解 MonkeyRunner>书籍“.但因为诸多原因,没有如愿.所以这里把草稿分享出来,所以错误在 ...