Skip to content

Commit 3340f2d

Browse files
Merge pull request #22 from NikolasK-source/main
Release 1.3.0-beta1
2 parents 5a1d7f6 + 2cd37a8 commit 3340f2d

File tree

8 files changed

+409
-60
lines changed

8 files changed

+409
-60
lines changed

CMakeLists.txt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ cmake_minimum_required(VERSION 3.13.4 FATAL_ERROR)
44
# ======================================================================================================================
55

66
# project
7-
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.2.3)
7+
project(Modbus_TCP_client_shm LANGUAGES CXX VERSION 1.3.0)
88

99
# settings
1010
set(Target "modbus-tcp-client-shm") # Executable name (without file extension!)
@@ -14,7 +14,7 @@ set(ARCHITECTURE "native") # CPU architecture to optimize for (only relev
1414
# options
1515
option(BUILD_DOC "Build documentation" OFF)
1616
option(COMPILER_WARNINGS "Enable compiler warnings" ON)
17-
option(ENABLE_MULTITHREADING "Link the default multithreading library for the current target system" OFF)
17+
option(ENABLE_MULTITHREADING "Link the default multithreading library for the current target system" ON)
1818
option(MAKE_32_BIT_BINARY "Compile as 32 bit application. No effect on 32 bit Systems" OFF)
1919
option(OPENMP "enable openmp" OFF)
2020
option(OPTIMIZE_DEBUG "apply optimizations also in debug mode" ON)
@@ -23,6 +23,8 @@ option(CLANG_TIDY "use clang-tidy" OFF)
2323
option(OPTIMIZE_FOR_ARCHITECTURE "enable optimizations for specified architecture" OFF)
2424
option(LTO_ENABLED "enable interprocedural and link time optimizations" ON)
2525
option(COMPILER_EXTENSIONS "enable compiler specific C++ extensions" OFF)
26+
option(BETA_VERSION "version is a beta version" ON)
27+
set(BETA_VERSION_NR 1)
2628

2729
# ======================================================================================================================
2830
# ======================================================================================================================
@@ -55,7 +57,12 @@ set_target_properties(${Target} PROPERTIES
5557
)
5658

5759
# compiler definitions
58-
target_compile_definitions(${Target} PUBLIC "PROJECT_VERSION=\"${CMAKE_PROJECT_VERSION}\"")
60+
if(BETA_VERSION)
61+
target_compile_definitions(${Target} PUBLIC "PROJECT_VERSION=\"${CMAKE_PROJECT_VERSION}-beta${BETA_VERSION_NR}\"")
62+
else()
63+
target_compile_definitions(${Target} PUBLIC "PROJECT_VERSION=\"${CMAKE_PROJECT_VERSION}\"")
64+
endif()
65+
5966
target_compile_definitions(${Target} PUBLIC "PROJECT_NAME=\"${CMAKE_PROJECT_NAME}\"")
6067
target_compile_definitions(${Target} PUBLIC "COMPILER_INFO=\"${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}\"")
6168
target_compile_definitions(${Target} PUBLIC "SYSTEM_INFO=\"${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_VERSION} ${CMAKE_HOST_SYSTEM_PROCESSOR}\"")

docs/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ setcap 'cap_net_bind_service=+ep' /path/to/binary
7878

7979
## Install
8080

