C语言 modbustcp从站支持多个主站连接
功能描述
基于libmodbus库开发。
1、modbustcp作为从站,在ini文件中进行配置端口、地址、允许连接的主站数量。
2、程序中使用两个线程进行开发。
3、使用工具modbuspoll作为主站进行测试连接。
1、ini 配置文件初始化
在Linux系统中,指定路径新建一个.ini文件,设置modbus相关参数。
[MODBUS]
MODBUSIP = 192.168.1.136
MODBUSPORT = 502
MAXCLIENTCOUNT = 10
程序中初始化文件的获取,开辟空间,以及供外部函数调用接口。
/* 配置变量 */
static APP_CONF_T appConf;
/* 默认配置文件名 */
#define INT_CONFIG "/ubi/conf/modbustcp_server/config"
/**
* @brief 读取config.ini,设置appConf全局结构变量
* @note
* @retval 0-ok, -1-err
*/
int AppConf_Init()
{
APP_CONF_T *config = &appConf;
char fileName[50];
memset(config, '\0', sizeof(APP_CONF_T));
memset(fileName, 0, sizeof(fileName));
snprintf(fileName, sizeof(fileName), INT_CONFIG".ini");
if(ini_parse(fileName, handler, config) < 0)
{
printf("Can't load '%s', please check the synax.", fileName);
AppConf_UnInit();
return -1;
}
return 0;
}
static int handler(void *user, const char *section, const char *name,
const char *value)
{
APP_CONF_T *pconfig = (APP_CONF_T *)user;
pconfig->openpolicyFlag = 1;
#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0
if(MATCH("MODBUS", "MODBUSIP"))
{
pconfig->modbusIp = strdup(value);
}
else if(MATCH("MODBUS", "MODBUSPORT"))
{
pconfig->modbusPort = (uint32_t)atoi(value);
}
else if(MATCH("MODBUS", "MAXCLIENTCOUNT"))
{
pconfig->modbusClientCount = (uint32_t)atoi(value);
}
else
{
printf("unknown section:%s / name :%s", section, name);
return 0; /* unknown section/name, error */
}
return 1;
}
/**
* @brief 释放空间
* @note
* @retval None
*/
void AppConf_UnInit()
{
APP_CONF_T *pconfig = &appConf;
if(pconfig->modbusIp)
{
free((void *)pconfig->modbusIp);
}
}
/**
* @brief 获取MODBUSPORT
* @note
* @retval 数字
*/
uint32_t AppConf_GetModbusPort()
{
return appConf.modbusPort;
}
/**
* @brief 获取MODBUSIP
* @note
* @retval ip
*/
char *AppConf_GetModbusIp()
{
return appConf.modbusIp;
}
/**
* @brief 获取允许客户端连接的数量
* @note
* @retval
*/
uint32_t AppConf_GetModbusClientCount()
{
return appConf.modbusClientCount;
}
typedef struct
{
char *modbusIp; /* modbus IP 接口*/
uint32_t modbusPort; /* modbus port 接口*/
uint32_t modbusClientCount; /* 允许监听的客户端 端口数量 */
} APP_CONF_T;
uint32_t AppConf_GetModbusPort();
char *AppConf_GetModbusIp();
uint32_t AppConf_GetModbusClientCount();
2、主函数创建线程
static int interrupted = 0;
void sigint_handler(int sig)
{
interrupted = 1;
}
int main(int argc, char **argv)
{
/* 读取配置文件 */
if(AppConf_Init() == -1)
{
return -1;
}
pthread_t pid1, pid2;
int flag1 = 0, flag2 = 0;
pthread_create(&pid1, NULL, ModbusTcp_MainTask, (void *)&flag1);
pthread_detach(pid1);
pthread_create(&pid2, NULL, TimeConsuming_Task, (void *)&flag2);
pthread_detach(pid2);
signal(SIGINT, sigint_handler); // ctrl-c
signal(SIGTERM, sigint_handler); // kill -15
while(!interrupted)
{
sleep(1);
}
while( (!flag1) || (!flag2)) // wait for threads exit
{
LOG_INFO("flags:%d,%d", flag1, flag2);
usleep(100000);
}
AppConf_UnInit();
usleep(100000);
exit(EXIT_SUCCESS);
return 0;
}
3、modbustcp环境搭建
/* 增加软件看门狗 */
extern uint16_t watchDogFlag;
#define MODBUS_TCP_POLL_CONNECT_MAX 1 /* 最多支持的主机数量 */
#define MODBUS_TCP_FUNCTION 7 /* modbus tcp的function所在位置的偏移字节数 */
#define MODBUS_TCP_SLEEP 10 * 1000UL
pthread_mutex_t dataMutex = PTHREAD_MUTEX_INITIALIZER;
static modbus_t *mb = NULL;
static int server_socket = -1, clientMb = -1;
uint32_t ModbusPort = 0;
char *ModbusIp = NULL;
uint32_t client_socket_count = 0;
/* 设备 modbus 起始地址 */
#define MODBUS_TCP_MAPPING_ADDR 10001
/* 数据 modbus地址 配置*/
#define MODBUS_TCP_DATA_SIZE 20
/* 设备 */
static modbus_mapping_t *Map = NULL;
/**
* @brief
* @note
* @retval
*/
int ModbusTcp_Init()
{
// 获取ini配置文件中的初始值
ModbusPort = AppConf_GetModbusPort();
ModbusIp = AppConf_GetModbusIp();
// 这里谨慎起见,若ini文件中配置错误,防止程序出现段错误死机,else中提供一个默认值
if((ModbusPort > 0) && (ModbusIp != NULL))
{
// 创建从机TCP
mb = modbus_new_tcp(ModbusIp, ModbusPort);
printf("ModbusTcp_Init:Port : %d,Ip : %s", ModbusPort, ModbusIp);
}
else
{
mb = modbus_new_tcp("192.168.1.136", 502);
printf("ModbusTcp_Init:Port : 502,Ip : 192.168.1.136");
}
if(NULL == mb)
{
printf("create modbus-tcp error!error:%s", modbus_strerror(errno));
return -1;
}
//设置调试模式,打开会有收发报文打印
// modbus_set_debug(mb, TRUE);
// 侦听主站连接
server_socket = modbus_tcp_listen(mb, MODBUS_TCP_POLL_CONNECT_MAX);
//阻塞模式,等待主站连接请求,没有则继续等待
// modbus_tcp_accept(mb, &server_socket);
if(-1 == server_socket)
{
printf("unable to listen tcp!error:%s", modbus_strerror(errno));
modbus_free(mb);
mb = NULL;
return -1;
}
else
{
// 设置为非阻塞
int flags = fcntl(server_socket, F_GETFL, 0);
fcntl(server_socket, F_SETFL, flags | O_NONBLOCK);
}
// 设置服务端等待客户端请求超时时间
modbus_set_indication_timeout(mb, 3, 0);
printf("modbus-tcp init success!");
return 0;
}
/**
* @brief
* @note
* @retval None
*/
void ModbusTcp_Uninit()
{
printf("modbus-tcp uninit!");
modbus_mapping_free(Map);
modbus_close(mb);
modbus_free(mb);
ModbusTcp_Disconnect_Server();
ModbusTcp_Disconnect_Client();
}
/**
* @brief 用于释放之前被占用的端口
* @note
* @retval None
*/
void ModbusTcp_Start()
{
modbus_close(mb);
modbus_free(mb);
ModbusTcp_Disconnect_Server();
ModbusTcp_Disconnect_Client();
}
/**
* @brief 获取 设备端 原始数据
* @note
* @retval None
*/
void DeviceData()
{
uint8_t num = 0;
int i;
int cnt = 0;
Map = modbus_mapping_new_start_address(0, 0, 0, 0, 0, 0, MODBUS_TCP_MAPPING_ADDR, MODBUS_TCP_DATA_SIZE);
Map->tab_input_registers[cnt] =0;
Map->tab_input_registers[cnt + 1] = 0;
Map->tab_input_registers[cnt + 2] = 0;
Map->tab_input_registers[cnt + 3] = 0;
Map->tab_input_registers[cnt + 4] = 0;
}
4、检测配置文件的更新
当ini配置文件中,modbus参数发生变化时,由timeconsuming线程去判断监测变化。由modbus的线程去执行修改后的设置。
#include "modbustcp.h"
uint32_t OldModbusPort = 0;
char *OldModbusIp = NULL;
uint32_t OldModbusClientCount = 0; //当前连接的主站数量 计数
uint8_t modbusenFlag = 0;
#define INT_CONFIG "/ubi/conf/modbustcp_server/config.ini"
/**
* @brief 判断文件是否变更
* @note
* @param *filename: 要判断的文件名
* @retval 1:文件变更,0:文件未变更
*/
uint8_t fileChange(const char *fileName)
{
struct stat fileNewStat;
static time_t Time_config;
uint8_t rt = 0;
int err = stat(fileName, &fileNewStat);
if(err == 0)
{
if(fileNewStat.st_mtime != Time_config)
{
rt = 1;
Time_config= fileNewStat.st_mtime;
}
}
return rt;
}
void *TimeConsuming_Task(void *param)
{
int *outflag = (int *)param;
OldModbusPort = AppConf_GetModbusPort();
OldModbusIp = AppConf_GetModbusIp();
OldModbusClientCount = AppConf_GetModbusClientCount();
while(1)
{
watchDogFlag &= ~(1 << (TIME_CONSUM_TASK_DETECT));
if(fileChange(INT_CONFIG))
{
printf("config.ini has changed");
if(AppConf_IPCheck() < 0)
{
printf("check config.ini error");
}
/* ini文件发生修改后,对modbus参数进行更新 */
if((AppConf_GetModbusPort() != OldModbusPort) || (strcmp(AppConf_GetModbusIp(), OldModbusIp) != 0))
{
ModbusTcp_Start(); //如果不释放之前的端口,会出现更新后不生效,之前的端口一直被占用,只有重启设备才会更新
ModbusTcp_Init();
OldModbusPort = AppConf_GetModbusPort();
OldModbusIp = AppConf_GetModbusIp();
}
if(AppConf_GetModbusClientCount() != OldModbusClientCount)
{
// Moudbus_Recive();
printf("OldModbusClientCount:%d,NewModbusClientCount:%d", OldModbusClientCount, AppConf_GetModbusClientCount());
OldModbusClientCount = AppConf_GetModbusClientCount();
}
}
}
*outflag = 1;
return NULL;
}
5、多个主站连接时,逻辑处理
实验现象:
假如一个从站支持最多10个主站同时连接。
1、当设置最多10个连接,连接第11个时,提示数量超出,不会创建新端口。可以关闭一个已连接,再重新连接一个新端口。
2、当设置最多10个连接,当前已连接8个,又修改最大连接5个时,随机关闭多余的3个连接。
3、当设置最多10个连接,又修改最大连接15个时,则可以继续连接至15个。
/**
* @brief 接收函数
* @note
* @retval None
*/
void Moudbus_Recive()
{
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH] = {0};
struct timeval time_wait = {0, 100 * 1000UL};
int master_socket;
fd_set refset, rdset;
int fdmax ;
int flag = 0;
int ret = 0;
uint32_t Max_Clients, OldClient;
// 允许支持多个主站连接的关键
FD_ZERO(&refset);
FD_SET(server_socket, &refset);
fdmax = server_socket;
while(true)
{
// 这里需注意的是select这个函数问题,需要在while里面添加time参数
time_wait.tv_sec = 0;
time_wait.tv_usec = 500000;
if(mb != NULL)
{
Max_Clients = AppConf_GetModbusClientCount();//线程实时监测是否有变化
rdset = refset;
// ret = select(fdmax + 1, &rdset, NULL, NULL, NULL);//用这一句的话,程序退出时会卡死在select函数中。
ret = select(fdmax + 1, &rdset, (fd_set *)0, (fd_set *)0, &time_wait);
if(ret == -1)
{
printf("Server select() failure!error:%s", modbus_strerror(errno));
break;
}
for(master_socket = 0; master_socket <= fdmax; master_socket++)
{
if(!FD_ISSET(master_socket, &rdset))
{
continue;
}
if(master_socket == server_socket)
{
socklen_t addrlen;
struct sockaddr_in clientaddr;
int newfd;
addrlen = sizeof(clientaddr);
memset(&clientaddr, 0, sizeof(clientaddr));
newfd = accept(server_socket, (struct sockaddr *)&clientaddr, &addrlen);
if(newfd == -1)
{
printf("Server newfd failure!error:%s", modbus_strerror(errno));
break;
}
else
{
client_socket_count++;
// 当前主站连接数量,没超出设定的数量时
if(client_socket_count <= Max_Clients )
{
FD_SET(newfd, &refset);
if(newfd > fdmax)
{
fdmax = newfd;
}
printf("New connection from %s:%d on socket %d,client_socket_count:%d", inet_ntoa(clientaddr.sin_addr), clientaddr.sin_port, newfd, client_socket_count);
}
else
{
//当超过设定的连接端口数量时
printf("socket overquantity, client_socket_count:%d", client_socket_count);
client_socket_count = client_socket_count - 1;
}
}
}
else
{
modbus_set_socket(mb, master_socket);
ret = modbus_receive(mb, query);
if(client_socket_count > Max_Clients)
{
// 主动关闭当前多余的连接
for(int j = 0; j < (client_socket_count - Max_Clients); j++)
{
printf("Connection closed on socket %d,client_socket_count:%d", master_socket, client_socket_count);
client_socket_count --;
close(master_socket);
FD_CLR(master_socket, &refset);
if(master_socket == fdmax)
{
fdmax--;
}
}
}
if(ret != -1)
{
uint16_t address = (query[MODBUS_TCP_FUNCTION + 1] << 8) + query[MODBUS_TCP_FUNCTION + 2];
if(address >= MODBUS_TCP_MAPPING_ADDR)
{
// modbus_reply会自动解析modbus请求并做响应
pthread_mutex_lock(&dataMutex);
DeviceData(); //设备数据
pthread_mutex_unlock(&dataMutex);
modbus_reply(mb, query, ret, Map);
}
}
else
{
client_socket_count--;
if(client_socket_count < 0)
{ client_socket_count = 0; }
printf("Connection closed on socket %d,client_socket_count:%d", master_socket, client_socket_count);
close(master_socket);
FD_CLR(master_socket, &refset);
if(master_socket == fdmax)
{
fdmax--;
}
}
}
}
}
if(main_GetOut()) // check out
{
break;
}
}
}
/**
* @brief 主函数
* @note
* @param *param:
* @retval None
*/
void *ModbusTcp_MainTask(void *param)
{
int *outFlag = (int *)param;
int ret = 0;
printf("ModbusTcp_MainTask thread.");
ret = ModbusTcp_Init();
if(ret == -1)
{
ModbusTcp_Uninit();
*outFlag = 1;
return NULL;
}
while(1)
{
watchDogFlag &= ~(1 << (TIME_CONSUM_TASK_DETECT));
if(mb != NULL)
{
Moudbus_Recive();
}
if(main_GetOut()) // check out
{
break;
}
usleep(MODBUS_TCP_SLEEP);
}
ModbusTcp_Uninit();
*outFlag = 1;
// pthread_exit(0);
return NULL;
}