Async Read/Write TcpStream

Hi

I have been trying to understand and write a simple client program that connects to a remote ncat instance. It is supposed to be able to read messages from the ncat instance and also send messages to the ncat instance. The requirements will be that the read and write halves will be in infinite loops and I will terminate the program using Ctrl + C.

Previously I started a similar thread on using native_tls on TcpStream but I realized I did not even get the normal unencrypted TCP session done correctly.

I followed other users' suggestions and did some reading on async/await etc but still cannot seem to get it done properly.

Current issues

  • I can receive messages from ncat but cannot send messages to ncat.

  • Is there a standard/commonly accepted way to write such network programs (that also use TLS) that I can refer to?

  • One main issue is that reading/writing to stdin/stdout would block?

async fn read_pipe(mut read_half: futures::io::ReadHalf<async_std::net::TcpStream>) -> std::io::Result<()>
{
	loop
	{
		let mut buffer2 = [0; 256];
		read_half.read(&mut buffer2).await;
		print!("{}",std::str::from_utf8(&buffer2).unwrap());
	}

	Ok(())
}

async fn write_pipe(mut write_half: futures::io::WriteHalf<async_std::net::TcpStream>) -> std::io::Result<()>
{
	loop
	{
		let mut message = String::new();
		io::stdin().read_line(&mut message).expect("Please enter a message");
		let converted_message = message.as_bytes();
		write_half.write(converted_message).await;
	}

	Ok(())
}

async fn async_main() -> std::io::Result<()>
{
	let stream = TcpStream::connect("127.0.0.1:4444").await?;

	let (buffer_reader, mut buffer_writer) = stream.split();

	read_pipe(buffer_reader).await;
	write_pipe(buffer_writer).await;

	Ok(())
}

fn main() {
	block_on(async_main());
}

In async code, the read_pipe will have to finish first before write_pipe. This implies that nothing will get written until the read_pipe function finishes. The solution here is to instead use futures::try_join (which runs both futures). Take a look here: https://rust-lang-nursery.github.io/futures-api-docs/0.3.0-alpha.10/futures/macro.try_join.html

You could also use join instead of try_join; the difference is that try_join immediantly stops once an error is returned from either future.

1 Like

(https://docs.rs/futures/0.3.5/futures/macro.try_join.html is the current docs, should probably setup redirects from the old alpha docs).

1 Like

Hi

If I tried the following

	let read_future = read_pipe(buffer_reader);
	let write_future = write_pipe(buffer_writer);

	futures::join!(read_future,write_future);

I can send messages but I cannot receive messages now.

Then I tried to use threading but even when using join, the program will exit immediately instead of waiting for messages/sending messages.

	let read_thread = thread::spawn(move || {
		read_pipe(buffer_reader);
	});

	let write_thread = thread::spawn(move || {
		write_pipe(buffer_writer);
	});

	read_thread.join().unwrap();
	write_thread.join().unwrap();

The use of blocking IO is not allowed, and results in preventing other things from running, which is exactly what you are running into:

io::stdin().read_line(&mut message).expect("Please enter a message");
1 Like

Hi Alice

Thanks for pointing that out. What will be the correct way to do what I wanted to do? Is there a read_line version that does not block?

I also came across someone who already did something similar but when I tried the code, I can send messages but cannot receive messages (https://github.com/tuggan/rncat/blob/master/src/writer.rs).

Tokio has a nonblocking tokio::io::stdin() function analogous to the one in std. You might have to wrap it in a BufReader to read lines from it.

Hi alice

When I changed the line you mentioned

io::stdin().read_line(&mut message).expect("Please enter a message");

to this

async_std::io::stdin().read_line(&mut message).await;

The problem seems to be resolved now.