通过调整终端大小来阻止 Websocket 中断(操作系统错误 4)

问题描述 投票:0回答:1

我和一些朋友目前正在网站上开发在线乒乓球游戏。 这个游戏运行得很好,但我想制作一个 Rust CLI 应用程序,允许用户在网站上与在线玩家进行游戏。

为此,我使用 tungstenite crate 中的 websocket。我还使用 ncurses crate 获取用户输入,并使用 term_size crate 了解终端的大小以适应游戏的渲染方式。

我与 websocket 服务器的连接正常,但是当我调整终端大小以测试“响应式”是否正常工作时,我收到操作系统错误 4,这是系统调用中断。经过一些研究,我发现当调整终端大小时,操作系统会向进程发送一个 SIGWINCH 信号,该信号会中断每个阻塞的系统调用。 我知道同步箱中的钨矿(所以我的 websocket 被阻塞),这就是为什么我选择它而不是 tokio(这是我的第一个 Rust 应用程序,我不想用线程和异步代码来复杂化它,而我不熟悉)与语言)。

我真的不知道如何禁用这个信号(我认为这样做不是一个好主意)或者如何以不会中断阻塞系统调用的方式处理它。我也不确定禁用 SIGWINCH 是否不会“破坏” term_size 板条箱。

这是我的代码的一部分:

initscr();
raw();
keypad(stdscr(), true);
noecho();
timeout(0);
loop {
    match getch() {
        27 => { // ESC
            endwin();
            break;
        },
        ch => {
            let ch = match char::from_u32(ch as u32) {
                Some(ch) => {
                    ch
                },
                None => ' '
            };
            if ch != ' ' { // Send user input to the server
                socket.write_message(Message::Text(r#"{"message":"{ch}"}"#.to_string().replace("{ch}", &ch.to_string())));
            }
        }
    }
    match socket.read_message() { // THIS IS WHERE THE ERROR OCCURED
        Ok(msg) => match msg {
            Message::Text(msg) => {
                // render the game
            },
            _ => {}
        },
        Err(err) => { ... }
    }
}

我试过了,但它没有改变任何东西:

use std::os::unix::io::RawFd;
use nix::sys::signal::{self, Signal, SigHandler, SigAction, SigSet, SaFlags};
use nix::unistd::Pid;

fn main() {
    let sig_action = SigAction::new(
        SigHandler::SigIgn,
        SaFlags::empty(),
        SigSet::empty(),
    );
    
    unsafe {
        let _ = signal::sigaction(Signal::SIGWINCH, &sig_action).expect("Failed to ignore SIGWINCH");
    }
    
    // The rest of my code are here (like authentification, selection to create or join a game, etc...)
rust websocket command-line-interface tungstenite
1个回答
0
投票

一般来说,优雅地处理

EINTR
(你遇到的错误)是在 Unix 上编写代码的一部分。理论上,大多数系统调用都可能发生这种情况,包括大多数形式的 I/O。

在某些系统上可以使用

sigaction
SA_RESTART
标志来重新启动 some 系统调用;这些取决于操作系统。但是,如果您使用的是终端库,则捕获
SIGWINCH
的不是您的代码,而是终端库,因此您的代码仍然需要优雅地处理
EINTR

好消息是,如果您得到

EINTR
,则表示没有发送或接收数据;如果发送或接收了一些数据,那么您将得到一个简短的读取或写入指示该部分数据。因此,如果您确实得到了
EINTR
,您只需循环重试,直到获得成功或出现不同的错误。所以它看起来像这样:

loop {
    match getch() {
        27 => { // ESC
            endwin();
            break;
        },
        ch => {
            let ch = match char::from_u32(ch as u32) {
                Some(ch) => {
                    ch
                },
                None => ' '
            };
            if ch != ' ' { // Send user input to the server
                socket.write_message(Message::Text(r#"{"message":"{ch}"}"#.to_string().replace("{ch}", &ch.to_string())));
            }
        }
    }
    loop {
        match socket.read_message() {
            Ok(msg) => match msg {
                Message::Text(msg) => {
                    // render the game
                },
                _ => {}
            },
            Err(err) if err.kind() == ErrorKind::Interrupted => continue,
            Err(err) => { ... }
        }
        break;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.