|
| 1 | +// An example of an echo server using fixed buffers for reading and writing TCP streams. |
| 2 | +// A buffer registry size of two is created, to allow a maximum of two simultaneous connections. |
| 3 | + |
| 4 | +use std::{env, iter, net::SocketAddr}; |
| 5 | + |
| 6 | +use tokio_uring::{ |
| 7 | + buf::{fixed::FixedBufRegistry, BoundedBuf}, |
| 8 | + net::{TcpListener, TcpStream}, |
| 9 | +}; // BoundedBuf for slice method |
| 10 | + |
| 11 | +// A contrived example, where just two fixed buffers are created. |
| 12 | +const POOL_SIZE: usize = 2; |
| 13 | + |
| 14 | +fn main() { |
| 15 | + let args: Vec<_> = env::args().collect(); |
| 16 | + |
| 17 | + let socket_addr = if args.len() <= 1 { |
| 18 | + "127.0.0.1:0" |
| 19 | + } else { |
| 20 | + args[1].as_ref() |
| 21 | + }; |
| 22 | + let socket_addr: SocketAddr = socket_addr.parse().unwrap(); |
| 23 | + |
| 24 | + tokio_uring::start(accept_loop(socket_addr)); |
| 25 | +} |
| 26 | + |
| 27 | +// Bind to address and accept connections, spawning an echo handler for each connection. |
| 28 | +async fn accept_loop(listen_addr: SocketAddr) { |
| 29 | + let listener = TcpListener::bind(listen_addr).unwrap(); |
| 30 | + |
| 31 | + println!( |
| 32 | + "Listening on {}, fixed buffer pool size only {POOL_SIZE}", |
| 33 | + listener.local_addr().unwrap() |
| 34 | + ); |
| 35 | + |
| 36 | + // Other iterators may be passed to FixedBufRegistry::new also. |
| 37 | + let registry = FixedBufRegistry::new(iter::repeat(vec![0; 4096]).take(POOL_SIZE)); |
| 38 | + |
| 39 | + // Register the buffers with the kernel, asserting the syscall passed. |
| 40 | + |
| 41 | + registry.register().unwrap(); |
| 42 | + |
| 43 | + loop { |
| 44 | + let (stream, peer) = listener.accept().await.unwrap(); |
| 45 | + |
| 46 | + tokio_uring::spawn(echo_handler(stream, peer, registry.clone())); |
| 47 | + } |
| 48 | +} |
| 49 | + |
| 50 | +// A loop that echoes input to output. Use one fixed buffer for receiving and sending the response |
| 51 | +// back. Once the connection is closed, the function returns and the fixed buffer is dropped, |
| 52 | +// getting the fixed buffer index returned to the available pool kept by the registry. |
| 53 | +async fn echo_handler(stream: TcpStream, peer: SocketAddr, registry: FixedBufRegistry) { |
| 54 | + println!("peer {} connected", peer); |
| 55 | + |
| 56 | + // Get one of the two fixed buffers. |
| 57 | + // If neither is unavailable, print reason and return immediately, dropping this connection; |
| 58 | + // be nice and shutdown the connection before dropping it so the client sees the connection is |
| 59 | + // closed immediately. |
| 60 | + |
| 61 | + let mut fbuf = registry.check_out(0); |
| 62 | + if fbuf.is_none() { |
| 63 | + fbuf = registry.check_out(1); |
| 64 | + }; |
| 65 | + if fbuf.is_none() { |
| 66 | + let _ = stream.shutdown(std::net::Shutdown::Write); |
| 67 | + println!("peer {} closed, no fixed buffers available", peer); |
| 68 | + return; |
| 69 | + }; |
| 70 | + |
| 71 | + let mut fbuf = fbuf.unwrap(); |
| 72 | + |
| 73 | + let mut n = 0; |
| 74 | + loop { |
| 75 | + // Each time through the loop, use fbuf and then get it back for the next |
| 76 | + // iteration. |
| 77 | + |
| 78 | + let (result, fbuf1) = stream.read_fixed(fbuf).await; |
| 79 | + fbuf = { |
| 80 | + let read = result.unwrap(); |
| 81 | + if read == 0 { |
| 82 | + break; |
| 83 | + } |
| 84 | + assert_eq!(4096, fbuf1.len()); // To prove a point. |
| 85 | + |
| 86 | + let (res, nslice) = stream.write_fixed_all(fbuf1.slice(..read)).await; |
| 87 | + |
| 88 | + let _ = res.unwrap(); |
| 89 | + println!("peer {} all {} bytes ping-ponged", peer, read); |
| 90 | + n += read; |
| 91 | + |
| 92 | + // Important. One of the points of this example. |
| 93 | + nslice.into_inner() // Return the buffer we started with. |
| 94 | + }; |
| 95 | + } |
| 96 | + let _ = stream.shutdown(std::net::Shutdown::Write); |
| 97 | + println!("peer {} closed, {} total ping-ponged", peer, n); |
| 98 | +} |
0 commit comments