1

我对 ncurses 有疑问,在网上找不到解决方案,所以我编写了以下小程序来演示该问题。

您可以通过以下方式编译它:

sudo aptitude install ncurses-dev
g++ -lncurses -o resize resize.cpp

它通过分叉到一个定时器进程来显示一个整数计数器,该计数器每秒递增一次,该进程通过套接字对定期向父进程发送一个字节。您可以通过按 CTRL+C 退出它。

当您调整终端大小时,您应该会收到一条“系统调用中断”的错误消息。因此,在调整大小时,读取调用会被 SIGWINCH 中断。但是我怎样才能避免这种情况呢?或者系统调用被中断是否常见?但是,由于文件描述符在中断后似乎已失效,因此我将如何处理中断的系统调用以继续递增计数器。

如果您使用非阻塞套接字,您将获得“资源暂时不可用”。

我使用的是稳定的 debian wheezy,所以 ncurses 版本是 5.9-10,libstdc++ 版本是 4.7.2-5。

#include <ncurses.h>
#include <signal.h>
#include <netdb.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <string>
#include <iostream>

//Define a second.
timespec span = {1, 0};

//Handles both, SIGWINCH and SIGINT
void handle(int signal) {
    switch (signal) {
        case SIGWINCH:
            //Reinitialize ncurses to get new size
            endwin();
            refresh();
            printw("Catched SIGWINCH and handled it.\n");
            refresh();
        break;
        case SIGINT:
            //Catched CTRL+C and quit
            endwin();
            exit(0);
        break;
    }
}

//This registers above signal handler function
void set_handler_for(int signal) {
    struct sigaction action;
    action.sa_handler = handle;
    action.sa_flags = 0;
    if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
        throw "Cannot set signal handler";
}

main() {
    int fd[2];
    //In this try block we fork into the timer process
    try {
        set_handler_for(SIGINT);
        set_handler_for(SIGWINCH);
        //Creating a socketpair to communicate between timer and parent process
        if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
            throw "Cannot create socketpair";
        pid_t pid;
        //Doing the fork
        if (-1 == (pid = fork()))
            throw "Cannot fork process";
        if (!pid) {
            //We are the timer, so closing the other end of the socketpair
            close(fd[0]);
            //We send one byte every second to the parent process
            while (true) {
                char byte;
                ssize_t bytes = write(fd[1], &byte, sizeof byte);
                if (0 >= bytes)
                    throw "Cannot write";
                nanosleep(&span, 0);
            }
            //Here the timer process ends
            exit(0);
        }
        //We are the parent process, so closing the other end of the socketpair
        close(fd[1]);
    }
    catch (const char*& what) {
        std::cerr << what << std::endl;
        exit(1);
    }
    //Parent process - Initializing ncurses
    initscr();
    noecho();
    curs_set(0);
    nodelay(stdscr, TRUE);
    //In this try block we read (blocking) the byte from the timer process every second
    try {
        int tick = 0;
        while (true) {
            char byte;
            ssize_t bytes = read(fd[0], &byte, sizeof byte);
            if (0 >= bytes)
                throw "Cannot read";
            //Clear screen and print increased counter
            clear();
            mvprintw(0, 0, "Tick: %d - Resize terminal and press CTRL+C to quit.\n", ++tick);
            //Catch special key KEY_RESIZE and reinitialize ncurses to get new size (actually not necassary)
            int key;
            while ((key = getch()) != ERR) {
                if (key == KEY_RESIZE) {
                    endwin();
                    refresh();
                    printw("Got KEY_RESIZE and handled it.\n");
                }
            }
            //Update the screen
            refresh();
        }
    }
    catch (const char*& what) {
        //We got an error - print it but don't quit in order to have time to read it
        std::string error(what);
        if (errno) {
            error.append(": ");
            error.append(strerror(errno));
        }
        error = "Catched exception: "+error+"\n";
        printw(error.c_str());
        refresh();
        //Waiting for CTRL+C to quit
        while (true)
            nanosleep(&span, 0);
    }
}

谢谢!

问候

4

2 回答 2

0

好的,我通过仅在信号处理程序中使用可重入函数来使其工作。现在套接字对在 EINTR 或 EAGAIN 之后仍在工作。

谢谢!

#include <ncurses.h>
#include <signal.h>

#include <netdb.h>
#include <unistd.h>

#include <string.h>
#include <errno.h>

#include <string>
#include <iostream>

// Define a second.
timespec base = {1, 0};

// Holds raised SIGINTs.
size_t raised_SIGINT = 0;

// Holds raised SIGWINCHs.
size_t raised_SIGWINCH = 0;

// Handle SIGWINCH
void handle_SIGWINCH(int) {
    ++raised_SIGWINCH;
}

// Handle SIGINT
void handle_SIGINT(int) {
    ++raised_SIGINT;
}

// Registers signal handlers.
void assign(int signal, void (*handler)(int)) {
    struct sigaction action;
    action.sa_handler = handler;
    action.sa_flags = 0;
    if (-1 == sigemptyset(&action.sa_mask) or -1 == sigaction(signal, &action, NULL))
        throw "Cannot set signal handler";
}

