TCP Client/Server 要考虑的问题

在一个子进程一个client的模式下,当子进程结束时,父进程不对子进程wait时,子进程变成僵尸进程。

A signal is a notification to a process that an event has occurred. Signals are sometimes called software interrupts. Signals usually occur asynchronously. By this we mean that a process doesn't know ahead of time exactly when a signal will occur.

Signals can be sent

  1. By one process to another process (or to itself)
  2. By the kernel to a process

The SIGCHLD signal is one that is sent by the kernel whenever a process terminates, to the parent of the terminating process.

Every signal has a disposition, which is also called the action associated with the signal. We set the disposition of a signal by calling the sigaction function (described shortly) and we have three choices for the disposition:

  1. We can provide a function that is called whenever a specific signal occurs. This function is called a signal handler and this action is called catching a signal. The two
    signals SIGKILL and SIGSTOP cannot be caught. Its function prototype is therefore
    void handler (int signo);
  2. We can ignore a signal by setting its disposition to SIG_IGN. The two signals SIGKILL and SIGSTOP cannot be ignored.
  3. We can set the default disposition for a signal by setting its disposition to SIG_DFL. The default is normally to terminate a process on receipt of a signal, with certain
    signals also generating a core image of the process in its current working directory. There are a few signals whose default disposition is to be ignored: SIGCHLD and
    SIGURG (sent on the arrival of out-of-band data) .

POSIX allows us to specify a set of signals that will be blocked when our signal handler is called. Any signal that is blocked cannot be delivered to a process. We set the sa_mask member to the empty set, which means that no additional signals will be blocked while our signal handler is running. POSIX guarantees that the signal being caught is always blocked while its handler is executing.

SA_RESTART is an optional flag. When the flag is set, a system call interrupted by this signal will be automatically restarted by the kernel.  If the signal being caught
is not SIGALRM, we specify the SA_RESTART flag, if defined. (The reason for making a special case for SIGALRM is that the purpose of generating this signal is normally to place a timeout on an I/O operation, in which case, we want the blocked system call to be interrupted by the signal.) Some older systems, notably SunOS 4.x,automatically restart an interrupted system call by default and then define the complement of this flag as SA_INTERRUPT. If this flag is defined, we set it if the signal being caught is SIGALRM.

If a signal is generated one or more times while it is blocked, it is normally delivered only one time after the signal is unblocked. That is, by default, Unix signals are not queued.

It is possible to selectively block and unblock a set of signals using the sigprocmask function. This lets us protect a critical region of code by preventing certain signals from being caught while that region of code is executing.

The purpose of the zombie state is to maintain information about the child for the parent to fetch at some later time. This information includes the process ID of the child, its
termination status, and information on the resource utilization of the child (CPU time, memory, etc.). If a process terminates, and that process has children in the zombie state,
the parent process ID of all the zombie children is set to 1 (the init process), which will inherit the children and clean them up (i.e., init will wait for them, which removes the zombie). Some Unix systems show the COMMAND column for a zombie process as <defunct>.

Obviously we do not want to leave zombies around. They take up space in the kernel and eventually we can run out of processes. Whenever we fork children, we must wait for
them to prevent them from becoming zombies. To do this, we establish a signal handler to catch SIGCHLD, and within the handler, we call wait.

when writing network programs that catch signals, we must be cognizant of interrupted system calls, and we must handle them.

We used the term "slow system call" to describe accept, and we use this term for any system call that can block forever. That is, the system call need never return. Most networking functions fall into this category.

The basic rule that applies here is that when a process is blocked in a slow system call and the process catches a signal and the signal handler returns, the system call can return an error of EINTR.  For portability, when we write a program that catches signals (most concurrent servers catch SIGCHLD), we must be prepared for slow system calls to return EINTR.

for ( ; ; ) {
clilen = sizeof (cliaddr);
if ( (connfd = accept (listenfd, (SA *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue; /* back to for () */
else
err_sys ("accept error");
}

What we are doing in this piece of code is restarting the interrupted system call. This is fine for accept, along with functions such as read, write, select, and open. But there is
one function that we cannot restart: connect. If this function returns EINTR, we cannot call it again, as doing so will return an immediate error.

#include <sys/wait.h>
pid_t wait (int *statloc);
pid_t waitpid (pid_t pid, int *statloc, int options);
Both return: process ID if OK, 0 or -1 on error

we called the wait function to handle the terminated child.

If there are no terminated children for the process calling wait, but the process has one or more children that are still executing, then wait blocks until the first of the existing
children terminates.

waitpid gives us more control over which process to wait for and whether or not to block. First, the pid argument lets us specify the process ID that we want to wait for. A value of
-1 says to wait for the first of our children to terminate.  The options argument lets us specify additional options. The most common option is WNOHANG. This option tells the
kernel not to block if there are no terminated children.

  1. We must catch the SIGCHLD signal when forking child processes.
  2. We must handle interrupted system calls when we catch signals.
  3. A SIGCHLD handler must be coded correctly using waitpid to prevent any zombies from being left around.

当三次握手完成,在accept调用之前,client发送了一个RST。此情况的处理各种实现不同,只要重新调用下accept就可以了。

Unfortunately, what happens to the aborted connection is implementation-dependent. Berkeley-derived implementations handle the aborted connection completely within the
kernel, and the server process never sees it. Most SVR4 implementations, however, return an error to the process as the return from accept, and the error depends on the
implementation. These SVR4 implementations return an errno of EPROTO ("protocol error"), but POSIX specifies that the return must be ECONNABORTED ("software caused connection abort") instead. The reason for the POSIX change is that EPROTO is also returned when some fatal protocol-related events occur on the streams subsystem. Returning the same error for the nonfatal abort of an established connection by the client makes it impossible for the server to know whether to call accept again or not. In the case of the ECONNABORTED error, the server can ignore the error and just call accept again.

When a process writes to a socket that has received an RST, the SIGPIPE signal is sent to the process. The default action of this signal is to terminate the process, so the process must catch the signal to avoid being involuntarily terminated. If the process either catches the signal and returns from the signal handler, or ignores the signal, the write operation returns EPIPE.

To pass binary values across the socket. We will see that this does not work when the client and server are run on hosts with different byte orders, or on hosts that do not agree on the size of a long integer.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值