TCP Server

One or more outstation instances may be bound to a TCP server using an AddressFilter. Unlike most server protocols, each outstation instance may only communicate with a single client at a time. This is because DNP3 is stateful and maintains event data for a particular master. If you need to support more than one master connection on the same port, you must preconfigure each master IP address and associate it with a particular outstation.

graph TD A[Master #1] --> B[TCP Server ] C[Master #2] --> B subgraph Application B --> D[Outstation #1] B --> E[Outstation #2] end

Creating a server#

The first step is to create a TcpServer instance. Creating a server does not initiate bind/listen.

Addresses may be any specified as any valid IPv4 or IPv6 local endpoint, e.g.:

  • 127.0.0.1 for localhost only
  • 0.0.0.0 for all adapters
  • IP address of a particular adapter

TcpServer creation can fail if the endpoint is not well formed: <address>:<port>.

let mut server = TcpServer::new(LinkErrorMode::Close, "127.0.0.1:20000".parse()?);
tip

The LinkErrorMode controls what happens when malformed link-layer frames are received. The typical behavior is to close the socket as data should never be corrupted over TCP. This value may be set to Discard in which case the parser will discard errors and search for the start of the next frame. This behavior is required if you are connecting to a terminal server which bridges TCP to a serial port.

Adding Outstation(s)#

You can now associate one or more outstations with the TcpServer. The TcpServer::addOutstation method takes all of the components discussed in previous sections, followed by an AddressFilter. The filter controls which masters are allowed to associate with a particular outstation. If the filter overlaps with the filter of an existing outstation, i.e. they would both allow the same address, then TcpServer::addOutstation will fail.

let outstation = server.add_outstation(
get_outstation_config(),
get_event_buffer_config(),
DefaultOutstationApplication::create(),
DefaultOutstationInformation::create(),
DefaultControlHandler::with_status(CommandStatus::NotSupported),
NullListener::create(),
AddressFilter::Any,
)?;
note

The terminology differs here between Rust and bindings. The Rust API's add_outstation method doesn't spawn it onto the runtime.

The examples above use AddressFilter.any() to allow any master IP address to connect:

graph LR A[Master] --> B[TCP Server ] subgraph Application B --*.*.*.*--> c[Outstation] end

If an outstation is already connected to a master, and another matching IP address attempts to connect, the existing connection will be closed and the outstation will begin a communication session with the new master.

ConnectionStateListener#

The ConnectionStateListener interface is only specified for connection oriented transport (e.g. TCP) when adding an outstation to the server. It has a single method that informs user code when a TCP connection has been established to the outstation. The listener only fires a connected event when an IP address matching the AddressFilter is received. If the outstation is already connected and processing a connection, and another matching connection is received, then the listener will fire a disconnected event before firing a connected event.

Binding the Server#

When all of the outstation associated with the server have been added, it is time to bind the server and begin accepting connections.

// dropping the ServerHandle shuts down the server and outstation(s)
let _server_handle = server.bind().await?;
note

The terminology differs here between Rust and bindings.

Binding may fail if the underlying socket bind/listen calls fail, e.g. if another process is already bound to that port.