unit-test-client.c
main函数中的初始化
根据命令行输入来判断使用哪些具体的协议,默认使用modbus tcp协议。
if (argc > 1) {
if (strcmp(argv[1], "tcp") == 0) {
use_backend = TCP;
} else if (strcmp(argv[1], "tcppi") == 0) {
use_backend = TCP_PI;
} else if (strcmp(argv[1], "rtu") == 0) {
use_backend = RTU;
} else {
printf("Usage:\n %s [tcp|tcppi|rtu] - Modbus client for unit testing\n\n", argv[0]);
exit(1);
}
} else {
use_backend = TCP;
}
若使用默认值,则会调用如下函数创建modbus_t属性ctx并初始化一部分信息。
ctx = modbus_new_tcp("127.0.0.1", 1502);
需要注意的是在该函数中有一句话比较重要:ctx->backend = &_modbus_tcp_backend;
该语句将一个结构体赋值给ctx->backend的属性,给属性的类型是modbus_backend_t
,其中存储了一些必要的信息,以及所有操作函数的指针。
接下来,调用 modbus_connect
函数连接到服务器。
if (modbus_connect(ctx) == -1) { //创建套接字并连接服务器。
fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
在 modbus_connect
函数就是通过ctx->backend
的函数指针调用_modbus_tcp_connect函数。
在_modbus_tcp_connect函数中如下函数实现连接到服务器。
static int _modbus_tcp_connect(modbus_t *ctx)
{
int rc;
/* Specialized version of sockaddr for Internet socket address (same size) */
struct sockaddr_in addr;
modbus_tcp_t *ctx_tcp = ctx->backend_data;
int flags = SOCK_STREAM;
#ifdef SOCK_CLOEXEC
flags |= SOCK_CLOEXEC;
#endif
//设置套接字为非阻塞模式
#ifdef SOCK_NONBLOCK
flags |= SOCK_NONBLOCK;
#endif
ctx->s = socket(PF_INET, flags, 0);
//在该函数中设置套接字为立即发送,默认为缓冲区满才发送。
rc = _modbus_tcp_set_ipv4_options(ctx->s);
//连接服务器
rc = _connect(ctx->s, (struct sockaddr *)&addr, sizeof(addr), &ctx->response_timeout);
return 0;
在_connect
函数中,使用select结合非阻塞套接字实现对connect连接超时的检测。
static int _connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen,const struct timeval *ro_tv)
{
int rc = connect(sockfd, addr, addrlen);
if (rc == -1 && errno == EINPROGRESS) {
fd_set wset;
int optval;
socklen_t optlen = sizeof(optval);
struct timeval tv = *ro_tv;
FD_ZERO(&wset);
FD_SET(sockfd, &wset);
rc = select(sockfd + 1, NULL, &wset, NULL, &tv);
/* The connection is established if SO_ERROR and optval are set to 0 */
rc = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, (void *)&optval, &optlen);
if (rc == 0 && optval == 0) {
return 0;
} else {
errno = ECONNREFUSED;
return -1;
}
}
return rc;
}
在modbus tcp中,常用的数据共有4中类型,线圈、离散量、输入寄存器和保持寄存器。其中,线圈和离散量均为位操作,而输入寄存器和保持寄存器为字操作。
分配并初始化内存,用于存储位数据,线圈和离散量公用一款缓冲区,所以需要以最大的值来分配空间。
nb_points = (UT_BITS_NB > UT_INPUT_BITS_NB) ? UT_BITS_NB : UT_INPUT_BITS_NB;
tab_rp_bits = (uint8_t *) malloc(nb_points * sizeof(uint8_t));
memset(tab_rp_bits, 0, nb_points * sizeof(uint8_t));
分配并初始化内存,存储寄存器数据。输入寄存器和保持寄存器共用一块缓冲缓冲区,所以需要以最大的值来分配空间。
nb_points = (UT_REGISTERS_NB > UT_INPUT_REGISTERS_NB) ?
UT_REGISTERS_NB : UT_INPUT_REGISTERS_NB;
tab_rp_registers = (uint16_t *) malloc(nb_points * sizeof(uint16_t));
memset(tab_rp_registers, 0, nb_points * sizeof(uint16_t));
接下来就是对一些操作的测试。
写线圈测试
rc = modbus_write_bit(ctx, UT_BITS_ADDRESS, ON);
printf("1/2 modbus_write_bit: ");
ASSERT_TRUE(rc == 1, "");
在modbus_write_bit
函数中主要调用了write_single
函数,该函数可以用于写线圈和写保持寄存器。函数定义如下:
static int write_single(modbus_t *ctx, int function, int addr, const uint16_t value)
{
req_length = ctx->backend->build_request_basis(ctx, function, addr, (int) value, req);
rc = send_msg(ctx, req, req_length);
if (rc > 0) {
rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION);
rc = check_confirmation(ctx, req, rsp, rc);
}
return rc;
}
主要分为4部分,构造请求头build_request_basis
、发送请求send_msg
、接受响应_modbus_receive_msg
以及校验响应check_confirmation
。
build_request_basis函数
函数指针build_request_basis
指向的是_modbus_tcp_build_request_basis
函数。函数内容及作用如下:
/* 构造modbus tcp通信头 */
static int _modbus_tcp_build_request_basis(modbus_t *ctx, int function,int addr, int nb,uint8_t *req)
{
modbus_tcp_t *ctx_tcp = ctx->backend_data;
/* Increase transaction ID */
if (ctx_tcp->t_id < UINT16_MAX)
ctx_tcp->t_id++;
else
ctx_tcp->t_id = 0;
/*事物处理标识,在每次通信后就要增加1,类似于TCP的 req请求序列号*/
req[0] = ctx_tcp->t_id >> 8; //高8位
req[1] = ctx_tcp->t_id & 0x00ff; //低8位
/* 协议标识,0 表示modbus tcp */
req[2] = 0;
req[3] = 0;
/* Length will be defined later by set_req_length_tcp at offsets 4
and 5 */
/* 4和5位表示长度,后续进行更改 */
/*从机地址*/
req[6] = ctx->slave;
/*功能位*/
req[7] = function;
/*起始地址*/
req[8] = addr >> 8; //高8位
req[9] = addr & 0x00ff; //低8位
/*操作的地址数量*/
req[10] = nb >> 8; //高8位
req[11] = nb & 0x00ff; //低8位
return _MODBUS_TCP_PRESET_REQ_LENGTH;
}
send_msg函数
在该函数中,主要是发送数据,以及进行一个简单的错误处理,函数send_msg
定义如下:
/* Sends a request/response */
static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)
{
int rc;
int i;
/* 直接调用send函数发送消息 */
msg_length = ctx->backend->send_msg_pre(msg, msg_length);
if (ctx->debug) {
for (i = 0; i < msg_length; i++)
printf("[%.2X]", msg[i]);
printf("\n");
}
/* 在恢复模式下,将发出写命令,直到成功,默认禁用 */
do {
/* 直接调用 send 发送 */
rc = ctx->backend->send(ctx, msg, msg_length);
if (rc == -1) {
_error_print(ctx, NULL);
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
int saved_errno = errno;
/*错误处理*/
if ((errno == EBADF || errno == ECONNRESET || errno == EPIPE)) {
/* 关掉,睡眠,重连 */
modbus_close(ctx);
_sleep_response_timeout(ctx);
modbus_connect(ctx);
} else {
/* 重连,刷新:刷新就是清空套接字的缓冲区 */
_sleep_response_timeout(ctx);
modbus_flush(ctx);
}
errno = saved_errno;
}
}
} while ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
rc == -1);
if (rc > 0 && rc != msg_length) {
errno = EMBBADDATA;
return -1;
}
return rc;
}
_modbus_receive_msg函数
函数_modbus_receive_msg
定义及解释如下:
int _modbus_receive_msg(modbus_t *ctx, uint8_t *msg, msg_type_t msg_type)
{
int rc;
fd_set rset;
struct timeval tv;
struct timeval *p_tv;
int length_to_read; //剩余读取的字节数
int msg_length = 0; //已经读取的字节数
_step_t step;
if (ctx->debug) {
if (msg_type == MSG_INDICATION) {
printf("Waiting for an indication...\n");
} else {
//客户端
printf("Waiting for a confirmation...\n");
}
}
/* 构造select监听集合 */
FD_ZERO(&rset);
FD_SET(ctx->s, &rset);
/* We need to analyse the message step by step. At the first step, we want
* to reach the function code because all packets contain this
* information. */
step = _STEP_FUNCTION;
/* 获取首次接受数据的长度,header_length为响应头,+1表示接收功能码*/
length_to_read = ctx->backend->header_length + 1;
/* 设置超时时间 */
if (msg_type == MSG_INDICATION) {
//服务器端
/* Wait for a message, we don't know when the message will be
* received */
if (ctx->indication_timeout.tv_sec == 0 && ctx->indication_timeout.tv_usec == 0) {
/* By default, the indication timeout isn't set */
p_tv = NULL;
} else {
/* Wait for an indication (name of a received request by a server, see schema) */
tv.tv_sec = ctx->indication_timeout.tv_sec;
tv.tv_usec = ctx->indication_timeout.tv_usec;
p_tv = &tv;
}
} else {
//客户端
tv.tv_sec = ctx->response_timeout.tv_sec;
tv.tv_usec = ctx->response_timeout.tv_usec;
p_tv = &tv;
}
/* 用于接收数据的循环,直到接收完成、超时或者错误才会退出 */
while (length_to_read != 0) {
/*调用_modbus_tcp_select函数,其中调用select函数实现带超时的阻塞等待*/
/* length_to_read参数在select函数中并没有使用 */
rc = ctx->backend->select(ctx, &rset, p_tv, length_to_read);
/*表示出错,进行错误处理*/
if (rc == -1) {
_error_print(ctx, "select");
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) {
int saved_errno = errno;
if (errno == ETIMEDOUT) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
} else if (errno == EBADF) {
modbus_close(ctx);
modbus_connect(ctx);
}
errno = saved_errno;
}
return -1;
}
/* 接收 响应头部以及功能码 数据,直接调用recv进行数据接收 */
rc = ctx->backend->recv(ctx, msg + msg_length, length_to_read);
if (rc == 0) {
errno = ECONNRESET;
rc = -1;
}
/* 错误处理 */
if (rc == -1) {
_error_print(ctx, "read");
if ((ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK) &&
(errno == ECONNRESET || errno == ECONNREFUSED ||
errno == EBADF)) {
int saved_errno = errno;
/* 断开重连 */
modbus_close(ctx);
modbus_connect(ctx);
/* Could be removed by previous calls */
errno = saved_errno;
}
return -1;
}
if (ctx->debug) {
int i;
for (i=0; i < rc; i++)
printf("<%.2X>", msg[msg_length + i]);
}
/* Sums bytes received */
msg_length += rc; //变量存储已经接受的数据数量,用于偏移写入缓冲区位置
/* Computes remaining bytes */
/* 计算剩余需要读取的字节数 */
length_to_read -= rc;
/* 如果数据接收完成,则进行一些处理 */
if (length_to_read == 0) {
switch (step) {
case _STEP_FUNCTION:
/* 根据 命令码 判断剩余需要读取的数量,在写线圈测试中,还需读取4个字节 */
length_to_read = compute_meta_length_after_function(
msg[ctx->backend->header_length],
msg_type);
if (length_to_read != 0) {
step = _STEP_META;
break;
} /* else switches straight to the next step */
case _STEP_META:
length_to_read = compute_data_length_after_meta(
ctx, msg, msg_type);
if ((msg_length + length_to_read) > (int)ctx->backend->max_adu_length) {
errno = EMBBADDATA;
_error_print(ctx, "too many data");
return -1;
}
step = _STEP_DATA;
break;
default:
break;
}
}
/* 如果还有数据需要读,并且还未超时,则就读取 */
if (length_to_read > 0 &&
(ctx->byte_timeout.tv_sec > 0 || ctx->byte_timeout.tv_usec > 0)) {
/* If there is no character in the buffer, the allowed timeout
interval between two consecutive bytes is defined by
byte_timeout */
tv.tv_sec = ctx->byte_timeout.tv_sec;
tv.tv_usec = ctx->byte_timeout.tv_usec;
p_tv = &tv;
}
/* else timeout isn't set again, the full response must be read before
expiration of response timeout (for CONFIRMATION only) */
}
if (ctx->debug)
printf("\n");
/* 验证完整性,该函数直接将msg_length返回 */
return ctx->backend->check_integrity(ctx, msg, msg_length);
}
check_confirmation函数
在该函数中,主要是对接受到的数据进行校验,包括响应的协议标志、功能码、数据长度等。
static int check_confirmation(modbus_t *ctx, uint8_t *req,
uint8_t *rsp, int rsp_length)
{
int rc;
int rsp_length_computed;
const int offset = ctx->backend->header_length;
const int function = rsp[offset];
/* 检查事件处理标志和协议标志 */
if (ctx->backend->pre_check_confirmation) {
rc = ctx->backend->pre_check_confirmation(ctx, req, rsp, rsp_length);
if (rc == -1) {
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
}
return -1;
}
}
/* 计算预期响应长度*/
/* 头部长度 +数据长度+0 ,与compute_meta_length_after_function函数类似*/
rsp_length_computed = compute_response_length_from_request(ctx, req);
/* 异常处理,如果function >= 0x80表示返回的是异常代码 */
if (function >= 0x80) {
if (rsp_length == (offset + 2 + (int)ctx->backend->checksum_length) &&
req[offset] == (rsp[offset] - 0x80)) {
/* Valid exception code received */
int exception_code = rsp[offset + 1];
if (exception_code < MODBUS_EXCEPTION_MAX) {
errno = MODBUS_ENOBASE + exception_code;
} else {
errno = EMBBADEXC;
}
_error_print(ctx, NULL);
return -1;
} else {
errno = EMBBADEXC;
_error_print(ctx, NULL);
return -1;
}
}
/* 检查响应总长度是否正确 */
if ((rsp_length == rsp_length_computed ||
rsp_length_computed == MSG_LENGTH_UNDEFINED) &&
function < 0x80) {
int req_nb_value;
int rsp_nb_value;
/* 如果返回的命令码不相同 */
if (function != req[offset]) {
if (ctx->debug) {
fprintf(stderr,
"Received function not corresponding to the request (0x%X != 0x%X)\n",
function, req[offset]);
}
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
}
errno = EMBBADDATA;
return -1;
}
/* 根据命令与响应中的数据长度为来计算数据长度 */
switch (function) {
case MODBUS_FC_READ_COILS:
case MODBUS_FC_READ_DISCRETE_INPUTS:
/* Read functions, 8 values in a byte (nb
* of values in the request and byte count in
* the response. */
req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
req_nb_value = (req_nb_value / 8) + ((req_nb_value % 8) ? 1 : 0);
rsp_nb_value = rsp[offset + 1];
break;
case MODBUS_FC_WRITE_AND_READ_REGISTERS:
case MODBUS_FC_READ_HOLDING_REGISTERS:
case MODBUS_FC_READ_INPUT_REGISTERS:
/* Read functions 1 value = 2 bytes */
req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
rsp_nb_value = (rsp[offset + 1] / 2);
break;
case MODBUS_FC_WRITE_MULTIPLE_COILS:
case MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
/* N Write functions */
req_nb_value = (req[offset + 3] << 8) + req[offset + 4];
rsp_nb_value = (rsp[offset + 3] << 8) | rsp[offset + 4];
break;
case MODBUS_FC_REPORT_SLAVE_ID:
/* Report slave ID (bytes received) */
req_nb_value = rsp_nb_value = rsp[offset + 1];
break;
default:
/* 1 Write functions & others */
req_nb_value = rsp_nb_value = 1;
}
/* 校验数据长度 */
if (req_nb_value == rsp_nb_value) {
rc = rsp_nb_value;
} else {
if (ctx->debug) {
fprintf(stderr,
"Quantity not corresponding to the request (%d != %d)\n",
rsp_nb_value, req_nb_value);
}
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
}
errno = EMBBADDATA;
rc = -1;
}
} else {
/* 错误处理 */
if (ctx->debug) {
fprintf(stderr,
"Message length not corresponding to the computed length (%d != %d)\n",
rsp_length, rsp_length_computed);
}
if (ctx->error_recovery & MODBUS_ERROR_RECOVERY_PROTOCOL) {
_sleep_response_timeout(ctx);
modbus_flush(ctx);
}
errno = EMBBADDATA;
rc = -1;
}
return rc;
}
unit-test-server.c
主函数
根据命令行输入来判断使用哪些具体的协议,默认使用modbus tcp协议。
if (argc > 1) {
if (strcmp(argv[1], "tcp") == 0) {
use_backend = TCP;
} else if (strcmp(argv[1], "tcppi") == 0) {
use_backend = TCP_PI;
} else if (strcmp(argv[1], "rtu") == 0) {
use_backend = RTU;
} else {
printf("Usage:\n %s [tcp|tcppi|rtu] - Modbus server for unit testing\n\n", argv[0]);
return -1;
}
} else {
/*默认为tcp方式*/
use_backend = TCP;
}
构建modbus_t
类型对象 ctx
,操作与client中的操作一致。
ctx = modbus_new_tcp("127.0.0.1", 1502); //设置地址为本地回环地址
分配空间,接受命令:
query = malloc(MODBUS_TCP_MAX_ADU_LENGTH); //为数据申请空间,按最大ADU申请
//TCP MODBUS ADU = 253 bytes + MBAP (7 bytes) = 260 bytes
模拟实际环境,构造空间,用以存放实际产生的数据:
mb_mapping = modbus_mapping_new_start_address(
UT_BITS_ADDRESS, UT_BITS_NB,
UT_INPUT_BITS_ADDRESS, UT_INPUT_BITS_NB,
UT_REGISTERS_ADDRESS, UT_REGISTERS_NB_MAX,
UT_INPUT_REGISTERS_ADDRESS, UT_INPUT_REGISTERS_NB);
在数据空间中初始化必要的信息(只读信息,这些数据只能由服务器初始化):
modbus_set_bits_from_bytes(mb_mapping->tab_input_bits, 0, UT_INPUT_BITS_NB,
UT_INPUT_BITS_TAB);
* 输入寄存器对于客户端来说是只读的,初始化只能在服务器端完成,然后由客户端进行读取。*/
for (i=0; i < UT_INPUT_REGISTERS_NB; i++) {
mb_mapping->tab_input_registers[i] = UT_INPUT_REGISTERS_TAB[i];;
}
开始监听,等待客户端连接。
if (use_backend == TCP) {
//开启监听。等待连接
s = modbus_tcp_listen(ctx, 1);
modbus_tcp_accept(ctx, &s);
} else if (use_backend == TCP_PI) {
s = modbus_tcp_pi_listen(ctx, 1);
modbus_tcp_pi_accept(ctx, &s);
} else {
rc = modbus_connect(ctx);
if (rc == -1) {
fprintf(stderr, "Unable to connect %s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
}
循环接受请求,处理数据。此部分多数为测试代码,仅有rc = modbus_reply(ctx, query, rc, mb_mapping);
为正常的功能代码。
for (;;) {
do {
//最终调用_modbus_receive_msg函数,与client接受过程相同
rc = modbus_receive(ctx, query);
/* Filtered queries return 0 */
} while (rc == 0);
/* The connection is not closed on errors which require on reply such as
bad CRC in RTU. */
if (rc == -1 && errno != EMBBADCRC) {
/* Quit */
break;
}
/* 测试代码,测试代码,测试代码 */
/* 测试行为,使用03功能码为测试代码,根据后面附带数据的不同,进行不同的测试的测试 */
if (query[header_length] == 0x03) {
/* Read holding registers */
if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 3)
== UT_REGISTERS_NB_SPECIAL) {
printf("Set an incorrect number of values\n");
MODBUS_SET_INT16_TO_INT8(query, header_length + 3,
UT_REGISTERS_NB_SPECIAL - 1);
} else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
== UT_REGISTERS_ADDRESS_SPECIAL) {
printf("Reply to this special register address by an exception\n");
modbus_reply_exception(ctx, query,
MODBUS_EXCEPTION_SLAVE_OR_SERVER_BUSY);
continue;
} else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
== UT_REGISTERS_ADDRESS_INVALID_TID_OR_SLAVE) {
const int RAW_REQ_LENGTH = 5;
uint8_t raw_req[] = {
(use_backend == RTU) ? INVALID_SERVER_ID : 0xFF,
0x03,
0x02, 0x00, 0x00
};
printf("Reply with an invalid TID or slave\n");
modbus_send_raw_request(ctx, raw_req, RAW_REQ_LENGTH * sizeof(uint8_t));
continue;
} else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
== UT_REGISTERS_ADDRESS_SLEEP_500_MS) {
printf("Sleep 0.5 s before replying\n");
usleep(500000);
} else if (MODBUS_GET_INT16_FROM_INT8(query, header_length + 1)
== UT_REGISTERS_ADDRESS_BYTE_SLEEP_5_MS) {
/* 测试低电平仅在TCP模式下可用捕获应答并发送应答字节一个字节 */
uint8_t req[] = "\x00\x1C\x00\x00\x00\x05\xFF\x03\x02\x00\x00";
int req_length = 11;
int w_s = modbus_get_socket(ctx);
if (w_s == -1) {
fprintf(stderr, "Unable to get a valid socket in special test\n");
continue;
}
/* Copy TID */
req[1] = query[1];
for (i=0; i < req_length; i++) {
printf("(%.2X)", req[i]);
usleep(5000);
rc = send(w_s, (const char*)(req + i), 1, MSG_NOSIGNAL);
if (rc == -1) {
break;
}
}
continue;
}
}
/* 重点关注,执行操作并发送响应 */
rc = modbus_reply(ctx, query, rc, mb_mapping);
if (rc == -1) {
break;
}
}
modbus_reply函数中
该函数主要响应了客户端的命令,判断命令的正确与否,并根据命令进行操作,然后合成并发送响应。
在该函数中,大部分代码思路相近,在这进行了删除处理。
需要注意的是,在这里使用了内存映射,原理很简单。
函数定义及功能如下:
int modbus_reply(modbus_t *ctx, const uint8_t *req,
int req_length, modbus_mapping_t *mb_mapping)
{
int offset;
int slave;
int function;
uint16_t address;
uint8_t rsp[MAX_MESSAGE_LENGTH];
int rsp_length = 0;
sft_t sft;
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
/* 获取请求头的长度 */
offset = ctx->backend->header_length;
slave = req[offset - 1];
function = req[offset];
/* 根据命令中的数据合成要操作的地址 */
address = (req[offset + 1] << 8) + req[offset + 2];
sft.slave = slave;
sft.function = function;
/* 合成事件处理标志 */
sft.t_id = ctx->backend->prepare_response_tid(req, &req_length);
/* Data are flushed on illegal number of values errors. */
/* 数据被刷新为非法数值错误。 */
switch (function) {
case MODBUS_FC_READ_COILS: //读线圈
case MODBUS_FC_READ_DISCRETE_INPUTS: { //读离散量
unsigned int is_input = (function == MODBUS_FC_READ_DISCRETE_INPUTS);
//选取地址
int start_bits = is_input ? mb_mapping->start_input_bits : mb_mapping->start_bits;
//最大操作位数
int nb_bits = is_input ? mb_mapping->nb_input_bits : mb_mapping->nb_bits;
//获取表
uint8_t *tab_bits = is_input ? mb_mapping->tab_input_bits : mb_mapping->tab_bits;
//获取名字
const char * const name = is_input ? "read_input_bits" : "read_bits";
//获取要操作数量
int nb = (req[offset + 3] << 8) + req[offset + 4];
/* 映射可以被移动以减少内存消耗,并且它并不总是从地址零开始 */
//获取地址偏移值
int mapping_address = address - start_bits;
if (nb < 1 || MODBUS_MAX_READ_BITS < nb) {
/* 操作数量异常 */
//合成错误响应
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_DATA_VALUE, rsp, TRUE,
"Illegal nb of values %d in %s (max %d)\n",
nb, name, MODBUS_MAX_READ_BITS);
} else if (mapping_address < 0 || (mapping_address + nb) > nb_bits) {
/* 操作地址异常 */
//合成错误响应
rsp_length = response_exception(
ctx, &sft,
MODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS, rsp, FALSE,
"Illegal data address 0x%0X in %s\n",
mapping_address < 0 ? address : address + nb, name);
} else {
rsp_length = ctx->backend->build_response_basis(&sft, rsp);
rsp[rsp_length++] = (nb / 8) + ((nb % 8) ? 1 : 0);
/* 响应IO操作 */
rsp_length = response_io_status(tab_bits, mapping_address, nb, rsp, rsp_length);
}
}
break;
default:
rsp_length = response_exception(
ctx, &sft, MODBUS_EXCEPTION_ILLEGAL_FUNCTION, rsp, TRUE,
"Unknown Modbus function code: 0x%0X\n", function);
break;
}
/* Suppress any responses when the request was a broadcast */
/* 当请求是广播时禁止任何响应 */
//如果不是广播,发送正确的响应
return (ctx->backend->backend_type == _MODBUS_BACKEND_TYPE_RTU &&
slave == MODBUS_BROADCAST_ADDRESS) ? 0 : send_msg(ctx, rsp, rsp_length);
}
内存映射
在服务器中,为了模拟真实的环境,使用了内存映射的方式。使用modbus_mapping_t
结构体来描述
typedef struct _modbus_mapping_t {
int nb_bits;
int start_bits;
int nb_input_bits;
int start_input_bits;
int nb_input_registers;
int start_input_registers;
int nb_registers;
int start_registers;
uint8_t *tab_bits;
uint8_t *tab_input_bits;
uint16_t *tab_input_registers;
uint16_t *tab_registers;
} modbus_mapping_t;
在该结构体中,主要包含了3部分内容。分别是,最大操作数、起始地址和表(tab_xxxx)。
其中,表就是用来模拟真实的寄存器,是最终要操作的地址。最大操作数是该寄存器能够操作的最大数量,也就是表的长度。
内存映射过程很简单。在startz_xxx中记录了起始地址,用户要操作的地址
减去起始地址
就是目的操作的地址
在表中的映射,即下标。
而最终得到的目的操作地址要求,大于等于0,小于等于nb_xxxx。