我和一些朋友目前正在网站上开发在线乒乓球游戏。 这个游戏运行得很好,但我想制作一个 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...)
一般来说,优雅地处理
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;
}
}