libmodbus-3.1.6 unit-test-client.c 和 unit-test-server.c源码阅读

9 篇文章 0 订阅
1 篇文章 0 订阅

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。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值