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

5.慢系统调用及EINTR

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

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

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

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

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

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

 server_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 = 0;
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 = 0; if (sigaction(signo, &act, &oact) < 0)
{
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 + 1] = {0};
int num = 0;
pid_t pid_child; 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);
}
signal_ex(SIGCHLD, sig_chld);
while (1)
{
addrlen = sizeof(client);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)
{
#if 0
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
}
#endif 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));
if (0 == (pid_child = fork()))
{
close(connectfd);
close(listenfd);
printf("child a ha!\n");
sleep(10);
exit(0);
} close(connectfd);
connectfd = -1;
} close(listenfd); return 0;
}
server_eintr

客户端程序:

 client_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] = {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_eintr

运行后,打印如下:

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

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

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

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

 server_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 = 0;
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 = 0; if (sigaction(signo, &act, &oact) < 0)
{
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 + 1] = {0};
int num = 0;
pid_t pid_child; 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);
}
signal_ex(SIGCHLD, sig_chld);
while (1)
{
addrlen = sizeof(client);
printf("start accept!\n");
if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1)
{
#if 1
if (EINTR == errno)
{
printf("EINTR!\n");
continue;
}
#endif 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));
if (0 == (pid_child = fork()))
{
close(connectfd);
close(listenfd);
printf("child a ha!\n");
sleep(10);
exit(0);
} close(connectfd);
connectfd = -1;
} close(listenfd); return 0;
}
server_eintr

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

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

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

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

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

 
 

SOCKET网络编程细节问题(4)的更多相关文章

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

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

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

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

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

    SOCKET网络编程快速上手(二)——细节问题(1) 三.细节问题一个也不能少 Socket编程说简单也简单,程序很容易就能跑起来,说麻烦还真是麻烦,程序动不动就出问题.记得刚开始写网络代码的时候,那 ...

  4. SOCKET网络编程5

    SOCKET网络编程快速上手(二)——细节问题(5)(完结篇) 6.Connect的使用方式 前面提到,connect发生EINTR错误时,是不能重新启动的.那怎么办呢,是关闭套接字还是直接退出进程呢 ...

  5. Py西游攻关之Socket网络编程

    新闻 管理   Py西游攻关之Socket网络编程   知识预览 计算机网络 回到顶部 网络通信要素: A:IP地址   (1) 用来标识网络上一台独立的主机 (2) IP地址 = 网络地址 + 主机 ...

  6. Python面向对象进阶和socket网络编程-day08

    写在前面 上课第八天,打卡: 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __i ...

  7. Python面向对象进阶和socket网络编程

    写在前面 为什么坚持?想一想当初: 一.面向对象进阶 - 1.反射补充 - 通过字符串去操作一个对象的属性,称之为反射: - 示例1: class Chinese: def __init__(self ...

  8. Linux Socket 网络编程

    Linux下的网络编程指的是socket套接字编程,入门比较简单.在学校里学过一些皮毛,平时就是自学玩,没有见识过真正的socket编程大程序,比较遗憾.总感觉每次看的时候都有收获,但是每次看完了之后 ...

  9. Python Socket 网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

随机推荐

  1. Linux下一个C基本的编程----写进Blog在那之前

    展望2周的实习吧. 各种酸甜苦辣.由于公司只是广告.毛承保让我去.严重的歧视.想也想开,争夺.结果让它成为.还是把它写自己的学习经验,我有同样的希望和迷茫的同学.少走一点弯路.行.切入正题: 一.參考 ...

  2. idea类似eclipse鼠标技巧java api信息

    版权声明:本文博客原创文章,博客,未经同意,不得转载.

  3. DevExpress中获取RichTextEdit中RichEditControl的两种方式

    方式一: var rte = sender as RichTextEdit; control = rte.Controls[] as RichEditControl; 方式二: PropertyInf ...

  4. JavaWeb显示器

    本文研究的总结.欢迎转载,但请注明出处:http://blog.csdn.net/pistolove/article/details/44310967 A:监听器的定义      专门用于其它对象身上 ...

  5. 【Bootstrap】兼容IE8、谷歌和其他主流浏览器的观众IMAX风格的页面

    尽管说IE6除了苛刻要求的一部分,我们一直没能考虑,但WIN7内置的浏览器IE8还需要支持. 在本文中,基本这种方法的优点,我个人认为,这是准备少.我们不需要在网上找了很多照片,使材料,你只要准备好了 ...

  6. lcc之内存分配

    现在感觉这本书有些地方没有说透,如果自己的理解能力与基础知识够强的话,理解起来还好,如果不行的话,某些地方理解起来会相当的头疼. 现在来说说内存管理.当初看这一章的时候很快就过去了,感觉很多地方很好理 ...

  7. 使用jquery实现放大镜效果

    原文:使用jquery实现放大镜效果 实现原理 首先,我们讲解一下放大镜效果的实现方式: 方法一:准备一张高像素的大图,当鼠标放到原图上,加载显示大图的对应位置. 方法二:对原图片进行放大,也就是调整 ...

  8. IE中的事件对象

    IE中的事件对象 1)type属性 用于获取事件类型 2)srcElement属性 用于获取事件的目标 3)cancelBubble属性 用于阻止事件冒泡 设置为true表示阻止事件冒泡  设置为fa ...

  9. CSS知识总结之设计模式(持续学习中)

    OOCSS 参考:http://coding.smashingmagazine.com/2011/12/12/an-introduction-to-object-oriented-css-oocss  ...

  10. valid number 判断字符串是否为有效数字

    RT,面试题,给定一个字符串判断是否为科学计数法的有效数字.此题各种繁琐考虑.今天终于学会了用有限状态机来处理.理解之后简洁易懂.在此分享我的理解推导思路,大有收获啊. 网上有解法说先记录每个状态代表 ...