5.慢系统调用及EINTR

还记得前面readn和writen函数么?里面有个EINTR,现在就来谈谈这个,这个很重要。

Linux世界有个叫信号的东西,感觉他就像一位隐士,很少遇到他,而他又无处不在。当你船到桥头时,他从天而降,将你领入另一片天地。(唉,博客再写下去我都可以改行了)前面已经初步窥探了信号的神奇,一个“小小”的SIGPIPE能让我们不知道怎么回事就惨遭“灭门”。那还有其他千千万万的信号呢,是不是也会对我们写的网络程序表现出神奇的现象。答案:有!还有很多!

写到这,才发现自己还没涉及网络编程涉及的那些函数,这也是一项基础知识的学习。在前面知识的基础上,相信很快能掌握那些套接字函数。因此,那些函数的说明也就不罗嗦了,说实话,即使你用错了也影响不大,编译器会帮你发现大部分问题(当然,最好不要有这种偷懒的表现)。继续谈细节。

我们可以发现,诸如:read、write、select、connect、accept这些函数都是阻塞的,阻塞是啥意思?就是说跑到他们,如果条件不合适,程序就只能死等了,下不去了(想到了下水道,呵呵)。

话说死等就死等呗,正好拿他作为条件符合的判断依据。没错,正常想法就是这样,可万一不是因为正常原因让他苏醒,那就不好了,睡着被吵醒脾气还是很大的。有人要笑了,怎么可能,下水道堵了只有等到下水管通了才行啊,这不是很简单的道理吗?可是,我想说,还有一种可能:下水管裂了!!!扯多了,我们看一下测试程序(以accept来做试验):

这次来高级一点的,搞个多进程,我很少用多进程,所以用错了不要笑我。服务器程序:

 #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>
#include <signal.h>
#include <errno.h> #define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000 void sig_chld(int signo)
{
pid_t pid;
int stat = ; pid = wait(&stat);
printf("child %d terminated(stat:%d)\n", pid, stat); return;
} void signal_ex(int signo, void* func)
{
struct sigaction act, oact; act.sa_handler = func;
sigemptyset(&act.sa_mask); //清空此信号集
act.sa_flags = ; if (sigaction(signo, &act, &oact) < )
{
printf("sig err!\n");
} //sigaction(SIGINT, &oact, NULL); //恢复成原始状态
return;
} int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char szbuf[MAXDATASIZE + ] = {};
int num = ;
pid_t pid_child; 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();
} signal_ex(SIGCHLD, sig_chld);
while ()
{
addrlen = sizeof(client);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -)
{
#if 0
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
}
#endif 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)); if ( == (pid_child = fork()))
{
close(connectfd);
close(listenfd);
printf("child a ha!\n");
sleep();
exit();
} close(connectfd);
connectfd = -;
} close(listenfd); return ;
}

server_eintr

客户端程序:

 #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), 0);
sleep();
} close(sockfd); return ;
}

client_eintr

运行后,打印如下:

在刚开始写程序时,我们往往会很相信系统调用,我们总是会认为系统调用很可靠,基本不会失败。注意,只是基本,因为我们说这话的时候本身信心就不足。因此,很多人在调用系统调用后就不考虑返回值、不考虑出错处理了,这是多么危险的一种心态。当然,也有些人,本身对某个系统调用不了解,但他有很强的责任意识,他会认为系统调用一旦出错,那说明系统就出大问题了,该直接退出整个进程。后者当然是无可厚非的,起码主动退出往往比异常退出来得更“温柔”一些。

回到这个例子,很显然,子进程退出后,accept错误返回了,错误处理就是退出整个进程。(这个例子主要是给SIGCHLD设置了一个默认处理函数,子进程退出时,主进程将捕获这个信号)你可以说我刻意构造了这样一个错误,可是,对于一个很大的系统来说,任何行为都可能导致这种“刻意”。

我们回到书上寻找原因,Stevens提到,“我们用慢系统调用来描述那些可能永远堵塞的系统调用(函数调用)”、“适用于慢系统调用的基本规则是:当阻塞于某个慢系统调用的一个进程捕获某个信号且相应信号处理函数返回时,该系统调用可能返回一个EINTR错误。”

我们按照这个思路,修改服务器代码:

 #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>
#include <signal.h>
#include <errno.h> #define PORT 1234
#define BACKLOG 5
#define MAXDATASIZE 1000 void sig_chld(int signo)
{
pid_t pid;
int stat = ; pid = wait(&stat);
printf("child %d terminated(stat:%d)\n", pid, stat); return;
} void signal_ex(int signo, void* func)
{
struct sigaction act, oact; act.sa_handler = func;
sigemptyset(&act.sa_mask); //清空此信号集
act.sa_flags = ; if (sigaction(signo, &act, &oact) < )
{
printf("sig err!\n");
} //sigaction(SIGINT, &oact, NULL); //恢复成原始状态
return;
} int main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char szbuf[MAXDATASIZE + ] = {};
int num = ;
pid_t pid_child; 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();
} signal_ex(SIGCHLD, sig_chld);
while ()
{
addrlen = sizeof(client);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -)
{
#if 1
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
}
#endif 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)); if ( == (pid_child = fork()))
{
close(connectfd);
close(listenfd);
printf("child a ha!\n");
sleep();
exit();
} close(connectfd);
connectfd = -;
} close(listenfd); return ;
}