81+
### Using the Arch User Repository (recommended for Arch based Linux distributions)
82+
The application is available as [modbus-tcp-client-shm](https://aur.archlinux.org/packages/modbus-tcp-client-shm) in the [Arch User Repository](https://aur.archlinux.org/).
83+
See the [Arch Wiki](https://wiki.archlinux.org/title/Arch_User_Repository) for information about how to install AUR packages.
84+
85+
8186
### Using the Modbus Collection Flapak Package: Shared Memory Modbus (recommended)
8287
[SHM-Modbus](https://nikolask-source.github.io/SHM_Modbus/) is a collection of the shared memory modbus tools.
8388
It is available as flatpak and published on flathub as ```network.koesling.shm-modbs```.

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ target_sources(${Target} PRIVATE main.cpp)
55
target_sources(${Target} PRIVATE modbus_shm.cpp)
66
target_sources(${Target} PRIVATE Modbus_TCP_Client.cpp)
77
target_sources(${Target} PRIVATE license.cpp)
8+
target_sources(${Target} PRIVATE Modbus_TCP_connection.cpp)
89

910

1011
# ---------------------------------------- header files (*.jpp, *.h, ...) ----------------------------------------------
1112
# ======================================================================================================================
1213
target_sources(${Target} PRIVATE modbus_shm.hpp)
1314
target_sources(${Target} PRIVATE Modbus_TCP_Client.hpp)
1415
target_sources(${Target} PRIVATE license.hpp)
16+
target_sources(${Target} PRIVATE Modbus_TCP_connection.hpp)
1517

1618

1719

src/Modbus_TCP_Client.cpp

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <netinet/tcp.h>
1313
#include <sstream>
1414
#include <stdexcept>
15+
#include <sys/poll.h>
1516
#include <sys/socket.h>
1617
#include <system_error>
1718
#include <unistd.h>
@@ -23,9 +24,15 @@ namespace TCP {
2324

2425
static constexpr int MAX_REGS = 0x10000;
2526

26-
Client::Client(const std::string &ip, unsigned short port, modbus_mapping_t *mapping, std::size_t tcp_timeout) {
27+
Client::Client(const std::string &host,
28+
const std::string &service,
29+
modbus_mapping_t *mapping,
30+
std::size_t tcp_timeout) {
31+
const char *host_str = "::";
32+
if (!(host.empty() || host == "any")) host_str = host.c_str();
33+
2734
// create modbus object
28-
modbus = modbus_new_tcp(ip.c_str(), static_cast<int>(port));
35+
modbus = modbus_new_tcp_pi(host_str, service.c_str());
2936
if (modbus == nullptr) {
3037
const std::string error_msg = modbus_strerror(errno);
3138
throw std::runtime_error("failed to create modbus instance: " + error_msg);
@@ -62,9 +69,15 @@ Client::Client(const std::string &ip, unsigned short port, modbus_mapping_t *map
6269
#endif
6370
}
6471

65-
Client::Client(const std::string &ip, unsigned short port, modbus_mapping_t **mappings, std::size_t tcp_timeout) {
72+
Client::Client(const std::string &host,
73+
const std::string &service,
74+
modbus_mapping_t **mappings,
75+
std::size_t tcp_timeout) {
76+
const char *host_str = "::";
77+
if (!(host.empty() || host == "any")) host_str = host.c_str();
78+
6679
// create modbus object
67-
modbus = modbus_new_tcp(ip.c_str(), static_cast<int>(port));
80+
modbus = modbus_new_tcp_pi(host_str, service.c_str());
6881
if (modbus == nullptr) {
6982
const std::string error_msg = modbus_strerror(errno);
7083
throw std::runtime_error("failed to create modbus instance: " + error_msg);
@@ -100,10 +113,14 @@ Client::Client(const std::string &ip, unsigned short port, modbus_mapping_t **ma
100113

101114
void Client::listen() {
102115
// create tcp socket
103-
socket = modbus_tcp_listen(modbus, 1);
116+
socket = modbus_tcp_pi_listen(modbus, 1);
104117
if (socket == -1) {
105-
const std::string error_msg = modbus_strerror(errno);
106-
throw std::runtime_error("failed to create tcp socket: " + error_msg);
118+
if (errno == ECONNREFUSED) {
119+
throw std::runtime_error("failed to create tcp socket: unknown or invalid service");
120+
} else {
121+
const std::string error_msg = modbus_strerror(errno);
122+
throw std::runtime_error("failed to create tcp socket: " + error_msg);
123+
}
107124
}
108125

109126
// set socket options
@@ -147,7 +164,6 @@ void Client::set_tcp_timeout(std::size_t tcp_timeout) {
147164
}
148165
#endif
149166

150-
151167
Client::~Client() {
152168
if (modbus != nullptr) {
153169
modbus_close(modbus);
@@ -164,32 +180,90 @@ void Client::set_debug(bool debug) {
164180
}
165181
}
166182

167-
std::string Client::connect_client() {
168-
int tmp = modbus_tcp_accept(modbus, &socket);
183+
/**
184+
* @brief convert socket address to string
185+
* @param sa socket address
186+
* @return sa as string
187+
*/
188+
static std::string sockaddr_to_str(const sockaddr_storage &sa) {
189+
char buffer[INET6_ADDRSTRLEN + 1];
190+
if (sa.ss_family == AF_INET) {
191+
auto peer_in = reinterpret_cast<const struct sockaddr_in *>(&sa);
192+
inet_ntop(sa.ss_family, &peer_in->sin_addr, buffer, sizeof(buffer));
193+
std::ostringstream sstr;
194+
return buffer;
195+
} else if (sa.ss_family == AF_INET6) {
196+
auto peer_in6 = reinterpret_cast<const struct sockaddr_in6 *>(&sa);
197+
inet_ntop(sa.ss_family, &peer_in6->sin6_addr, buffer, sizeof(buffer));
198+
std::ostringstream sstr;
199+
sstr << '[' << buffer << ']';
200+
return sstr.str();
201+
} else {
202+
return "UNKNOWN";
203+
}
204+
}
205+
206+
std::string Client::get_listen_addr() {
207+
struct sockaddr_storage sock_addr;
208+
socklen_t len = sizeof(sock_addr);
209+
int tmp = getsockname(socket, reinterpret_cast<struct sockaddr *>(&sock_addr), &len);
210+
211+
if (tmp < 0) {
212+
const std::string error_msg = modbus_strerror(errno);
213+
throw std::runtime_error("getsockname failed: " + error_msg);
214+
}
215+
216+
std::ostringstream sstr;
217+
sstr << sockaddr_to_str(sock_addr);
218+
// the port entries have the same offset and size in sockaddr_in and sockaddr_in6
219+
sstr << ':' << htons(reinterpret_cast<const struct sockaddr_in *>(&sock_addr)->sin_port);
220+
221+
return sstr.str();
222+
}
223+
224+
std::shared_ptr<Connection> Client::connect_client() {
225+
struct pollfd fd;
226+
memset(&fd, 0, sizeof(fd));
227+
fd.fd = socket;
228+
fd.events = POLL_IN;
229+
do {
230+
int tmp = poll(&fd, 1, -1);
231+
if (tmp <= 0) {
232+
if (errno == EINTR) continue;
233+
throw std::system_error(errno, std::generic_category(), "Failed to poll server socket");
234+
} else
235+
break;
236+
} while (true);
237+
238+
std::lock_guard<decltype(modbus_lock)> guard(modbus_lock);
239+
240+
int tmp = modbus_tcp_pi_accept(modbus, &socket);
169241
if (tmp < 0) {
170242
const std::string error_msg = modbus_strerror(errno);
171243
throw std::runtime_error("modbus_tcp_accept failed: " + error_msg);
172244
}
173245

174-
struct sockaddr_in peer_addr;
175-
socklen_t len = sizeof(peer_addr);
246+
struct sockaddr_storage peer_addr;
247+
socklen_t len = sizeof(peer_addr);
176248
tmp = getpeername(modbus_get_socket(modbus), reinterpret_cast<struct sockaddr *>(&peer_addr), &len);
177249

178250
if (tmp < 0) {
179251
const std::string error_msg = modbus_strerror(errno);
180252
throw std::runtime_error("getpeername failed: " + error_msg);
181253
}
182254

183-
char buffer[INET_ADDRSTRLEN];
184-
inet_ntop(peer_addr.sin_family, &peer_addr.sin_addr, buffer, sizeof(buffer));
185-
186255
std::ostringstream sstr;
187-
sstr << buffer << ':' << htons(peer_addr.sin_port);
188256

189-
return sstr.str();
257+
sstr << sockaddr_to_str(peer_addr);
258+
// the port entries have the same offset and size in sockaddr_in and sockaddr_in6
259+
sstr << ':' << htons(reinterpret_cast<const struct sockaddr_in *>(&peer_addr)->sin_port);
260+
261+
return std::make_shared<Connection>(sstr.str(), modbus_get_socket(modbus), modbus_lock, modbus, mappings);
190262
}
191263

192264
bool Client::handle_request() {
265+
std::lock_guard<decltype(modbus_lock)> guard(modbus_lock);
266+
193267
// receive modbus request
194268
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
195269
int rc = modbus_receive(modbus, query);

src/Modbus_TCP_Client.hpp

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55

66
#pragma once
77

8+
#include <memory>
89
#include <modbus/modbus.h>
10+
#include <mutex>
911
#include <string>
1012
#include <unordered_map>
1113

14+
#include "Modbus_TCP_connection.hpp"
15+
1216
namespace Modbus {
1317
namespace TCP {
1418

@@ -22,31 +26,32 @@ class Client {
2226
*mappings[MAX_CLIENT_IDS]; //!< modbus data objects (one per possible client id) (see libmodbus library)
2327
modbus_mapping_t *delete_mapping; //!< contains a pointer to a mapping that is to be deleted
2428
int socket = -1; //!< socket of the modbus connection
29+
std::mutex modbus_lock;
2530

2631
public:
2732
/*! \brief create modbus client (TCP server)
2833
*
29-
* @param ip ip to listen for incoming connections (default 0.0.0.0 (any))
30-
* @param port port to listen for incoming connections (default 502)
34+
* @param host host to listen for incoming connections (default 0.0.0.0 (any))
35+
* @param service service/port to listen for incoming connections (default 502)
3136
* @param mapping modbus mapping object for all client ids
3237
* nullptr: an mapping object with maximum size is generated
3338
* @param tcp_timeout tcp timeout (currently only available on linux systems)
3439
*/
35-
explicit Client(const std::string &ip = "0.0.0.0",
36-
short unsigned int port = 502,
40+
explicit Client(const std::string &host = "any",
41+
const std::string &service = "502",
3742
modbus_mapping_t *mapping = nullptr,
3843
std::size_t tcp_timeout = 5);
3944

4045
/**
4146
* @brief create modbus client (TCP server) with dedicated mappings per client id
4247
*
43-
* @param ip ip to listen for incoming connections
44-
* @param port port to listen for incoming connections
48+
* @param host host to listen for incoming connections
49+
* @param service service/port to listen for incoming connections
4550
* @param mappings modbus mappings (one for each possible id)
4651
* @param tcp_timeout tcp timeout (currently only available on linux systems)
4752
*/
48-
Client(const std::string &ip,
49-
short unsigned int port,
53+
Client(const std::string &host,
54+
const std::string &service,
5055
modbus_mapping_t *mappings[MAX_CLIENT_IDS],
5156
std::size_t tcp_timeout = 5);
5257

@@ -61,11 +66,17 @@ class Client {
6166
*/
6267
void set_debug(bool debug);
6368

69+
/** \brief get the address the tcp server is listening on
70+
*
71+
* @return server listening address
72+
*/
73+
std::string get_listen_addr();
74+
6475
/*! \brief wait for client to connect
6576
*
6677
* @return ip of the connected client
6778
*/
68-
std::string connect_client();
79+
std::shared_ptr<Connection> connect_client();
6980

7081
/*! \brief wait for request from Modbus Server and generate reply
7182
*

src/Modbus_TCP_connection.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (C) 2022 Nikolas Koesling <nikolas@koesling.info>.
3+
* This program is free software. You can redistribute it and/or modify it under the terms of the MIT License.
4+
*/
5+
6+
#include "Modbus_TCP_connection.hpp"
7+
8+
#include <cstring>
9+
#include <sys/poll.h>
10+
11+
namespace Modbus {
12+
namespace TCP {
13+
14+
bool Connection::handle_request() {
15+
struct pollfd fd;
16+
memset(&fd, 0, sizeof(fd));
17+
fd.fd = socket;
18+
fd.events = POLL_IN;
19+
do {
20+
int tmp = poll(&fd, 1, -1);
21+
if (tmp <= 0) {
22+
if (errno == EINTR) continue;
23+
throw std::system_error(errno, std::generic_category(), "Failed to poll client socket");
24+
} else
25+
break;
26+
} while (true);
27+
28+
29+
std::lock_guard<std::mutex> guard(modbus_lock);
30+
31+
// set client socket
32+
int restore_socket = modbus_get_socket(modbus);
33+
modbus_set_socket(modbus, socket);
34+
35+
// receive modbus request
36+
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
37+
int rc = modbus_receive(modbus, query);
38+
39+
if (rc > 0) {
40+
const auto CLIENT_ID = query[6];
41+
42+
// get mapping
43+
auto mapping = mappings[CLIENT_ID];
44+
45+
// handle request
46+
int ret = modbus_reply(modbus, query, rc, mapping);
47+
if (ret == -1) {
48+
modbus_set_socket(modbus, restore_socket);
49+
const std::string error_msg = modbus_strerror(errno);
50+
throw std::runtime_error("modbus_reply failed: " + error_msg + ' ' + std::to_string(errno));
51+
}
52+
} else if (rc == -1) {
53+
if (errno == ECONNRESET) {
54+
modbus_set_socket(modbus, restore_socket);
55+
return true;
56+
}
57+
58+
modbus_set_socket(modbus, restore_socket);
59+
const std::string error_msg = modbus_strerror(errno);
60+
throw std::runtime_error("modbus_receive failed: " + error_msg + ' ' + std::to_string(errno));
61+
}
62+
63+
modbus_set_socket(modbus, restore_socket);
64+
return false;
65+
}
66+
67+
} // namespace TCP
68+
} // namespace Modbus

0 commit comments

Comments
 (0)