0

这里的目标是捕获 SIGINT 以关闭小型套接字服务器上的服务器套接字。我尝试使用嵌套函数来保持代码清洁。但...

当我执行 Ctrl-C(SIGINT,对吗?)时,我得到Illegal instruction: 4. 阅读这篇文章后,我尝试添加-mmacosx-version-min=10.8编译标志,因为我在 10.8 上。执行 Ctrl-C 时同样的错误。

这里有两个问题:为什么我会得到“非法指令 4”?如何在不使用全局变量的情况下关闭服务器套接字?

我的软件:

Mac OSX 10.8.4
GCC 4.2.1

这是我的编译方式:

gcc -fnested-functions main.c

这是代码:

#include <sys/socket.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

void register_sigint_handler(int *serverSocket)
{
    void sigint_handler(int signal) {
        printf("Shutting down...\n");
        printf("Server socket was %d\n", *serverSocket);
        close(*serverSocket);
        exit(0);
    }
    signal(SIGINT, &sigint_handler);
}

int main(void) {
    int serverSocket = 0, guestSocket = 0;

    register_sigint_handler(&serverSocket);

    serverSocket = socket(PF_INET, SOCK_STREAM, 0);

    while (1) {}

    close(serverSocket);
    return 0;
}
4

2 回答 2

6

虽然我不能具体告诉你会发生什么,但gcc 文档有一个概括:

如果您在包含函数退出后尝试通过其地址调用嵌套函数,那么一切都会崩溃。

将函数指针传递给 signal() 将完全做到这一点,在包含函数退出后调用本地函数。所以你不应该将嵌套函数指针传递给 signal()

您可能应该只为处理程序使用一个普通函数,该函数设置一个标志。

static volatile int do_exit;
void sigint_handler(int sig)
{
   do_exit = 1;
}

在服务器中,通常有某种主循环,例如选择或轮询,你的主循环,空的while循环现在可以变成

while (!do_exit) { 
  pause(); 
}

(请注意,当进程退出时,套接字会被操作系统自动关闭;)

于 2013-08-27T21:48:22.407 回答
5

那就别这样了。

GCC 的nested-functions-in-C 扩展不提供真正的闭包。当你取 的地址时sigint_handler,一个“trampoline”(一小段自修改代码)被写入堆栈;蹦床一register_sigint_handler出,即毁;随后尝试调用蹦床(通过内核,以调度信号)会导致未定义的行为。

根据定义,信号处理程序是进程全局的。因此, 原则上试图避免在信号处理程序中使用全局变量是不正确的。想象一下你会做些什么来让这段代码处理两个服务器套接字:你只能SIGINT注册一个处理程序,所以它必须以某种方式关闭两个套接字。

当您的进程终止时,所有打开的文件描述符都会自动关闭。 因此,不必先手动关闭它们。此外,在 上成功退出是违反惯例的^C 如果这个程序是由一个监督进程驱动的,那么这个进程会想知道(通过waitpid的状态码)它是因为SIGINT. 把这两件事放在一起,你根本不应该有这个信号处理程序

(如果您需要在退出时对活动套接字执行某些操作,那就不成立了。例如,如果您想在退出时为每个活动客户端连接写一些东西,那么您需要一个信号处理程序。但此时您想要让信号处理程序提醒主事件循环,并在那里完成工作。)

(考虑使用libevent这种事情,而不是自己做所有的低级粘液。)

于 2013-08-27T22:01:43.547 回答