C语言 modbustcp从站支持多个主站连接

2 篇文章 0 订阅
2 篇文章 0 订阅

功能描述

基于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;
}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值