server_eintr

修改思路为:EINTR预示着内核中断了慢系统调用,当错误返回时,检查该错误,如果是EINTR,我们就重新启动被中断的系统调用。(有很多系统调用是不可重入的,那就不能简单地重启来解决问题了,这个要注意)下面是打印:

打印“EINTR”了,有没有,并且我重新打开客户端后,accept成功了,我们成功规避了EINTR带来的错误。同样,像read,write等其他慢系统调用也可以通过这一方式进行出错处理。这无疑使我们的程序更加健壮。这当然是必须的了。

不过,还有两个注意点:首先,在这个测试代码里,有些人会说我是不是傻啊,现成的signal不用,为啥要自己封装一个函数(用sigaction)?这里面确实是有原因的,有心的人可以测试一下,两者的结果是不同的,至于原因,可以自己去寻找一下。

另外,前面也提到,并不是所有系统调用都能重新启动的,网络编程里也有这样的家伙,像connect。试想一下,connect调用后就发起了TCP的三次握手,这个过程完全取决于网络和底层协议栈的响应了,这个过程一旦开始,就停不下来了,更谈不上再来一次,这点也是很容易想明白的。

那么,connect遇到EINTR错误该怎么办呢?很想这一节给出的。不过想要把这个问题说明白还需要构造测试代码,还是放到下一节吧。TCP编程方面的知识差不多只积累了这么多,相信对于那么厚的《UNIX网络编程》来说,只能算是冰山一角,后续再慢慢学习补充吧。不过,既然是“快速上手”,我也是要对得住标题的,在目前看过的网络程序代码,上述知识基本能成就一个完整、稳健的网络连接程序了(不考虑上层应用的复杂度)。在下一节给出connect的使用方法及完整的TCP客户端和服务器代码。

socket网络编程快速上手(二)——细节问题(4)的更多相关文章

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

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

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

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

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

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

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

    3.SIGPIPE问题 人怕牺牲,我们写的程序也一样,人有死不瞑目,程序又何尝不是?程序跑着跑着,突然就崩掉了.好一点的牺牲前告诉你些打印,差点的也能用core文件等一些手段查出死在哪了,最惨不忍睹的 ...

  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. Java用ZIP格式压缩和解压缩文件

    转载:java jdk实例宝典 感觉讲的非常好就转载在这保存! java.util.zip包实现了Zip格式相关的类库,使用格式zip格式压缩和解压缩文件的时候,须要导入该包. 使用zipoutput ...

  2. 将firebug安装在chrome浏览器上

    一直很喜欢火狐浏览器,原因是火狐的插件很喜欢,几天突然发现firebug这个插件能够安装在chrome浏览器上,震惊,更震惊的是这个好似已经很长时间了,而我猜发现. 具体的具体页面地址是 http:/ ...

  3. IIS7伪静态化URL Rewrite模块

    原文 IIS7伪静态化URL Rewrite模块 在Win7安装了IIS7.5之后,搭建一些网站或者博客,但是IIS7.5本身没有URL Rewrite功能,也就是无法实现网址的伪静态化. 从网上找了 ...

  4. JUnit介绍

    8.1.1  JUnit简介 JUnit主要用来帮助开发人员进行Java的单元测试,其设计非常小巧,但功能却非常强 大. 下面是JUnit一些特性的总结: — 提供的API可以让开发人员写出测试结果明 ...

  5. 关于WIN32.EXE变态木马下载器的解决办法

    一.WIN32.EXE的来源:http://fdghewrtewrtyrew.biz/adv/130/win32.exe 二.运行后的表现:此WIN32.EXE通过80和8080端口访问若干个IP,若 ...

  6. PHP中遍历stdclass object 及 json

    原文:PHP中遍历stdclass object 及 json (从网上找的模拟实例)需要操作的数据: $test=Array ( [0] => stdClass Object ( [tags] ...

  7. C++中出现的计算机术语2

    C-style strings(C 风格字符串) C 程序把指向以空字符结束的字符数组的指针视为字符串.在 C++ 中,字符串字面值就是 C 风格字符串.C 标准库定义了一系列处理这样的字符串的库函数 ...

  8. leetcode第二题--Median of Two Sorted Arrays

    Problem:There are two sorted arrays A and B of size m and n respectively. Find the median of the two ...

  9. navigator获取参数

    <script type="text/javascript" language="javascript"> document.write(" ...

  10. grunt实用总结

    grunt实用总结 文章梗概如下: 如何让Grunt在项目跑起来 初识:Gruntfile.js 术语扫盲:task & target 如何运行任务 任务配置 自定义任务 文件通配符:glob ...