以下内容为视频学习记录。
1、父进程accept后返回的文件描述符为cfd以及用于创建连接的lfd;
调用fork()创建子进程后,子进程继承cfd,lfd,通过该cfd与连接过来的客户端通信,lfd对子进程来说没用,可以直接close(lfd);
对于父进程来说,只是用来建立连接的,故父进程中的cfd没有用,直接close(cfd);
2、子进程完成数据交互后,close(cfd);exit(1);此时成为僵尸进程,所以需要在父进程中收尸,回收进程描述符等资源。否则的话,当父进程一直没有退出的时候,僵尸进程会导致资源消耗殆尽。
但此时父进程在accept,无法waitpid(),此时使用signal的方式SIGCHILD来解决收尸问题,当子进程成僵尸进程后,由内核自动回收。
注意:进程只能由父进程来回收,兄弟进程、叔叔进程都不行。
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <ctype.h>
#include <unistd.h>
#include "wrap.h"
#define MAXLINE 8192
#define SERV_PORT 8000
void do_sigchild(int num)
{
while (waitpid(0, NULL, WNOHANG) > 0)
;
}
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int i, n;
pid_t pid;
struct sigaction newact;
newact.sa_handler = do_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGCHLD, &newact, NULL);
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
Listen(listenfd, 20);
printf("Accepting connections ...\n");
while (1) {
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
pid = fork();
if (pid == 0) {
Close(listenfd);
while (1) {
n = Read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
Write(STDOUT_FILENO, buf, n);
Write(connfd, buf, n);
}
Close(connfd);
return 0;
} else if (pid > 0) {
Close(connfd);
} else
perr_exit("fork");
}
return 0;
}
总结:子进程负责与客户端通信,父进程负责接收客户端连接,但因为父进程还需要回收子进程,所以通过信号的方式来回收,
信号部分的代码也可以放在fork之后判断父进程位置。
else if (pid > 0) {
Close(connfd);
signal(SIGCHLD,wait_child);
}
main函数外定义回调函数
void wait_child(int signo)
{
while(watipid(0,NULL,WNOHANG)>0); //大于0则继续回收
return;
}