我正在尝试生成一个服务器并在不同的线程上连接到它。我知道 Rust 有阻塞 I/O,但我觉得我应该能够在不同的线程中连接服务器。我对线程的了解不多。最终游戏是通过网络连接到该服务器。这就是我用 player_stream
TCPStream
模拟的内容。 player_stream
会一直等到它的缓冲区中有内容。一旦在那里写入了一些东西,它就会响应回服务器。照原样,程序不会终止。
use std::net::{TcpListener, TcpStream};
use std::io::{BufReader,BufWriter};
use std::io::Write;
use std::io::Read;
use std::thread;
fn main() {
thread::spawn(move || {
start_server();
});
let player_stream = TcpStream::connect("127.0.0.1:8000").expect("Couldn't connect");
let mut reader = BufReader::new(&player_stream);
let mut response = String::new();
reader.read_to_string(&mut response);
println!("Player received {}", response);
let mut writer = BufWriter::new(&player_stream);
writer.write_all("NAME".as_bytes());
}
fn start_server() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
fn handle_client(stream: TcpStream) {
println!("Client connected");
let mut writer = BufWriter::new(&stream);
writer.write_all("Red".as_bytes());
let mut reader = BufReader::new(&stream);
let mut response = String::new();
reader.read_to_string(&mut response);
println!("Server received {}", response);
}
// accept connections
for stream in listener.incoming() {
match stream {
Ok(stream) => {
handle_client(stream);
}
Err(e) => { panic!("{}",e) }
}
}
}
最佳答案
首先,不要忽略警告。您有 4 个类型为 warning: unused result which must be used
的错误。其中每一个都可能是您的代码失败的情况,而您甚至都不知道。自由使用 expect
!
其次,您有一个打开的客户端读取套接字,并且您要求“将所有数据读取到字符串中直到结束”。 什么决定结束?在这种情况下,是关闭套接字的时间;那是什么时候?
技巧问题!
- 当服务器的写套接字关闭时,客户端的读套接字也关闭。
- 服务器的写套接字在服务器的读套接字关闭时关闭。
- 当客户端的写套接字关闭时,服务器的读套接字也关闭。
那什么时候发生呢?因为没有专门执行此操作的代码,所以当套接字被删除时它将关闭,所以:
- 客户端的写套接字在客户端结束时关闭。
因此陷入僵局。可以通过显式关闭套接字的写入部分来解决此问题:
stream.shutdown(std::net::Shutdown::Write).expect("could not shutdown");
第三,您正在写入 BufWriter
。查看它的文档:
A
BufWriter
keeps an in-memory buffer of data and writes it to an underlying writer in large, infrequent batches.The buffer will be written out when the writer is dropped.
在您尝试读取响应后,BufWriter
被丢弃在作用域的末尾。这是另一个僵局。
最后,您需要建立一个协议(protocol)来界定如何分隔来回发送的消息。一个简单但非常有限的解决方案是采用面向行的协议(protocol):每条消息都放在以换行符结尾的一行中。
如果您选择它,则可以改用 read_to_line
。我还使用了 BufWriter::flush
来强制通过网络发送数据;您也可以将 writer
封装在一个 block 中,以便更早删除它或显式调用 drop(writer)
。
use std::net::{TcpListener, TcpStream};
use std::io::{BufReader, BufWriter, Write, BufRead};
use std::thread;
fn main() {
thread::spawn(start_server);
let player_stream = TcpStream::connect("127.0.0.1:8000").expect("Couldn't connect");
let mut reader = BufReader::new(&player_stream);
let mut response = String::new();
reader.read_line(&mut response).expect("Could not read");
println!("Player received >{}<", response.trim());
let mut writer = BufWriter::new(&player_stream);
writer.write_all("NAME\n".as_bytes()).expect("Could not write");
}
fn start_server() {
let listener = TcpListener::bind("127.0.0.1:8000").unwrap();
fn handle_client(stream: TcpStream) {
println!("Client connected");
let mut writer = BufWriter::new(&stream);
writer.write_all("Red\n".as_bytes()).expect("could not write");
writer.flush().expect("could not flush");
let mut reader = BufReader::new(&stream);
let mut response = String::new();
reader.read_line(&mut response).expect("could not read");
println!("Server received {}", response);
}
for stream in listener.incoming() {
let stream = stream.expect("Unable to accept");
handle_client(stream);
}
}
您会注意到该程序并不总是打印出服务器的响应。那是因为退出的主线程退出了程序。
您提到您的实际案例使用 XML,其中可以嵌入换行符,使得面向行的协议(protocol)不合适。另一种常见的协议(protocol)是在发送数据本身之前发送一个长度。对此有许多可能的实现。在以前的工作中,我们以这种方式发送 XML。我们从一个长度在数据本身之前的 ASCII 编码换行符终止字符串开始。在那种情况下,将长度作为字符串的可读性是一个好处。您还可以选择发送一些字节,这些字节可以根据某种字节顺序解释为 2 的补码。
另见:
关于tcp - 简单的 Rust TCP 服务器和客户端不接收消息并且永不终止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44015638/