Crate socket_config
source ·Expand description
This library sets up sockets in a way that can be controlled by the user of your application, such as through a command-line option or configuration file.
For example, your application might take a command-line option --listen=SOCKET
, where SOCKET
is a socket address that this library parses. Socket addresses can take forms like 127.0.0.1:12345
(IPv4), [::1]:12345
(IPv6), ./my.socket
(Unix-domain), or fd:3
(Unix file descriptor or Windows socket handle inherited from the parent process). See the SocketAddr
documentation for complete syntax.
Usage
The entry point of this library is the open
function, which accepts a socket address and a set of options, and opens a socket accordingly.
open
returns a socket2::Socket
, which can be used for ordinary blocking I/O. This library also has the AnyStdSocket
type in the convert
module, which can be used to convert a socket2::Socket
into one of the standard library’s socket types.
For non-blocking I/O with tokio
, the convert
module includes AnyTokioListener
and AnyTokioStream
.
Feature flags and platform support
This library is based on socket2
, and should work on any platform that socket2
works on. A list of platforms supported by socket2
can be found on its crates.io page.
Some items in this crate are limited in which platforms they’re available on, or behave differently on different platforms, or are only available if a particular feature flag is enabled. Such differences are noted with an “Availability” section in those items’ documentation.
Available feature flags
This library has the following feature flags:
clap
: Support parsing socket options from the command line usingclap
. Specifically, this adds an implementation of [clap::Args
] forSocketUserOptions
.futures
: Adds an implementation of [futures::Stream
] forAnyTokioListener
. Only works if thetokio
feature is also enabled; otherwise, this feature does nothing.serde
: Support parsing socket options from configuration files or environment variables usingserde
. Specifically, this adds an implementation ofserde::Deserialize
toSocketAddr
andSocketUserOptions
.tokio
: Adds the utility typesAnyTokioListener
andAnyTokioStream
.
Examples
Blocking I/O example
This is a CHARGEN server that accepts a single connection. It uses blocking I/O.
Click to show example
use anyhow::Context as _;
use socket_config::{
SocketAddr,
SocketAppOptions,
SocketUserOptions,
};
use socket2::Socket;
use std::io::Write;
/// A simple chargen server that listens on a stream socket, accepts one connection,
/// and endlessly sends characters to the client.
#[derive(clap::Parser)]
struct CommandLine {
#[command(flatten)]
options: SocketUserOptions,
socket: SocketAddr,
}
fn main() -> anyhow::Result<()> {
// Parse the command line options.
let command_line = <CommandLine as clap::Parser>::parse();
// Set up the `SocketAppOptions`.
//
// In this example, we'll set a default port of 27910, and leave the other
// options alone.
let mut socket_app_options = SocketAppOptions::new(socket2::Type::STREAM);
socket_app_options.default_port = Some(27910);
// Open the socket.
let socket: Socket = socket_config::open(
&command_line.socket,
&socket_app_options,
&command_line.options,
).context("couldn't open socket")?;
// Wait for and accept a connection.
let (mut connection, _): (Socket, _) = loop {
let result = socket.accept();
// On some platforms, `accept` can fail due to the system call being
// interrupted. When it does, just try again.
if matches!(&result, Err(e) if e.kind() == std::io::ErrorKind::Interrupted) {
continue;
}
break result
}.context("couldn't accept a connection")?;
// Close the listening socket once a connection is established.
drop(socket);
// Generate the characters we're going to send to the client: everything in the ASCII range.
let chars: Vec<u8> = (b' '..b'~').into_iter().collect();
// Send characters repeatedly until the client disconnects.
loop {
match connection.write_all(&chars) {
Ok(()) => {
// Bytes sent successfully. Keep going.
}
Err(error) if error.kind() == std::io::ErrorKind::WriteZero => {
// The client disconnected.
break;
}
Err(error) => {
// There was an error. Report it.
//
// Note that `write_all` *does* check for `std::io::ErrorKind::Interrupted`,
// so no need to check that here.
return Err(
anyhow::format_err!(error)
.context("couldn't send characters to client")
);
}
}
}
Ok(())
}
Non-blocking I/O example
This is an Echo server that modifies received bytes before echoing them. It uses Tokio non-blocking I/O.
Click to show example
use anyhow::Context as _;
use socket_config::{
convert::{
AnyTokioListener,
AnyTokioStream,
},
SocketAddr,
SocketAppOptions,
SocketUserOptions,
};
use socket2::Socket;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
/// A simple echo server that listens on a stream socket and echoes back all bytes to clients,
/// incremented by one.
#[derive(clap::Parser)]
struct CommandLine {
#[command(flatten)]
options: SocketUserOptions,
socket: SocketAddr,
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> anyhow::Result<()> {
// Parse the command line options.
let command_line = <CommandLine as clap::Parser>::parse();
// Set up the `SocketAppOptions`.
//
// In this example, we'll set a default port of 27910, and leave the other
// options alone.
let mut socket_app_options = SocketAppOptions::new(socket2::Type::STREAM);
socket_app_options.default_port = Some(27910);
// Open the socket.
let socket: Socket = socket_config::open(
&command_line.socket,
&socket_app_options,
&command_line.options,
).context("couldn't open socket")?;
// Set up the socket for use with Tokio.
let socket: AnyTokioListener =
socket.try_into()
.context("couldn't configure socket for Tokio")?;
// Start accepting connections.
loop {
let (connection, _): (AnyTokioStream, socket2::SockAddr) =
socket.accept().await
.context("couldn't accept a connection")?;
tokio::task::spawn(echo(connection));
}
}
async fn echo(mut connection: AnyTokioStream) {
let mut buf = [0u8; 1024];
loop {
// Read some bytes from the client.
let bytes_read = match connection.read(&mut buf).await {
Ok(0) => break,
Ok(n) => n,
Err(error) => {
eprintln!("Error reading from client: {error}!");
break
}
};
// Take a slice of the buffer, containing just the bytes that were read.
let buf = &mut buf[..bytes_read];
// Increment each byte by one.
for byte in &mut *buf {
*byte = byte.wrapping_add(1);
}
// Echo the bytes back.
if let Err(error) = connection.write_all(buf).await {
eprintln!("Error writing to client: {error}!");
break;
}
}
}
Related libraries
socket2
: Basis of this library.tokio_listener
: Inspired this library. This library has largely the same purpose astokio_listener
, but uses a different approach.
Modules
- Conversion to socket types besides
socket2::Socket
, such asstd::net::TcpListener
. - Various errors that can be raised by this library.
Structs
- Options for opening a socket, supplied by your application itself. This is one of the three parameters to
open
. - Options for opening a socket, supplied by the user of your application. This is one of the three parameters to
open
.
Enums
- The address to bind a socket to, or a description of an inherited socket to use. This is one of the three parameters to
open
.
Functions
- Checks whether the file at the given
path
is a Unix-domain socket. - Mark a socket as inheritable (or not), so that a child process will (or will not) inherit it.
socket_config
entry point. Opens a socket (or claims an inherited one), according to the given address and options.