最近项目中用到libmodbus TCP通讯的过程处理数据,原项目程序无法实现断网重连,现在要求只要的有新的连接,就主动断掉上一次的连接。
也是第一次用libmodbus TCP库。学习过程及代码如下:
基础知识:
SELECT 函数用于在非阻塞中,当一个套接字或者一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型。
函数原型:
libmodbus TCP通信demo测试
#include <sys/time.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
第一个参数:int nfds—>是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
第二个参数:fd_set *readfds---->用来检查一组可读性的文件描述符。
第三个参数:fd_set *writefds---->用来检查一组可写性的文件描述符。
第四个参数:fd_set *exceptfds---->用来检查文件文件描述符是否异常
第五个参数:sreuct timeval *timeout—>是一个时间结构体,用来设置超时时间
timeout:最多等待时间,对阻塞操作则为NULL
select函数的返回值
负值:select错误
正值:表示某些文件可读或可写
0:等待超时,没有可读写或错误的文件
阻塞:
当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作的条件不满足,那么就会一直在那等待,直至满足条件;
非阻塞:
当某个事件或者任务在执行过程中,它发出一个请求操作,如果该操作需要的条件不满足,会立即返回一个标志信息告知条件不满足,不会一直等待下去。
这里在补充一个同步和异步的关系。
同步和异步的重在与多个任务的执行过程,一个任务的执行是否导致整个流程暂时等待。
阻塞和非阻塞的重点在发出一个请求操作时,进行操作的条件是否会返回一个标志信息告知。
什么是套接字?
网络套接字又叫网际插座,又叫Internet Socket。
在网络中,套接字扮演的角色正如插座一样,它是一个端点,可以与网络中的其他套接字建立连接。
微软计算机词典对于套接字给出的解释如下:
Socket is an identifier for a particular service on a particular node on a network. The socket consists of a node address and a port number, which identifies the service.
套接字是一个位于网络中特定节点的服务所具有的标识符。套接字包含一个节点地址和一个端口号,用来标识这一服务
在网络中,IP地址可以唯一确定一台主机,但准确来说,网络通讯的双方不是主机,而是运行在主机上的进程,这样的酒需要进一步确定主机中的哪个进程需要网络通讯,因此,除IP地址之外,还需要端口来唯一确定主机的通讯过程。
总结:IP地址和端口号构成了一个网络中的唯一标识符,及套接字。
套接字允许两个进行通讯,这个进程可以在同一台机器上,也可能在不同机器上运行。
套接字是使用标准Unix文件描述符来与其他计算机进行通讯的一种方式。
在Unix操作系统中,每一个读写操作都是通过读写文件描述符来完成的。一个文件描述符就是一个与打开的文件相关联的整数,它可以是一个网络连接,一个文本文件,一个终端或者其他东西。
套接字类型:流套接字(TCP),数据包套接字(UDP),原始套接字
示例一:服务端与客户端连接通信,断网无法重连:
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include "modbus.h"
#include "modbus-tcp.h"
#include "modbus-version.h"
#if defined(_WIN32)
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#define NB_CONNECTION 5
//线圈地址数据的定义
const uint16_t UT_BITS_ADDRESS = 0x13;
const uint16_t UT_BITS_NB = 0x25;
const uint8_t UT_BITS_TAB[] = { 0xCD, 0x6B, 0xB2, 0x0E, 0x1B };
//离散量输入寄存器地址数据的定义
const uint16_t UT_INPUT_BITS_ADDRESS = 0xC4;
const uint16_t UT_INPUT_BITS_NB = 0x16;
const uint8_t UT_INPUT_BITS_TAB[] = { 0xAC, 0xDB, 0x35 };
//读保持寄存器地址数据的定义
const uint16_t UT_REGISTERS_ADDRESS = 0x6B;
/* Raise a manual exception when this adress is used for the first byte */
const uint16_t UT_REGISTERS_ADDRESS_SPECIAL = 0x6C;
const uint16_t UT_REGISTERS_NB = 0x3;
const uint16_t UT_REGISTERS_TAB[] = { 0x022B, 0x0001, 0x0064 };
/* If the following value is used, a bad response is sent.
It's better to test with a lower value than
UT_REGISTERS_NB_POINTS to try to raise a segfault. */
const uint16_t UT_REGISTERS_NB_SPECIAL = 0x2;
//输入寄存器地址数据定义
const uint16_t UT_INPUT_REGISTERS_ADDRESS = 0x08;
const uint16_t UT_INPUT_REGISTERS_NB = 0x1;
const uint16_t UT_INPUT_REGISTERS_TAB[] = { 0x000A };
modbus_t *ctx = NULL;
int server_socket;
modbus_mapping_t *mb_mapping;
static void close_sigint(int dummy)
{
close(server_socket);
modbus_free(ctx);
modbus_mapping_free(mb_mapping);
exit(dummy);
}
int main(void)
{
int master_socket;
int rc;
fd_set refset;
fd_set rdset;
/* Maximum file descriptor number */
int fdmax;
ctx = modbus_new_tcp("127.0.0.1", 502);
//new一个modbus的映射空间
mb_mapping = modbus_mapping_new(
UT_BITS_ADDRESS + UT_BITS_NB,//读线圈
UT_INPUT_BITS_ADDRESS + UT_INPUT_BITS_NB,//读离散量输入
UT_REGISTERS_ADDRESS + UT_REGISTERS_NB,//读保持寄存器
UT_INPUT_REGISTERS_ADDRESS + UT_INPUT_REGISTERS_NB);//读输入寄存器
if (mb_mapping == NULL)
{
fprintf(stderr, "Failed to allocate the mapping: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
/** 初始化离散量输入寄存器 **/
modbus_set_bits_from_bytes(mb_mapping->tab_input_bits,UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB,UT_INPUT_BITS_TAB);
/** 初始化输入寄存器 **/
for (i=0; i < UT_INPUT_REGISTERS_NB; i++)
{
mb_mapping->tab_input_registers[UT_INPUT_REGISTERS_ADDRESS+i] =UT_INPUT_REGISTERS_TAB[i];
}
/**初始化读保持寄存器**/
for(i=0; i<UT_REGISTERS_NB; i++)
{
mb_mapping->tab_registers[UT_REGISTERS_ADDRESS+i]=UT_REGISTERS_TAB[i];
}
server_socket = modbus_tcp_listen(ctx, NB_CONNECTION);
signal(SIGINT, close_sigint);
/* Clear the reference set of socket */
FD_ZERO(&refset);
/* Add the server socket */
FD_SET(server_socket, &refset);
/* Keep track of the max file descriptor */
fdmax = server_socket;
for (;;)
{
rdset = refset;
if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1)
{
perror("Server select() failure.");
close_sigint(1);
}
/* Run through the existing connections looking for data to be
* read */
for (master_socket = 0; master_socket <= fdmax; master_socket++)
{
if (FD_ISSET(master_socket, &rdset))
{
if (master_socket == server_socket)
{
/* A client is asking a new connection */
socklen_t addrlen;
struct sockaddr_in clientaddr;
int newfd;
/* Handle new connections */
addrlen = sizeof(clientaddr);
memset(&clientaddr, 0, sizeof(clientaddr));
newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen);
if (newfd == -1)
{
perror("Server accept() error");
}
else
{
FD_SET(newfd, &refset);
if (newfd > fdmax)
{
/* Keep track of the maximum */
fdmax = newfd;
}
printf("New connection from %s:%d on socket %d\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd);
}
}
else
{
/* An already connected master has sent a new query */
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
int data = 0;
int address=0;
modbus_set_socket(ctx, master_socket);
rc = modbus_receive(ctx, query);
address = (query[header_length+1]<<8) + query[header_length+2];
if(query[header_length] == 0x06 )
{
//to do 功能码 写保持寄存器
}
if (rc != -1)
{
modbus_reply(ctx, query, rc, mb_mapping);
}
else
{
/* Connection closed by the client, end of server */
printf("Connection closed on socket %d\n", master_socket);
close(master_socket);
/* Remove from reference set */
FD_CLR(master_socket, &refset);
if (master_socket == fdmax)
{
fdmax--;
}
}
}
}
}
}
return 0;
}
示例二:服务端与客户端连接通信,断网可以重连,一对多(可以与多个客户端通讯):
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include "modbus.h"
#include "modbus-tcp.h"
#include "modbus-version.h"
#if defined(_WIN32)
#include <ws2tcpip.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#define NB_CONNECTION 5
//线圈地址数据的定义
const uint16_t UT_BITS_ADDRESS = 0x13;
const uint16_t UT_BITS_NB = 0x25;
const uint8_t UT_BITS_TAB[] = { 0xCD, 0x6B, 0xB2, 0x0E, 0x1B };
//离散量输入寄存器地址数据的定义
const uint16_t UT_INPUT_BITS_ADDRESS = 0xC4;
const uint16_t UT_INPUT_BITS_NB = 0x16;
const uint8_t UT_INPUT_BITS_TAB[] = { 0xAC, 0xDB, 0x35 };
//读保持寄存器地址数据的定义
const uint16_t UT_REGISTERS_ADDRESS = 0x6B;
/* Raise a manual exception when this adress is used for the first byte */
const uint16_t UT_REGISTERS_ADDRESS_SPECIAL = 0x6C;
const uint16_t UT_REGISTERS_NB = 0x3;
const uint16_t UT_REGISTERS_TAB[] = { 0x022B, 0x0001, 0x0064 };
/* If the following value is used, a bad response is sent.
It's better to test with a lower value than
UT_REGISTERS_NB_POINTS to try to raise a segfault. */
const uint16_t UT_REGISTERS_NB_SPECIAL = 0x2;
//输入寄存器地址数据定义
const uint16_t UT_INPUT_REGISTERS_ADDRESS = 0x08;
const uint16_t UT_INPUT_REGISTERS_NB = 0x1;
const uint16_t UT_INPUT_REGISTERS_TAB[] = { 0x000A };
modbus_t *ctx = NULL;
int server_socket;
modbus_mapping_t *mb_mapping;
static void close_sigint(int dummy)
{
close(server_socket);
modbus_free(ctx);
modbus_mapping_free(mb_mapping);
exit(dummy);
}
int main(void)
{
int master_socket;
int rc;
fd_set refset;
fd_set rdset;
/* Maximum file descriptor number */
int fdmax;
ctx = modbus_new_tcp("127.0.0.1", 502);
//new一个modbus的映射空间
mb_mapping = modbus_mapping_new(
UT_BITS_ADDRESS + UT_BITS_NB,//读线圈
UT_INPUT_BITS_ADDRESS + UT_INPUT_BITS_NB,//读离散量输入
UT_REGISTERS_ADDRESS + UT_REGISTERS_NB,//读保持寄存器
UT_INPUT_REGISTERS_ADDRESS + UT_INPUT_REGISTERS_NB);//读输入寄存器
if (mb_mapping == NULL)
{
fprintf(stderr, "Failed to allocate the mapping: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
/** 初始化离散量输入寄存器 **/
modbus_set_bits_from_bytes(mb_mapping->tab_input_bits,UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB,UT_INPUT_BITS_TAB);
/** 初始化输入寄存器 **/
for (i=0; i < UT_INPUT_REGISTERS_NB; i++)
{
mb_mapping->tab_input_registers[UT_INPUT_REGISTERS_ADDRESS+i] =UT_INPUT_REGISTERS_TAB[i];
}
/**初始化读保持寄存器**/
for(i=0; i<UT_REGISTERS_NB; i++)
{
mb_mapping->tab_registers[UT_REGISTERS_ADDRESS+i]=UT_REGISTERS_TAB[i];
}
server_socket = modbus_tcp_listen(ctx, NB_CONNECTION);
signal(SIGINT, close_sigint);
/* Clear the reference set of socket */
FD_ZERO(&refset);
/* Add the server socket */
FD_SET(server_socket, &refset);
/* Keep track of the max file descriptor */
fdmax = server_socket;
for (;;)
{
rdset = refset;
if (select(fdmax+1, &rdset, NULL, NULL, NULL) == -1)
{
perror("Server select() failure.");
close_sigint(1);
}
/* Run through the existing connections looking for data to be
* read */
for (master_socket = 0; master_socket <= fdmax; master_socket++)
{
if (FD_ISSET(master_socket, &rdset))
{
if (master_socket == server_socket)
{
/* A client is asking a new connection */
socklen_t addrlen;
struct sockaddr_in clientaddr;
int newfd;
/* Handle new connections */
addrlen = sizeof(clientaddr);
memset(&clientaddr, 0, sizeof(clientaddr));
newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen);
if (newfd == -1)
{
perror("Server accept() error");
}
else
{
FD_SET(newfd, &refset);
if (newfd > fdmax)
{
/* Keep track of the maximum */
fdmax = newfd;
}
printf("New connection from %s:%d on socket %d\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd);
}
}
else
{
/* An already connected master has sent a new query */
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
int data = 0;
int address=0;
modbus_set_socket(ctx, master_socket);
rc = modbus_receive(ctx, query);
address = (query[header_length+1]<<8) + query[header_length+2];
if(query[header_length] == 0x06 )
{
//to do 功能码 写保持寄存器
}
if (rc != -1)
{
modbus_reply(ctx, query, rc, mb_mapping);
}
else
{
/* Connection closed by the client, end of server */
printf("Connection closed on socket %d\n", master_socket);
close(master_socket);
/* Remove from reference set */
FD_CLR(master_socket, &refset);
if (master_socket == fdmax)
{
fdmax--;
}
}
}
}
}
}
return 0;
}
示例三:服务端与客户端连接通信,断网可以重连,一对一,只接收最新客户端连接通讯:
// 传输模式TCP
ctx = modbus_new_tcp(m_szIp1, m_ntcp_port);
if (ctx == NULL)
{
fprintf(stderr, "Unable to allocate libmodbus context\n");
bConnect = false;
}
unsigned int mb_start_bits = 0; unsigned int mb_nb_bits = 0;
unsigned int mb_start_input_bits = 0; unsigned int mb_nb_input_bits = 0;
unsigned int mb_start_registers = 0;unsigned int mb_nb_registers = 0;
unsigned int mb_start_input_registers = 0; unsigned int mb_nb_input_registers = 0;
if (protocol->m_nYxFun==0x01)
{
mb_start_bits = protocol->m_nYxStartAddr;
mb_nb_bits = protocol->m_nYx_nb;
}
if (protocol->m_nYxFun==0x02)
{
mb_start_input_bits = protocol->m_nYxStartAddr;
mb_nb_input_bits = protocol->m_nYx_nb;
}
if (protocol->m_nYcFun==0x03)
{
mb_start_registers = protocol->m_nYcStartAddr;
mb_nb_registers += protocol->m_nYc_nb*2;
}
if (protocol->m_nYcFun==0x04)
{
mb_start_input_registers = protocol->m_nYcStartAddr;
mb_nb_input_registers +=protocol->m_nYc_nb*2;
}
if (protocol->m_nDdFun==0x03)
{
mb_start_registers = protocol->m_nDdStartAddr;
mb_nb_registers +=protocol->m_nDd_nb*2;
}
if (protocol->m_nDdFun==0x04)
{
mb_start_input_registers = protocol->m_nDdStartAddr;
mb_nb_input_registers +=protocol->m_nDd_nb*2;
}
//new一个modbus的映射空间
mb_mapping = modbus_mapping_new(
mb_start_bits + mb_nb_bits,//读线圈
mb_start_input_bits + mb_nb_input_bits,//读离散量输入
mb_start_registers + mb_nb_registers,//读保持寄存器
mb_start_input_registers + mb_nb_input_registers);//读输入寄存器
//mb_mapping = modbus_mapping_new_start_address(mb_start_bits,mb_nb_bits,mb_start_input_bits,mb_nb_input_bits,mb_start_registers,mb_nb_registers,mb_start_input_registers,mb_nb_input_registers);
if (mb_mapping == NULL)
{
fprintf(stderr, "Failed to allocate the mapping: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
int rc;
fd_set refset;
fd_set rdset;
/* Maximum file descriptor number */
int fdmax;
server_socket = modbus_tcp_listen(ctx, NB_CONNECTION);
signal(SIGINT, close_sigint);
/* Clear the reference set of socket */
FD_ZERO(&refset);
/* Add the server socket */
FD_SET(server_socket, &refset);
/* Keep track of the max file descriptor */
fdmax = server_socket;
for (;;)
{
//Zx add code
rdset = refset;
int ret = select(fdmax+1, &rdset, NULL, NULL, NULL); //fdmax+1 参数是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1
if (ret == -1)
{
printf("Server select() failure.");
close_sigint(1);
}
else
{
printf("Server select() %d fdmax = %d rdset[0] = %d rdset[1] = %d rdset[2] = %d rdset[3] = %d \n", ret,fdmax, rdset.fd_array[0], rdset.fd_array[1], rdset.fd_array[2] ,rdset.fd_array[3]);
}
/** Run through the existing connections looking for data to be
* read */
if (FD_ISSET( rdset.fd_array[0], &rdset))
{
if ( rdset.fd_array[0] == server_socket)
{
/* A client is asking a new connection */
socklen_t addrlen;
struct sockaddr_in clientaddr;
int newfd;
/* Handle new connections */
addrlen = sizeof(clientaddr);
memset(&clientaddr, 0, sizeof(clientaddr));
newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen);
/*Record the first time a link descriptor*/
if(m_bNewfd)
{
m_iNewfd = newfd;
m_bNewfd = false;
}
if(m_iNewfd != newfd )
{
/* Connection closed by the client, end of server */
close(m_iNewfd);
/* Remove from reference set */
FD_CLR(m_iNewfd, &refset);
m_iNewfd = newfd;
printf(" accept -->Connection closed on socket %d\n", m_iNewfd);
}
if (newfd == -1)
{
printf("Server accept() error");
}
else
{
FD_SET(newfd, &refset);
if (newfd > fdmax)
{
/* Keep track of the maximum */
fdmax = newfd;
}
printf("New connection from %s:%d on socket %d\n", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd);
}
}
else
{
/* An already connected master has sent a new query */
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
modbus_set_socket(ctx, m_iNewfd);
rc = modbus_receive(ctx, query);
if (rc != -1)
{
//zx add processing data
bConnect = true;
modbus_reply(ctx, query, rc, mb_mapping);
uint8_t* temp_rec_buf;
uint8_t* temp_send_buf;
int temp_nSendLen =0;
int temp_nRecLen =0;
BYTE ok_send_buf[300];
BYTE ok_recv_buf[300];
ZeroMemory(ok_send_buf, sizeof(ok_send_buf));
ZeroMemory(ok_recv_buf, sizeof(ok_recv_buf));
temp_nSendLen=modbus_get_sentlengh();
temp_send_buf = modbus_get_sentbuf();
temp_nRecLen=modbus_get_recvlengh();
temp_rec_buf = modbus_get_recvbuf();
memcpy(ok_send_buf, temp_send_buf, temp_nSendLen);
memcpy(ok_recv_buf, temp_rec_buf, temp_nRecLen);
m_pShow->SaveCharIn(ok_recv_buf,temp_nRecLen,10);
m_pShow->SaveCharOut(ok_send_buf,temp_nSendLen,9);
}
else
{
//bConnect = false;
/* Connection closed by the client, end of server */
printf("Connection closed on socket %d\n", m_iNewfd);
close(m_iNewfd);
/* Remove from reference set */
FD_CLR(m_iNewfd, &refset);
}
}
}
}
附录
libmodbus协议介绍中文完整带书签版:https://download.csdn.net/download/u011251940/11341158
下载libmodbus源码包:https://www.libmodbus.org/releases/libmodbus-3.0.6.tar.gz
libmodbus使用手册:https://libmodbus.org/docs/v3.0.6/
libmodbus调试工具:https://download.csdn.net/download/u011251940/11341326
致谢
以上是对Libmodbus TCP通讯的简单整理,学习过程离不开他人的帮助,感谢源博主分享
libmodbus TCP断开重连