《伺候好多进程的“祖宗”:信号(Signal)和Sigaction的使用指南》

本文针对于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;
	

}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值