I'm trying to serialize a Rust enum with serde, send it over a TCP stream, and deserialize it on the other end.
Both files import the same enum:
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
pub enum Message {
Frame {freqs: Vec<i32>, values: Vec<i32>},
Connect {addr: String},
ConnectResult {res: Result<(),String>},
Error{err:String},
}
On one end, a test server generates some random numbers to send over a TCP stream.
fn handle_connection(stream: TcpStream) {
let sleep_time = Duration::from_millis(200);
let mut rng = thread_rng();
let mut writer = std::io::BufWriter::new(stream);
loop {
// continually send fake data
let x_vals: Vec<i32> = (0..100).collect();
let mut y_vals = [0; 100];
rng.fill(&mut y_vals[..]);
let data: Message = Message::Frame{freqs: x_vals, values: y_vals.to_vec()};
let serialized = serde_json::to_string(&data).unwrap();
let deserialized: Message = serde_json::from_str(&serialized).unwrap();
println!("{:?}", deserialized); // works fine
serde_json::to_writer(&mut writer, &data)
.expect("should be able to serialize frame message");
sleep(sleep_time);
}
}
On the other, a client receives the messages.
fn net_thread(send_channel: crossbeam_channel::Sender<Message>,
recv_channel: crossbeam_channel::Receiver<Message>) {
let mut conn: Option<TcpStream> = None;
loop {
// check for messages (non-blocking)
let msg = recv_channel.try_recv();
match msg {
Err(TryRecvError::Empty) => {}
Ok(Message::Connect{addr}) => {
let stream = TcpStream::connect(addr);
match stream {
Err(e) => {
send_channel.send(Message::ConnectResult{res: Err(e.to_string())})
.expect("should be able to send message to shared thread channel");
}
Ok(obj) => {
send_channel.send(Message::ConnectResult{res: Ok(())})
.expect("should be able to send message to shared thread channel");
conn = Some(obj);
}
}
},
_ => todo!()
}
match conn {
Some(ref mut stream) => {
let reader = std::io::BufReader::new(stream);
let frame: Message = serde_json::from_reader(reader)
.expect("should be able to deserialize a frame from TCP");
send_channel.send(frame).expect("should be able to send message to shared thread channel");
}
None => {}
}
}
}
Unfortunately, the TCP stream is inserting what I assume are some NULL characters or similar that Serde is choking on:
thread 'net_io' panicked at src/bin/client.rs:175:22:
should be able to deserialize a frame from TCP: Error("trailing characters", line: 1, column: 1414)
Serializing and deserializing within the same thread (which you can see in the test server above) works fine, but sending over TCP introduces the error. The column indicated in the error is always one character longer than the len
of the serialized JSON.
How can I send a Serde-serialized enum over a TCP stream without having issues with unexpected characters?
TCP is a streaming protocol, so there is no separation between "frames". The "trailing characters" that your client sees after deserializing the first message are actually the beginning of the second message.
You can use a StreamDeserializer
to parse multiple values from the stream:
match conn {
Some(ref mut stream) => {
let reader = std::io::BufReader::new(stream);
for frame in serde_json::Deserializer::from_reader (reader).into_iter::<Message>() {
send_channel.send(frame.expect ("should be able to deserialize a frame from TCP")
.expect("should be able to send message to shared thread channel");
},
None => {}
}