本文针对于C/C++网络编程中的多进程服务器
代码:
#include <sys/wait.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
int main() {
pid_t pid = fork();
if(pid > 0){
std::cout << "This is main process, will died" << std::endl;
int status;
pid_t result;
while ((result = waitpid(-1, &status, WNOHANG)) == 0) {
std::cout << "waiting for son process" << std::endl;
sleep(1);
}
if (result < 0) {
perror("waitpid");
}
else{
if (WIFEXITED(status)) {
std::cout << "son process normally with code : " << WEXITSTATUS(status) << std::endl;
}
else if (WIFSIGNALED(status)) {
std::cout << "son process was died for signal!" << std::endl;
}
else {
std::cout << "I don't know!" << std::endl;
}
}
}
else if (pid == 0) {
std::cout << "This is son process, will died for two seconds" << std::endl;
sleep(2);
exit(0);
}
else {
perror("fork");
}
return 0;
}
这段代码很简单,你也能明白,关键点是,while循环那边持续等待子进程结束的代码你可能不会理解,因为这是非阻塞(WNOHANG),所以,他的行为只是看一看当时是否有子进程死亡等待回收,如果没有就立刻退出,如果没有while循环,那么就很难办了,因为子进程或许有其他的操作,导致主进程结束之后,子进程并没有结束,那么此时就出错啦!子进程并没有被回收,你可能会想到阻塞(0)这个方法,等待子进程结束再运行主进程,但是这样的话,太鸡肋啦,如果我的主进程还有别的事情要干呢?所以还得用非阻塞,那么只能用while循环吗?不断的消耗cpu?那这和阻塞有什么区别,C不允许这样的事情发生,所以引入了一个头文件:signal.h 信号。
代码:
#include <sys/wait.h>
#include <iostream>
#include <unistd.h>
#include <signal.h>
void signal_close(int sig) {
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
std::cout << "Child " << pid << "has been recycled." << std::endl;
if (WIFEXITED(status)) {
std::cout << " Exited with code: " << WEXITSTATUS(status) << std::endl;
}
else if (WIFSIGNALED(status)) {
std::cout << " Killed by signal: " << WTERMSIG(status) << std::endl;
}
}
}
int main() {
signal(SIGCHLD, signal_close);
for (int i = 0; i < 3; ++i) {
pid_t pid = fork();
if (pid == 0) {
std::cout << "Child :" << getpid() << "starting...." << std::endl;
sleep(i + 2);
std::cout << "Child :" << getpid() << "exiting." << std::endl;
exit(i);
}
}
while (true) {
std::cout << "main process working..." << std::endl;
sleep(1);
}
return 0;
}
这段代码很好的展示了signal和wait是如何联动的,完美的解决了效率问题,signal(SIGCHLD, signal_close);这段代码的意思是一旦有子进程退出程序,就立即触发signal_close函数,这也就完美的将主进程和子进程分开,子进程干子进程的事情,主进程干主进程的事,两者互不干扰,这样的话,对于多进程的服务器也就可以操作了,你可能会想,当父进程accept之后,然后创建一个子进程去读取和回复消息,但是当你一旦创建子进程之后,会同时拥有两个client_fd(通信通道)和server_fd,(算不算两个呢?可以说client_fd和server_fd被主进程和子进程同时使用,可以联想到shared_ptr,也就是引用计数,当主进程和子进程都close之后才会真正的close)那么,我们其实只需要一个client_fd和server_fd,那么就需要在子进程关闭server_fd(因为主进程才是主要的服务器),在主进程关闭client_fd(因为主进程并不需要接收消息,让子进程去干即可)。那么为什么一定要close呢?如果主进程不关client_fd,这个链接会一直“悬空”,浪费资源,子进程不关server_fd,那么等下一次accept的时候,就可能会出现错误,客户端不知道连谁,导致连接错误,同时还有一个致命的问题,惊群效应(Thundering Herd) :多个子进程持有server_fd,当连接到来时,所有的子进程都会被唤醒,但只能有一个accept,无法连接到所有子进程,所以其他的子进程都会失败,所以,千万不能忘记关闭。
了解完这个之后,你是否觉得signal是绝对安全的方式了呢?但是,signal是一个比较远古的信号注册方式,在大部分系统或者环境下,它表达的意义可能不会符合你的预期,那么,有没有什么统一的方式呢?接下来介绍:sigaction
(sigaction包含于头文件#include<signal.h>)
局部代码:
struct sigaction sa;
sa.sa_handler = signal_close;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, nullptr) == -1) {
perror("sigaction");
return -1;
}// 后续 fork() 子进程...
我们来看看sigaction为什么更加安全全面吧,sa.sa_handler这个的意思就是我要调用哪一个函数,这个自然不用多说,往下看,sigemptyset就比较有意思了, 屏蔽信号机制,看看这个函数名字,empty set?空闲设置?(或者也可以说把信号集合清空)再看一下sa_mask,这个是信号屏蔽集合,当我们把这个放进去之后,是不是就意味着所有信号都会被屏蔽,第二个参数可以添加特定的信号屏蔽,比如Ctrl + c,表明我要屏蔽Ctrl + c这个信号,所以,如果只写一个&sa.sa_mask,表示的就是不屏蔽任何信号。接下来就是sa_flags = SA_RESTART,这个就是表明的是如果有一个子进程死了,那么我就会回收他,但是在多进程服务器里,我一直在accept,一直在阻塞,这个时候系统就会打断正在执行的accept(),转而去执行signal_close函数,没有SA_RESTART的时候,直接炸钢,直接会返回错误,但是有了SA_RESTART就不一样了,虽然我会直接绕过accept()去执行signal_close,但是,执行完之后,自动回到accept()这个函数,继续等待客户,几乎没有中断!接下来,就是最后的一个要说的了,if(sigaction(SIGCHLD,&sa,nullptr) == -1){ ... },这个就是信号注册了,第一个就是我们要处理子进程死亡这个事件,第二个参数就是我应该用什么操作处理这个子进程死亡这个事件?也就是我们刚刚创建的结构体,因为我们都把规则写进里面了,所以这里直接传进去就行,第三个就是旧的设置不用保存,但是如果传的是指针的话,也可以保存。
最后附上完整多进程服务器代码:
#include <sys/wait.h>
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>
#include <chrono>
#include <cerrno>
#include <thread>
void signal_close(int sig) {
int pid;
while ((pid = waitpid(-1, nullptr, WNOHANG)) > 0) {
std::cout << "son process :" << pid << "now recycle" << std::endl;
}
}
int main() {
struct sigaction sa;
sa.sa_handler = signal_close;
sa.sa_flags = SA_RESTART;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGCHLD, &sa, nullptr) == -1) {
perror("sigaction");
return -1;
}
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return -1;
}
sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(8080);
socklen_t server_addr_len = sizeof(server_addr);
if (bind(server_fd, (sockaddr*)&server_addr, server_addr_len) < 0) {
perror("bind");
close(server_fd);
return -1;
}
if (listen(server_fd, 5) < 0) {
perror("listen");
close(server_fd);
return -1;
}
std::cout << "now all sets for server was already,waiting..." << std::endl;
while (true) {
sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int server_to_client = accept(server_fd, (sockaddr*)&client_addr, &client_addr_len);
if (server_to_client < 0) {
perror("accept");
continue;
}
std::cout << "accept finally success!" << std::endl;
pid_t pid = fork();
if (pid == 0) {
close(server_fd);
while (true) {
char buffer[1024];
int len = recv(server_to_client, buffer, sizeof(buffer) - 1, 0);
if (len == 0) {
std::cout << "client disconnected" << std::endl;
close(server_to_client);
break;
}
else if (len < 0) {
perror("recv");
close(server_to_client);
break;
}
else {
buffer[len] = '\0';
std::cout << "client:" << buffer << std::endl;
if (strcmp(buffer, "hello") == 0) {
std::string response = "server : hello i'm server!";
if (send(server_to_client, response.c_str(), response.length(), 0) < 0) {
perror("send");
break;
}
}
else if (strcmp(buffer, "bye") == 0) {
std::string response = "server : good bye";
if (send(server_to_client, response.c_str(), response.length(), 0) < 0) {
perror("send");
break;
}
close(server_to_client);
break;
}
else {
std::string response = "server : what are you say?";
if (send(server_to_client, response.c_str(), response.length(), 0) < 0) {
perror("send");
break;
}
}
}
}
exit(0);
}
else if (pid > 0) {
close(server_to_client);
}else{
perror("fork");
close(server_to_client);
}
}
return 0;
}

被折叠的 条评论
为什么被折叠?



