1. 思路
(1)父进程使用监听套接字lfd监听连接请求,有新连接请求则提取新连接的套接字cfd,并创建子进程处理该新请求;
(2)父进程中关闭提取的套接字cfd,继续使用套接字lfd监听并循环提取新连接请求;
(3)子进程中关闭复制而来的套接字lfd,处理套接字cfd的请求即可。
(4)注意:fork子进程之前,父进程需要注册子进程退出信号处理函数,避免回收子进程资源而阻塞,从而使得父进程可以继续提取新连接。
2. 代码实现
#include<stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
#include<signal.h>
#include"wrap.h"
void waitforchild(int signum) {
pid_t pid = -1;
while (1) { // 循环回收多个退出的子进程
pid = waitpid(-1, NULL, WNOHANG);
if (pid > 0) {
printf("子进程%d退出.\n", pid);
} else {
break;
}
}
printf("子进程退出..\n");
}
int main(int argc, const char* argv[]) {
/* 先将SIGCHLD阻塞住,
以防父进程注册子回收子进程函数之前有子进程退出,
那些子进程将不会被回收 */
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGCHLD); // 将SIGCHLD加入信号集set
sigprocmask(SIG_BLOCK, &set, NULL); // 将信号集set加入阻塞信号集
// 1.创建socket & 绑定
int lfd = tcp4bind(8888, NULL);
// 2.监听
Listen(lfd, 128);
// 4.循环提取,并处理连接请求
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
while (1) {
char ip[16] = "";
int cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &len);
printf("新客户端%d到来!ip = %s, port = %d\n", cfd,
inet_ntop(AF_INET, &(cliaddr.sin_addr.s_addr), ip, 16),
ntohs(cliaddr.sin_port));
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork");
exit(0);
} else if (0 == pid) { // 子进程
close(lfd);
while (1) {
char buf[1024] = "";
int n = read(cfd, buf, sizeof(buf));
if (n < 0) {
perror("read");
close(cfd);
exit(0);
} else if (0 == n) {
// 客户端关闭. 若客户端仍保持连接,但没发数据,此时read不会返回0,而是阻塞。
printf("客户端%d已关闭连接.\n", cfd);
close(cfd);
exit(0);
} else {
printf("客户端:%s\n", buf);
write(cfd, buf, n);
}
}
} else { // 父进程
close(cfd);
// 注册信号处理函数,回收子进程
struct sigaction act;
act.sa_handler = waitforchild; // 信号处理函数
act.sa_flags = 0; // 使用旧的信号处理函数
sigemptyset(&act.sa_mask); // 确保执行信号处理函数时,不会被其他信号打断
sigaction(SIGCHLD, &act, NULL);
sigprocmask(SIG_UNBLOCK, &set, NULL); // 将set信号集从阻塞信号集中删除
}
}
// 5.关闭
close(lfd);
return 0;
}
运行结果: