Closed BrandonLWhite closed 10 years ago
It's better to use mailing list for question, isn't it?
So no, you can't gracefully interrupt modbus_receive but this change https://github.com/stephane/libmodbus/issues/95 could improve the situation.
Could you pursue talk on mailing list, please?
Thanks for the response Stephane. I believe you are better off capturing this in a GitHub issue as it does revolve around a fairly substantial deficiency in using libmodbus to create a slave server.
I'd very much like to avoid using timeouts to kluge a solution.
My apologies that the original issue request came across like a forum post. My intent was to formally request that libmodbus gracefully stop a blocked server call to modbus_receive(). I would greatly appreciate seeing this issue reopened and addressed -- libmodbus would be even better and more robust as result.
The libmodbus goal is to implement Modbus protocol not to provide a slave or server out the box. In a safe network, you can stop your slave on specific Modbus request or implement signal handling, see my examples in mbtools (https://github.com/webstack/mbtools/blob/master/src/collect.c#L30)
Hi, I have the same problem and the solution is two new fuction which shutdowns the server and reports to an application about the shutdown state. You can download the package from here;
http://vgrin.host56.com/software/libmodbus-master-withshutdown.zip
Additionally it contains test projects: tests\break-test-client.c and ests\break-test-server.c and projects files in tests\Win32\ . The break-test-server requires the code for UNIX – I have only Windows implementation, sorry :-). The server terminates itself from its own thread which waits some time. Of course the solution a bit risky because the library was not designed for multi-threading, but looks like not too much. :-) Maybe Stephane adds this to the official library.
I tried your implementation of this signal-handlers in mbtools. It is calling modbus_close()
which should make select()
to return gracefully. It doesn't in my case (RTU). It seems the UNIX-way to do would be the self-pipe-trick.
Without change libmodbus, what I will do is to call select on my own before calling modbus_receive
. And in my FD_SET I will add a self-pipe, which will be used by the main thread to signal the stop-condition to the slave-thread.
@pboettch can you please show the code you used in order to bypass this issue?
I haz teh codez! ;-)
My tcp-server is a single thread server handling all clients sequentially if there are multiple requests at the same time. It's using poll()
and is written in C++, here is snippet, I'll try to contribute a complete example along with my callback-implementation (which is used in this example and not yet integrated in master but pushed on my fork).
At global (or better object-scope) you will need a pipe for self-piping
int selfPipe_[2];
This is the "server"-code
modbus_t *ctx = modbus_new_tcp("0.0.0.0", port_);
int server_socket = modbus_tcp_listen(ctx, 2);
if (server_socket < 0) {
// error handling
modbus_free(ctx);
return;
}
const modbus_reply_callbacks_t callbacks{
nullptr,
&ModbusTCPServer::verify,
&ModbusTCPServer::read,
&ModbusTCPServer::write};
modbus_set_reply_callbacks(ctx, &callbacks, this); // this is the code not yet upstream
std::vector<struct pollfd> sockets; // using a vector as behind it is a C-array and can be passed to poll()
sockets.push_back({selfPipe_[0], POLLIN, 0}); // add self-pipe to poll-list
sockets.push_back({server_socket, POLLIN, 0}); // add the server-socket
bool running = true;
while (running) {
int ret = poll(sockets.data(), sockets.size(), -1); // poll on all sockets, infinitely
if (ret < 0) {
// error handling
break;
}
// poll woke up, something happened on one of the sockets
struct pollfd new_socket { // for later use if new client connection
- 1
};
for (auto p = sockets.begin(); p != sockets.end();) { // which socket has triggered the wakeup?
if (p->fd == selfPipe_[0]) { // was it the self-pipe, we will stop here
if (p->revents != 0) {
running = false;
break;
}
} else if (p->fd == server_socket) { // server-socket - new client
if (p->revents & POLLIN) {
std::cerr << "new connection\n";
int fd = modbus_tcp_accept(ctx, &server_socket);
if (fd >= 0)
new_socket = {fd, POLLIN, 0}; // prepare poll-structure for new client
else {
// error handling
}
} else if (p->revents != 0) { // server socket closed, end
running = false;
break;
}
} else { // handle one client request
if (p->revents & POLLIN) {
modbus_set_socket(ctx, p->fd); // set the socket to libmodbus answers to the right client
uint8_t query[MODBUS_RTU_MAX_ADU_LENGTH];
int rc = modbus_receive(ctx, query);
if (rc <= 0) { // connection has been closed actually by the client
::close(p->fd);
p = sockets.erase(p); // remove it from the poll-array
continue;
}
/* rc is the query size */
rc = modbus_reply_callback(ctx, query, rc); // handle the reply (via the callbacks)
if (rc < 0) {
// error handling - invalid request
::close(p->fd); // close client
p = sockets.erase(p);
continue;
}
} else if (p->revents != 0) {
// handle unexpected revents for user-connection
::close(p->fd);
p = sockets.erase(p);
continue;
}
}
p++;
}
if (new_socket.fd != -1) // we have seen a new connection coming, doing it outside the loop because cannot add an element to vector while looping
sockets.push_back(new_socket);
}
// stopping server -> closing all connections
for (auto &p : sockets)
::close(p.fd);
modbus_close(ctx);
modbus_free(ctx);
You need to run a thread for the code above, before you need to create the self-pipe
if (pipe(selfPipe_) != 0) // oh oh!
// error handling
and when you're done with your work to stop the thread you do
close(selfPipe_[1]);
I need the ability to gracefully close/exit/abort a Modbus server thread that is blocked in modbus_receive(). At present, I am unable to get the blocking call to modbus_receive() to return. Invoking modbus_close() on the context from another thread does not abort the blocking call. On Linux, it appears to have no effect. On Windows, the modbus_close() call blocks indefinitely as well.
There does not seem to be any test coverage or examples of this, so my assumption is that this is not currently achievable in libmodbus. Please let me know if I am wrong, and I'd appreciate some guidance.
Note: Terminating the server thread is not a viable solution. I need to be able to gracefully get the call to modbus_receive to return.