网络编程学习(二)

#include <sys/wait.h>

pid_t wait(int *statloc);

pid_t wait(pid_t pid, int *statloc, int options);

函数wait和waitpid均返回两个值,已终止子进程的进程ID号,以及通过statloc指针返回的子进程终止状态(一个整数)。我们可以调用三个宏来检查终止状态,并辨别子进程是正常终止、由某个信号杀死还是仅仅由作业控制停止而已。另有些宏用于接着获取子进程的退出状态、杀死子进程的信号值或停止子进程的作业控制信号值。

如果调用wait的进程没有已终止的子进程,不过有一个或多个子进程仍在执行,那么wait将阻塞到现有子进程第一个终止为止。

waitpid函数就“等待哪个进程”以及“是否阻塞”给了我们更多的控制。

void sig_chld(int signo)
{
    pid_t pid;
    int stat;

    pid = wait(&stat);
    printf("child %d terminated\n", pid);
    return;    //unified
}


建立一个信号处理函数并在其中调用wait并不足以防止出现僵死进程。本问题在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的。正确的解决办法是调用waitpid而不是wait,我们在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,它告知waitpid在有尚未终止的子进程在运行时不要阻塞。

void sig_chld_pid(int signo)
{
    pid_t pid;
    int stat;
    while((pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    return;
}



网络编程可能会遇到:

1.当fork子进程时,必须捕获SIGCHLD信号;

2.当捕获信号时,必须处理被中断的系统调用;

3.SIGCHLD的信号处理函数必须正确编写,应使用waitpid函数以免留下僵死进程。


accept返回前连接中止

三路握手完成从而连接建立之后,客户TCP却发送了一个RST(复位)。在服务器端看来,就在该连接已由TCP排队,等着服务器进程调用accept的时候RST到达。稍后,服务器进程调用accept。

模拟:启动服务器,让它调用socket、bind和listen,然后在调用accept之前睡眠一小段时间。在服务器进程睡眠时,启动客户,让它调用socket和connect。一旦connect返回,就设置SO_LINGER套接字选项以产生这个RST,然后终止。

Berkeley:内核不将错误传递给进程,RST -> tcp_close -> in_pcbdetach ->sofree,发现待中止的连接仍在监听套接字的已完成连接队列中,于是从该队列中删除该连接,并释放相应的已连接的套接字。当服务器最终调用accept函数时,它根本不知道曾经有一个已完成的连接稍后被从已完成连接队列中删除了。

SVR4:EPROTO(协议错误)

POSIX:ECONNABORTED(软件引起的连接中止)

POSIX作出修改的理由在于:流子系统(streams subsystem)中发生某些致命的协议相关事件时,也会返回EPROTO。要是对于由客户引起的一个已建立连接的非致命中止也返回同样的错误,那么服务器就不知道该再次调用accept还是不该了。换成ECONNABORTED错误,服务器就可以忽略它,再次调用accept就行。


当一个进程向某个已收到RST的套接字执行写操作时,内核向该进程发送一个SIGPIPE信号。该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿地被终止。

不论进程是否捕获了该信号并从其信号处理函数返回,还是简单地忽略该信号,写操作都将返回EPIPE错误。

一个在Usenet上经常问及的问题(frequently asked quesetion, FAQ)是如何在第一次写操作时而不是在第二次写操作时捕获该信号。这是不可能的。遵照上述讨论,第一次写操作引发RST,第二次写引发SIGPIPE信号。写一个已接受了FIN的套接字不成问题,但是写一个已接受了RST的套接字则是一个错误。

处理SIGPIPE的建议方法取决于它发生时应用进程想做什么。

如果没有特殊的事情要做,那么将信号处理办法直接设置为SIG_IGN,并假设后续的输出操作将捕捉EPIPE错误并终止。

如果信号出现时需采取特殊措施(可能需在日志文件中登记),那么就必须捕获该信号,以便在信号处理函数中执行所有期望的动作。但是必须意识到,如果使用了多个套接字,该信号的递交无法告诉我们是哪个套接字出的错。如果我们确实需要知道是哪个write出了错,那么要么不理会该信号,要么从信号处理函数返回后再处理来自write的EPIPE。


假设服务器主机已崩溃,从而对客户的数据分节根本没有响应,那么所返回的错误是ETIMEDOUT。然而如果某个中间路由器判定服务器主机已不可达,从而响应一个“destination unreachable”(目的地不可达)ICMP消息,那么所返回的错误是EHOSTUNREACH或ENETUNREACH。

如果对客户而言检测服务器主机崩溃与否很重要,即使客户不主动发送数据也要能检测出来,就需要采用其他某种技术(诸如SO_KEEPAIVE套接字选项或某些客户/服务器心博函数)。

Unix系统关机时,init进程通常先给所有进程发送SIGTERM信号(该信号可被捕获),等待一段固定的时间(往往在5s到20s之间),然后给所有人在运行的进程发送SIGKILL信号(该信号不能被捕获)。这么留给所有运行的进程一小段时间来清除和终止。如果我们不捕获SIGTERN信号并终止(也即捕获后忽略它),我们的服务器将由SIGKILL信号终止。当服务器子进程终止时,它的所有打开着的描述符都被关闭,随后发生的步骤同结束服务器进程。


如果把客户和服务器程序修改为穿越套接字传递二进制值(而不是文本串)。我们将看到,当这样的客户和服务器程序运行在字节序不一样的或者所支持长整数的大小不一致的两个主机上时,工作将失常。

1.不同的实现以不同的格式存储二进制数。(大端字节序与小端字节序)

2.不同的实现在存储相同的C数据类型上可能存在差异。(32/64位系统)

3.不同的实现给结构打包的方式存在差异,取决于各种数据类型所用的位数以及机器的对齐限制。

因此,穿越套接字传递二进制结构绝不是明智的。

解决这种数据格式问题有两个常用方法:

1.把所有的数值数据作为文本串来传递。当然这里假设客户和服务器主机具有相同的字符集。

2.显示定义所支持数据类型的二进制格式(位数、大端或小端字节序),并以这样的格式在客户与服务器之间传递所有数据。远程过程调用(Remote Procedure Call, RPC)软件包通常使用这种技术。

你可能感兴趣的:(网络编程学习(二))