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. Xcode6为什么干掉pch(Precompile Prefix Header)&amp;怎样加入pch文件

    一直在用xcode6开发,但项目都是在xcode5上创建的,所以一直没注意到,xcode6居然干掉pch文件了. 为什么xcode6没有自己主动创建pch文件呢? 简单地看:我们在写项目的时候,大部分 ...

  2. UVA Getting in Line

    题目例如以下: Getting in Line  Computer networking requires that the computers in the network be linked. T ...

  3. Web API 2 对 CORS 的支持

    Web API 2 对 CORS 的支持 CORS概念 跨域资源共享 (CORS) 是一种万维网联合会 (W3C) 规范(通常被认为是 HTML5 的一部分),它可让 JavaScript 克服由浏览 ...

  4. js checkbox多选值采集

    var objs = document.getElementsByTagName("input"); for (var i = 0; i < objs.length; i++ ...

  5. nodeJS起步 1

    nodeJS起步 -- (1) 先来简单介绍nodeJS 我们知道JavaScript是运行在浏览器中的,浏览器为它提供了一个上下文(context),从而让JavaScript得以解析执行. nod ...

  6. 如何将经纬度利用Google Map API显示C# VS2005 Sample Code

    原文 如何将经纬度利用Google Map API显示C# VS2005 Sample Code 日前写了一篇如何用GPS抓取目前所在,并回传至资料库储存,这篇将会利用这些回报的资料,将它显示在地图上 ...

  7. 一张地图,告诉你NodeJS命令行调试器语句

    NodeJS提供脚本调试. 进入node debug xx.js您可以进入调试模式. 版权声明:本文博客原创文章,博客,未经同意,不得转载.

  8. 一个简单的创建dom的函数

    var  regName = /^(div|a|p|ul|li|input|select|document|body|iframe)$/;function createDom(name, obj) { ...

  9. 安装Windows8.1操作系统 - 初学者系列 - 学习者系列文章

    Windows 8这款操作系统是微软最新的操作系统.虽然微软做了推广,但是据消息称市场份额暂时没那么高.下面就对该操作系统的安装进行简要介绍. 1.  将光盘装入光驱,设置BIOS中光驱启动,启动计算 ...

  10. NFTS数据流

    NFTS数据流 NTFS交换数据流(alternate data streams,简称ADS)是NTFS磁盘格式的一个特性,在NTFS文件系统下,每一个文件都能够存在多个数据流,就是说除了主文件流之外 ...