我有一个用C编写的服务器程序,应该可以接受请求并派生一个子进程来处理该请求。这是相关代码:
client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);
if (client_sock < 0) {
perror("accept failed");
return 1;
}
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if (pid == 0){
close(socket_desc);
/* code to handle request */
close(client_sock);
// exit(0);
kill(getpid(), SIGKILL);
}
close(client_sock);
我想问的是,在子进程中使用exit(0)
或kill()
时,该进程是否被杀死?当我运行上述程序并向其发送请求时,在大约10000个请求之后,fork调用失败,并显示消息fork: Resource temporarily unavailable
。如果我可靠地杀死了分叉的进程,那么为什么fork调用会失败?另外,如何在不更改系统限制的情况下进行管理?
答案 0 :(得分:0)
您无法进行分叉,可能是因为您在可创建的进程数上达到了用户或系统限制。这样做的原因是,当您让孩子正确离开自己时,您之后就不会在父母中“等待”他们了。这意味着该进程不会被清理,并且会为其分配某些资源。
一种解决方法是让父母对SIGCHLD做出响应,这是OS发送给父母的信号,通知其孩子已终止。
使用https://beej.us/guide/bgnet/html/multi/clientserver.html#simpleserver中的示例,您为操作系统添加了一个函数来在事件发生时进行调用,然后使用sys调用来通知OS您打算对该信号使用此函数的意图。
您添加
void sigchld_handler(int s)
{
// waitpid() might overwrite errno, so we save and restore it:
int saved_errno = errno;
while(waitpid(-1, NULL, WNOHANG) > 0);
errno = saved_errno;
}
并在您的主要功能中的某个时候完成
struct sigaction sa;
sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
令人高兴的是,您可以使用此方法对来自操作系统的其他信号做出反应,并在孩子死后将其扩展。
答案 1 :(得分:0)
基于@immibus和@ShadowRanger的建议,我按如下方式修改了代码,并且效果很好(服务器外部的信号调用接受了while循环):
signal(SIGCHLD, SIG_IGN);
...
while (1) {
c = sizeof(struct sockaddr_in);
client_sock = accept(socket_desc, (struct sockaddr *)&client, (socklen_t*)&c);
if (client_sock < 0) {
perror("accept failed");
return 1;
}
if((pid = fork()) < 0){
perror("fork");
exit(1);
}else if (pid == 0){
close(socket_desc);
/* code to handle request */
close(client_sock);
// exit(0);
kill(getpid(), SIGKILL);
}
close(client_sock);
}