当子进程结束的时候,内核会发送SIGCHLD信号给它的父进程。父进程会根据这个SIGCHLD来回收进程的资源。
当父进程不回收资源的时候,就会产生僵尸进程。这显然不符合我们的编程规范,因为僵尸进程会占用系统资源。
规范的编程思路是每一个fork函数都应有一个wait或者waitpid函数和它对应。wait或者waitpid函数就是系统用于回收结束的子进程资源。
tcpcliecho.c
/*
* 抄自《UNIX网络编程:卷1》, 稍作修改。
* 仅仅用于学习目的。学无止境,进步每一天。
*
* slickedit编辑。
*
* 254008829@qq.com
*
*/
//
do { \
int ll_rv; \
if ( (ll_rv=func) < 0) { \
printf("LINE = %d, error str: %s\n", __LINE__, strerror(errno)); \
perror("FUNC_RET"); \
exit(-1); \
} \
val = ll_rv; \
} while (0);
do { \
int ll_rv; \
if ( (ll_rv=func) <= 0) { \
printf("LINE = %d, error str: %s\n", __LINE__, strerror(errno)); \
perror("FUNC_RET"); \
exit(-1); \
} \
val = ll_rv; \
} while (0);
do { \
int ll_rv; \
if ( (ll_rv=func) != 0) { \
printf("LINE = %d, error str: %s\n", __LINE__, strerror(errno)); \
perror("FUNC_RET"); \
exit(-1); \
} \
val = ll_rv; \
} while (0);
do { \
void* ll_prv; \
if ( (ll_prv=func) == NULL) { \
printf("LINE = %d, error str: %s\n", __LINE__, strerror(errno)); \
perror("FUNC_RET"); \
exit(-1); \
} \
val = ll_prv; \
} while (0);
ssize_t
writen(int fd, char *vptr, size_t n)
{
size_t nleft;
ssize_t nwrite;
char *ptr;
nleft = n;
ptr = vptr;
while (nleft > 0) {
/*
内核里面由于缓冲的原因,一次write可能没有写完我们所要求的n个字节。没有关系,返回nwrite>0,
表明实际写了nwrite个字节。下次接着写。
在不出错的情况下,writen返回的一定是n。这个和readn有区别。
*/
if ( (nwrite = write(fd, ptr, nleft)) <= 0) {
if ( (nwrite < 0 && nwrite == EINTR)) {
nwrite = 0; // call write() again.
} else
return -1; // error
}
nleft -= nwrite;
ptr += nwrite;
}
return n;
}
/* 十分低效的readline函数,不带缓冲,每次只read一个字节, 每read一次都要进行一次系统调用。 */
ssize_t
readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n=1; n<maxlen; n++) {
again:
if ( (rc = read(fd, &c, 1)) == 1) {
*ptr++ = c;
if (c == '\n') {
break;
}
} else if (rc == 0) { // eof
return n-1;
} else {
if (errno == EINTR) {
goto again;
}
return -1;
}
}
*ptr = 0;
return n;
}
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE]={0}, recvline[MAXLINE]={0}, *pret;
int ret;
for (;;) {
FUNC_RET_PTR(fgets(recvline, MAXLINE, fp), pret);
if (!pret) {
break;
}
if (writen(sockfd, recvline, strlen(recvline)) != strlen(recvline)) {
exit(0);
}
if (readline(sockfd, sendline, MAXLINE) == 0) {
exit(0);
}
FUNC_RET_PSTV(fputs(sendline, stdout), ret);
}
}
int
main(int argc, char **argv)\
{
struct sockaddr_in servaddr;
int sockfd[5], ret, i = 0;
if (argc != 2) {
printf("usage: %s ip\n", argv[0]);
exit(0);
}
/* 创建五个sockfd,并且都connect服务器,那么服务器程序会创建五个子进程 */
for (; i<5; i++) {
FUNC_RET_NONNGTV(socket(AF_INET, SOCK_STREAM, 0), sockfd[i]);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
FUNC_RET_PSTV(inet_pton(AF_INET, argv[1], &servaddr.sin_addr), ret);
FUNC_RET_ZERO(connect(sockfd[i], (struct sockaddr *)&servaddr, sizeof(servaddr)), ret);
}
str_cli(stdin, sockfd[0]);
exit(0);
}
tcpserecho.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h> /* struct sockaddr_in, htons ... */
#include <linux/wait.h>
#define LISTENQ 1024
#define SERV_PORT 9877
#define MAXLINE 1024
#define FUNC_RET(func, val) \
do { \
int rv; \
if ( (rv=func) < 0) { \
printf("LINE = %d, error str: %s\n", __LINE__, strerror(errno)); \
perror("FUNC_RET"); \
exit(-1); \
} \
val = rv; \
} while (0);
#define FUNC_RET_PTR(func, val) \
do { \
void* ll_prv; \
if ( (ll_prv=func) == NULL) { \
printf("LINE = %d, error str: %s\n", __LINE__, strerror(errno)); \
perror("FUNC_RET"); \
exit(-1); \
} \
val = ll_prv; \
} while (0);
#if 0
void
str_echo(int sockfd)
{
char line[MAXLINE];
FILE *fpin, *fpout;
char *rptr;
FUNC_RET_PTR(fdopen(sockfd, "r"), fpin);
FUNC_RET_PTR(fdopen(sockfd, "w"), fpout);
for (;;) {
int put_ret;
FUNC_RET_PTR(fgets(line, MAXLINE, fpin), rptr);
if (ferror(fpin) && rptr == NULL) {
exit(0);
}
FUNC_RET(fputs(line, fpout), put_ret);
if (put_ret == EOF) {
exit(0);
}
}
}
#endif
ssize_t
writen(int fd, char *vptr, size_t n)
{
size_t nleft;
ssize_t nwrite;
char *ptr;
nleft = n;
ptr = vptr;
while (nleft > 0) {
if ( (nwrite = write(fd, ptr, nleft)) <= 0) {
if ( (nwrite < 0 && nwrite == EINTR)) {
nwrite = 0;
} else
return -1;
}
nleft -= nwrite;
ptr += nwrite;
}
return n;
}
void
str_echo(int sockfd)
{
ssize_t n;
char buf[MAXLINE];
again:
while ( (n=read(sockfd, buf, MAXLINE)) > 0) {
writen(sockfd, buf, n);
}
if (n < 0 && errno == EINTR) {
goto again;
}
else if (n < 0) {
exit(0);
}
}
void
sig_chld(int signo)
{
pid_t pid;
int stat;
while ( (pid = waitpid(-1, &stat, WNOHANG)) > 0) {
printf("child process id = %d\n", (int)pid);
}
return;
}
int
main(int argc, char **argv)
{
int listenfd, connfd, ret;
pid_t childpid;
struct sockaddr_in cliaddr, seraddr;
socklen_t clilen;
FUNC_RET(socket(AF_INET, SOCK_STREAM, 0), listenfd);
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_port = htons(SERV_PORT);
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
FUNC_RET(bind(listenfd, (struct sockaddr *)&seraddr, sizeof(seraddr)), ret);
FUNC_RET(listen(listenfd, LISTENQ), ret);
signal(SIGCHLD, sig_chld);
while (1) {
clilen = sizeof(cliaddr);
FUNC_RET(accept(listenfd, (struct sockaddr *)&cliaddr, &clilen), connfd);
if (connfd < 0) {
if (errno == EINTR) {
continue;
} else {
printf("accept error: %s\n", strerror(errno));
}
}
if ( (childpid = fork()) == 0) {
close(listenfd);
str_echo(connfd);
exit(0);
}
close(connfd);
}
exit(0);
}
客户程序创建了5个socket与服务器连接,那么服务器会fork五个子进程,当我们手动结束客户程序的时候(ctrl+d)。
子进程会依次结束,发送五次SIGCHLD信号给父进程,最终调用waitpid函数来回收资源。