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 ...
随机推荐
- hdu(2062)-Subset sequence 组合数学
意甲冠军:查找集合{1,2,3...n}第一m一个排列子. 收集的线索所行的大小. 例两个元素的排列子集合按字典树排列是:{1},{1,2},{2},{2,1}: 解法:一个一个元素来确定,每次把剩余 ...
- .net中用Action等委托向外传递参数
原文:.net中用Action等委托向外传递参数 一般我们可以使用ref,out达到向外传递参数目的. Action<T>是一个特殊的委托,除了常规应用.我们还可以用它来实现简单 ...
- 08. 删除重复&海量数据
原文:08. 删除重复&海量数据 重复数据,通常有两种:一是完全重复的记录,也就是所有字段的值都一样:二是部分字段值重复的记录. 一. 删除完全重复的记录完全重复的数据,通常是由于没有设置主键 ...
- Swift入门教程:基本语法(三)
打印输出 Swift提供了2个打印输出函数 println :输出内容后会自动换行 print :对比println,少了个自动换行的功能 示例 输出字符串 println("欢迎学习传智播 ...
- 勾选Create git respository的作用
在Xcode中创建项目时会弹出Source Control选项,勾选Create git repository选项可以帮助我们对照以前项目中代码中修改的部分,为开发提供方便. 在项目完成到一定程度时, ...
- Java Persistence with MyBatis 3(中国版) 第五章 与Spring集成
MyBatis-Spring它是MyBatis子模块框.它用来提供流行的依赖注入框架Spring无缝集成. Spring框架是一个基于依赖注入(Dependency Injection)和面向切面编程 ...
- navicat连接oracle一个错误:ORA-12737 Instant Client Light:unsupported server character set ZHS16GBK
今天使用Navicat连接Oracle数据库.它报告了以下错误:"ORA-12737 Instant Client Light:unsupported server character se ...
- Ninject.Extensions.
最近在使用IoC进行一个较复杂的项目进行架构,在IoC的选择上让我很是纠结.首先我不喜欢大量的配置文件进行配置,那简直是噩梦,比学习一门编程语言还痛苦.我喜欢前一段时间看EF的CodeFirst的那种 ...
- View & ViewData
ViewData 似乎没啥好说的,一个向 View 传送数据的字典. ----------------------------------------------------------------- ...
- 1.1 什么是LinQ
如今,软件应用环境越来越多样化,软件需要处理的数据量也日渐庞大,数据之间的关系日渐复杂.从而带动了存储技术的不断发展,越来越多的数据存储格式被应用到各种软件中. 通常,针对数据的查询是用简单的字符串文 ...