简单的Rust TCP服务器和客户端不会接收消息并且永远不会终止 [英] Simple Rust TCP server and client do not receive messages and never terminates

查看:80
本文介绍了简单的Rust TCP服务器和客户端不会接收消息并且永远不会终止的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试生成服务器并将其连接到其他线程.我知道Rust阻止了I/O,但是我觉得我应该能够在不同的线程中连接服务器.我对线程不了解很多.最终的游戏是通过网络连接到该服务器.这就是我使用 player_stream TCPStream 模拟的内容. player_stream 将等待,直到缓冲区中有东西为止.一旦在其中写入了内容,它将响应服务器.照原样,该程序将不会终止.

I am trying to spawn a server and connect to it on a different thread. I know Rust has blocking I/O, but I feel like I should be able to connect a server in a different thread. I do not have a lot of knowledge in threads. The end game is to connect to this server across a network. That is what I am simulating with the player_stream TCPStream. The player_stream will wait until there is something in its buffer. Once something has been written there, it will respond back to the server. As is, the program will not terminate.

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:必须使用的未使用结果.这些情况中的每一个都可能是您的代码失败而您甚至都不知道的情况.自由使用 expect

First off, don't ignore warnings. You have 4 errors of the type warning: unused result which must be used. Every single one of those could be cases where your code is failing and you wouldn't even know it. Use expect liberally!

第二,您有一个开放的客户端读取套接字,并且您要求读取所有数据,直到最后成一个字符串".什么决定结束?在这种情况下,是插座关闭的时候.那是什么时候?

Second, you have an open client read socket and you ask to "read all the data until the end into a string". What determines the end? In this case, it's when the socket is closed; so when is that?

技巧问题!

  • 当服务器的写套接字关闭时,客户端的读套接字也会关闭.
  • 服务器的读取套接字关闭时,服务器的写入套接字也会关闭.
  • 客户端的写套接字关闭时,服务器的读套接字也会关闭.

那什么时候发生?因为没有专门执行此操作的代码,所以在删除套接字时它将关闭,所以:

So when does that happen? Because there's no code that does it specifically, it will close when the socket is dropped, so:

  • 客户端结束时,客户端的写套接字关闭.

因此陷入僵局.可以通过显式关闭套接字的写入部分来解决此问题:

Thus the deadlock. The issue could be fixed by explicitly closing the write half of the socket:

stream.shutdown(std::net::Shutdown::Write).expect("could not shutdown");

第三,您正在写入 BufWriter .查看它的文档:

Third, you are writing into a BufWriter. Review the documentation for it:

BufWriter 保留内存中的数据缓冲区,并将其以大批量,不常见的批量写入基础写入器.

A BufWriter keeps an in-memory buffer of data and writes it to an underlying writer in large, infrequent batches.

放置 writer 时,缓冲区将被写出.

The buffer will be written out when the writer is dropped.

尝试读取响应后,将 BufWriter 放到作用域的末尾.那是另一个僵局.

The BufWriter is dropped at the end of the scope, after you've tried to read the response. That's another deadlock.

最后,您需要建立一个协议来确定如何来回限制发送的消息.一个简单但非常有限的解决方案是采用面向行的协议:每条消息都以一行以换行符结尾的行.

In the end, you need to establish a protocol for how to delimit messages sent back and forth. A simple but very limited solution is to have a line-oriented protocol: every message fits on one line ending with a newline character.

如果选择该选项,则可以改用 read_to_line .我还使用了 BufWriter :: flush 强制将数据发送到网络上.您还可以将 writer 封装在一个块中,以便将其更早删除或显式调用 drop(writer).

If you choose that, you can use read_to_line instead. I've also used BufWriter::flush to force the data to be sent down the wire; you could have also encapsulated writer in a block so it is dropped earlier or explicitly call 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);
    }
}

您会注意到,该程序并不总是打印出服务器的响应.那是因为退出的主线程退出了程序.

You'll note that the program doesn't always print out the server's response. That's because the main thread exiting exits the program.

您提到您的实际案例使用XML,该XML中可以嵌入换行符,从而使面向行的协议不合适.另一个常见的协议是在发送数据本身之前先发送一个长度.有许多可能的实现方式.在上一份工作中,我们以这种方式发送了XML.我们从一个以ASCII编码的换行符终止的字符串开始,该字符串的长度位于数据本身之前.在这种情况下,具有长度可读性作为字符串是有好处的.您还可以选择发送一定数量的字节,这些字节可以根据某些字节序解释为2的补码.

You mentioned that your real case uses XML, which can have newlines embedded in it, making a line-oriented protocol unsuitable. Another common protocol is to send a length before sending the data itself. There are many possible implementations for this. At a previous job, we sent XML in this fashion. We started with an ASCII-encoded newline-terminated string of the length before the data itself. In that case, having the readability of the length as a string was a benefit. You could also choose to send a number of bytes that can be interpreted according to some endianness as a 2's compliment number.

另请参阅:

这篇关于简单的Rust TCP服务器和客户端不会接收消息并且永远不会终止的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