// Prints ticks alive and usage information.
inline void print(size_t ticks) {
    mvprintw(0, 0, "%ds alive. Resize terminal and press CTRL+C to quit.\n\n", ticks);
}

int main() {
    // Holds the two socketpair file descriptors.
    int fd[2];

    // Fork into the timer process.
    try {
        // Register both signals.
        assign(SIGINT, handle_SIGINT);
        assign(SIGWINCH, handle_SIGWINCH);

        // Create a socketpair to communicate between timer and parent process.
        if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd))
            throw "Cannot create socketpair";

        // Doing the fork.
        pid_t pid;
        if (-1 == (pid = fork()))
            throw "Cannot fork process";
        if (!pid) {
            // We are the timer, so closing the parent end of the socketpair.
            close(fd[0]);

            // We send one byte every second to the parent process.
            while (true) {
                timespec less = base;
                int ret;

                // Continue sleeping after SIGWINCH but only for the time left.
                while (-1 == (ret = nanosleep(&less, &less)) and errno == EINTR and raised_SIGWINCH);

                // Maybe quit by user.
                if (raised_SIGINT)
                    return 0;

                // If something went wrong, terminate.
                if (-1 == ret)
                    throw "Cannot sleep";

                // Repeated writing if interrupted by SIGWINCH.
                char byte;
                ssize_t bytes;
                do {
                    // Doing the write.
                    bytes = write(fd[1], &byte, sizeof byte);
                }
                while (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);

                // Maybe quit by user.
                if (raised_SIGINT)
                    return 0;

                // If something went wrong, terminate.
                if (0 >= bytes)
                    throw "Cannot write";
            }

            // Here the timer process ends.
            return 0;
        }

        // We are the parent process, so closing the timer end of the socketpair.
        close(fd[1]);
    }
    catch (const char*& what) {
        // Print fatal error and terminate timer process causing parent process to terminate, too.
        std::cerr << what << std::endl;
        return 1;
    }

    // Initializing ncurses for the parent process.
    initscr();

    // Disable typing.
    noecho();

    // Disable cursor.
    curs_set(0);

    // Make reading characters non-blocking.
    nodelay(stdscr, TRUE);

    // Catch fatal errors.
    try {
        // Holds ticks alive.
        size_t ticks = 0;

        // Blockingly read the byte from the timer process awaiking us every second.
        while (true) {
            // Print ticks alive before incrementing them.
            print(ticks++);

            // Holds typed keys.
            std::string keys;

            // Read typed keys.
            for (int key = getch(); key != ERR; key = getch())
                if (key != KEY_RESIZE)
                    keys += key;

            // Format typed keys string.
            if (keys.size())
                printw("You've typed: ");
            else
                keys += "\n";
            keys += "\n\n";

            // Print typed keys string.
            printw(keys.c_str());

            // Doing the prints.
            refresh();

            // Repeated reading if interrupted by SIGWINCH.
            ssize_t bytes = 0;
            bool again = false;
            do {                    
                // Doing the read.
                char byte;
                bytes = read(fd[0], &byte, sizeof byte);
                again = (0 >= bytes and (errno == EAGAIN or errno == EINTR) and raised_SIGWINCH);

                // Print how often we got interrupted by SIGWINCH per time base.
                if (again) {
                    // Next two calls are the common way to handle a SIGWINCH.
                    endwin();
                    refresh();

                    // For simpicity clear everything.
                    clear();

                    // Re-print ticks.
                    print(ticks);

                    // Print the interruption counter.
                    printw("%dx catched SIGWINCH per time base.\n\n", raised_SIGWINCH);

                    // Doing the prints.
                    refresh();
                }
            }
            while (again);

            // Reset SIGWINCH raises per time base.
            raised_SIGWINCH = 0;

            // Maybe quit by user.
            if (raised_SIGINT) {
                endwin();
                return 0;
            }

            // If something went wrong, terminate.
            if (0 >= bytes)
                throw "Cannot read";
        }
    }
    catch (const char*& what) {
        // We got an error, appending errno if set.
        std::string error(what);
        if (errno) {
            error.append(": ");
            error.append(strerror(errno));
        }
        error = "Catched exception: "+error+"\n";

        // Print the fatal error.
        printw(error.c_str());

        //Doing the print.
        refresh();

        // Waiting for CTRL+C to quit.
        while (true)
            nanosleep(&base, 0);

        // Quit by user.
        endwin();
        return 0;
    }
}
于 2013-10-21T20:53:26.083 回答
0

大多数(如果不是全部)系统调用都有一个中断的错误代码(errno == EINTR),这是正常的。

我会检查从管道读取的 EINTR 并忽略它,然后再次读取。

我不会在信号处理程序中调用任何 ncurses 函数,有些是可重入的,但我怀疑 printw 是。只需进行 KEY_RESIZE 检查。

于 2013-10-14T20:11:11.300 回答