TCP Server
Each TCP server instance is capable of processing requests for one or more unit IDs from multiple external clients.
graph TD
A[Client #1] --> D[Modbus TCP Server]
B[Client #2] --> D
C[Client #3] --> D
subgraph Application
D --> E[UNIT ID 0x01]
D --> F[UNIT ID 0x02]
end
The DeviceMap
class is used to build the association between unit IDs and the custom read/write functionality you wish to provide to clients.
Creating a server
To create a server, first build a DeviceMap
for each unit ID that the server will answer. Then use the create_tcp_server
static method of the Server
class.
The created server will start listening on the port immediately.
The Server.CreateTcp
method takes the following arguments:
runtime
: tokio runtime used to drive the async process. See Runtime for more details.address
: IP address of the adapter on which to listen. It may be any specified as any valid IPv4 or IPv6 local endpoint, such as:127.0.0.1
for localhost only0.0.0.0
for all adapters- The IP address for a particular adapter
port
: port on which to listen for connectionfilter
:AddressFilter
which can be used to limit which external IPs may connect.max_sessions
: maximum concurrent sessions allowed by the server. When the maximum number of sessions is reached, a new connection will end the oldest session in order to limit resource usage.map
: Map of unit ids and their corresponding callback handlers.
- Rust
- C
- C++
- Java
- C#
let handler =
SimpleHandler::new(vec![false; 10], vec![false; 10], vec![0; 10], vec![0; 10]).wrap();
// map unit ids to a handler for processing requests
let map = ServerHandlerMap::single(UnitId::new(1), handler.clone());
let server = rodbus::server::spawn_tcp_server_task(
1,
"127.0.0.1:502".parse()?,
map,
AddressFilter::Any,
DecodeLevel::default(),
)
.await?;
rodbus_write_handler_t write_handler = {
.write_single_coil = on_write_single_coil,
.write_single_register = on_write_single_register,
.write_multiple_coils = on_write_multiple_coils,
.write_multiple_registers = on_write_multiple_registers,
};
rodbus_device_map_t* map = rodbus_device_map_create();
rodbus_device_map_add_endpoint(map,
1, // Unit ID
write_handler, // Handler for write requests
(rodbus_database_callback_t){.callback = configure_db } // Callback for the initial state of the database
);
rodbus_server_t* server = NULL;
rodbus_device_map_t* map = build_device_map();
rodbus_decode_level_t decode_level = rodbus_decode_level_nothing();
rodbus_address_filter_t* filter = rodbus_address_filter_any();
rodbus_param_error_t err = rodbus_server_create_tcp(runtime, "127.0.0.1", 502, filter, 100, map, decode_level, &server);
rodbus_address_filter_destroy(filter);
rodbus_device_map_destroy(map);
if (err) {
printf("Unable to initialize server: %s\n", rodbus_param_error_to_string(err));
return -1;
}
// check error
auto device_map = rodbus::DeviceMap();
auto init_transaction = rodbus::functional::database_callback([](rodbus::Database& db) {
for (uint16_t i = 0; i < 10; ++i)
{
db.add_coil(i, false);
db.add_discrete_input(i, false);
db.add_holding_register(i, 0);
db.add_input_register(i, 0);
}
});
device_map.add_endpoint(1, std::make_unique<WriterHandler>(), init_transaction);
auto filter = rodbus::AddressFilter::any();
auto server = rodbus::Server::create_tcp(runtime, "127.0.0.1", 502, filter, 100, device_map, rodbus::DecodeLevel::nothing());
DeviceMap map = new DeviceMap();
map.addEndpoint(ubyte(1), new ExampleWriteHandler(), db -> {
for(int i = 0; i < 10; i++) {
db.addCoil(ushort(i), false);
db.addDiscreteInput(ushort(i), false);
db.addHoldingRegister(ushort(i), ushort(0));
db.addInputRegister(ushort(i), ushort(0));
}
});
Server server = Server.createTcp(runtime, "127.0.0.1", ushort(502), AddressFilter.any(), ushort(100), map, DecodeLevel.nothing());
var map = new DeviceMap();
map.AddEndpoint(1, new WriteHandler(), db =>
{
for (ushort i = 0; i < 10; ++i)
{
db.AddCoil(i, false);
db.AddDiscreteInput(i, false);
db.AddHoldingRegister(i, 0);
db.AddInputRegister(i, 0);
}
});
var server = Server.CreateTcp(runtime, "127.0.0.1", 502, AddressFilter.Any(), 100, map, DecodeLevel.Nothing());
tip
In Rust, you can easily wrap your RequestHandler
implementation in a Arc<Mutex>
using the wrap()
default implementation.