UNIX Network Programming笔记之第五章下篇

第五章上篇主要讲述信号处理,其他异常情况,以及传输的数据格式。
    1. 信号处理
    本篇还会继续使用上篇说到的例子,上篇说到子进程结束会给父进程发送信号SIGCHLD,如果父进程不处理将会导致子进程僵死,可能会耗尽系统的进程资源。使sigaction函数和struct sigaction结构封装signal函数,书中并没有详细说明sigaction结构及函数,下面代码会给出详细结构和函数声明
struct sigaction{
	void (*sa_handler)(int); //处理函数的函数指针
	sigset_t sa_mask;		//信号掩码
	int sa_flags;			//标志符
	void (*sa_sigaction)(int, siginfo_t *, void*);
};
//第一个参数是信号
//第二个参数是处理该信号的sigaction结构指针
//第三个参数是原处理该信号的sigaction结构指针
//成功则返回0, 失败返回<0
int sigaction(int, struct sigaction *act, struct sigaction *oact);
    sigaction是POSIX的接口, 大部分情况下我们都应该使用该接口,以下是signal函数的封装.
typedef void (*SigFunc)(int);
SigFunc signal(int signo, SigFunc func)
{
	struct sigaction act, oact;
	act.sa_handler = func;  //设置处理函数
	sigemptyset(&act.sa_mask); //设置处理函数的信号掩码
	act.sa_flags = 0;   //设置标志
	if(signo == SIGALRM){
#ifdef SA_INTERRUPT
		act.sa_flags |= SA_INTERRUPT; //由该信号中断的系统调用不会重启
#endif
	}
	else{
#ifdef SA_RESTART
		act.sa_flags |= SA_RESTART;  //由该信号中断的系统调用不会重启
#endif
	}


	if(sigaction(signo,&act, &oact) < 0)  //调用信号处理sigaction
		return SIG_ERR;
	return oact.sa_handler;      //返回原有的信号处理函数
}
    再定义信号处理函数sig_chld, 我们可以在服务器代码中加入signal(SIGCHLD, sig_chld),用来捕获SIGCHLD信号清理已终止的子进程。
void sig_chld(int signo)
{
	pid_t pid;
	int stat;


	//wait不能很好处理
	//pid = wait(&stat);
	while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
		//在信号处理函数中调用printf等标准I/O函数是不合适的,只是为了查看子进程id
		printf("child %d terminated\n", pid);
	return;
}
    当SIGCHLD信号递交时,通过上篇可知道父进程阻塞于accept调用,而当阻塞于慢系统调用例如accept捕获到信号,且信号函数返回时该系统调用将会返回EINTR错误。我们必须处理这个错误。
for(;;){
	if((connfd = accept(listenfd, NULL, NULL)) < 0){
		if(errno == EINTR) continue; //处理被中断的系统调用
		else err_sys("accept error");
	}
....
}
    接着讲解wait函数和waitpid函数的原型, 它两的区别,以及为什么上面的代码只使用wait不能很好的处理已终止的子进程。
    #include <sys/wait.h>
    pid_t wait(int *statloc);
    pid_t waitpid(pid_t pid, int *statloc, int options);
    //均返回:成功子进程id,失败为0或-1
    wait和waitpid都可以通过statloc指针返回子进程终止状态(整数)。如果调用wait, 没有子进程终止但有一个或多个子进程在执行,那么wait会一直阻塞到现有子进程第一个终止为止;而waitpid可以就等待的进程以及是否阻塞给与我们更多的控制,pid参数允许我们指定等待的进程ID,-1表示等待第一个终止的进程(还可以指定进程组)。options参数指定附加的选项, 最常用的是WNOHANG,表示没有已终止的进程的时候不要阻塞。
    为什么信号处理函数用wait不好用?当我们和server建立多个连接,而只用一个连接来传输数据时,还会有终止的子进程变成僵死进程。例如client使用5个socket与server建立连接,server将会有5个子进程与client连接, 然后client只用一个socket来传输数据, 当client进程结束的时候,关闭所有描述符,client的每个连接的socket都会发送FIN分组给server的子进程socket,5个子进程将会几乎同时终止,返回5个SIGCHLD信号,而使用wait的信号处理函数只能处理一个或几个。因为5个信号都在信号处理函数之前产生,而信号处理函数只会执行一次,更严重的是,问题是不确定的。正确方法是使用waitpid而不是使用wait函数。

     目前在网络编程会遇到3个问题:1. 当fork子进程,子进程终止必须捕获SIGCHLD信号;2. 当捕获信号的时候,必须处理被中断的系统调用;3. SIGCHLD信号处理函数必须正确编写。


    2. 其他异常
    a. accept 返回前连接中止
    连接建立,客户TCP却发送一个RST。在服务器看来,该连接已由TCP排队,等着服务器进程调用accept的时候RST到达(这里本人不懂,书中前面的内容accept调用不是在第二路握手开始的时候,返回在第三次握手结束的时候吗,如有人懂,可评论告知,谢谢)
    b. 服务器进程终止
    建立连接后,client输入数据,可以回显,此时kill子进程,会怎样?
    服务器子进程被kill,关闭打开的连接套接字描述符,将向client发送FIN,而client响应一个ACK,这就是连接终止的前半部分。SIGCHLD信号能够被父进程正确处理,而此时client阻塞在fgets,当用户输入数据,client TCP会把输入数据发送给服务器,可以这么做,因为client接收到FIN,只是表示server已关闭连接的server端,不会再往client发送数据而已,FIN的接收并不会告知server进程终止 。本例中的server的进程确实终止了,server端接收到client的数据,将会返回一个RST。client在writen之后调用readline,readline返回0,出现我们自己打印的错误,服务器过早终止。最后client进程终止,它所有打开的描述符关闭。
    c. SIGPIPE信号
    当一个进程向某个已收到RST的套接字执行写操作时,内核向进程发送SIGPIPE信号,该信号默认动作是终止进程。
    调用两次writen: 第一次将数据第一个字节写入套接字中,睡眠1s,然后再写入剩下的数据。第一次是为了返回RST,第二次writen返回SIGPIPE。
    d. 服务器主机崩溃
    服务器主机崩溃, 已有的网络连接上不发出任何东西
    用户输入数据,client往套接字调用writen,并阻塞在readline,等待应答,客户TCP会持续重传数据分节,直到超时放弃重传。有时候我们希望更快的检测这种情况,所用方法就是给readline设置个超时。
    e. 服务器主机崩溃后重启
    当服务器主机崩溃重启时,它的TCP丢失了崩溃前的所有连接信息,因此会响应一个RST。当client接收到RST,此时它正阻塞到readline调用,导致该调用返ECONNRESET错误。我们需要采用某种技术,SO_KEEPALIVE 套接字选项,即使client不主动发送数据也能检出这种情况
    f. 服务器主机关机

    关机时,init进程会给所有进程发送SIGTERM信号,一段时间后,给仍在运行的进程发送SIGKILL信号。当服务器子进程终止时,它所有的打开的描述符都将关闭(和情形b一致)。使用select/poll函数,服务器进程终止这种情况一经发生就能检测。


    3. 数据格式
    使用二进制格式进行传输会有潜在问题:不同实现以不同的格式存储二进制数,大小端;存储的相同的C数据类型可能有差异,32位,64位系统;结构打包方式不同,字节对齐问题。
    有两个常用方法:将所有数值数据作为文本串传递;显示定义二进制格式,并以这样的格式在client和server传递所有数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值