一、Modbus主站如何等待响应报文
一直以来对于tcp/ip client发送出去的数据,如何等待响应数据,一直是有些疑问的。一种做法是将接收注册为一个回调函数,在回调函数中处理接收到的报文,一般回调函数也是独立的一条线程,此时是异步的;另一种像modbus这种,发送出报文后,等待响应报文。这块就有操作空间了——等待多长时间?如何实现的?通过阅读libModbus库源码——使用select来实现的,抽丝剥茧!
以modbus_write_bit为例:
//socket send接口 rc = send_msg(ctx, req, req_length); if (rc > 0) { /* Used by write_bit and write_register */ uint8_t rsp[MAX_MESSAGE_LENGTH]; rc = _modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION); if (rc == -1) return -1; rc = check_confirmation(ctx, req, rsp, rc); }
// _modbus_receive_msg
接收这块主要使用select判断fd是否可读,当可读时则结合超时时间进行“闭环操作”。结合Modbus Poll的连接参数设置,更加一目了然——连接超时设置,响应时间超时设置,还有一个Delay Between Polls,这个暂不关注。
结合上次连接发送的程序,在此基础上加了带超时的接受调用,接收超时判断具体程序如下:
fd_set rset; struct timeval tv; /* Add a file descriptor to the set */ FD_ZERO(&rset); FD_SET(clientfd, &rset); // 等待5.5秒 tv.tv_sec = 5; tv.tv_usec = 500*1000; // select int s_rc; while ((s_rc = select(clientfd + 1, &rset, NULL, NULL, &tv)) == -1) { if (errno == EINTR) { if (1) { fprintf(stderr, "A non blocked signal was caught\n"); } /* Necessary after an error */ FD_ZERO(&rset); FD_SET(clientfd, &rset); } else { return -1; } }
正常接收如下:
接收等待超时如下:
二、使用libModbus编写的TCP 从站
该程序功能是将接收到的线圈值拷贝给离散输入、将接收的保持寄存器值拷贝给输入寄存器。
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH]; int master_socket; int rc; fd_set refset; fd_set rdset; /* Maximum file descriptor number */ int fdmax; time_t t_ = time(NULL); struct tm* stime_ = NULL; char tmp_[32] = {0}; char *format_time_string[128] = {0}; long long curr_time = 0; ctx = modbus_new_tcp(MODBUS_SERVER_IP, MODBUS_SERVER_PORT); //01 Coil Status (0x) //02 Input Status (1x) //03 Holding Register (4x) //04 Input Registers (3x) //TODO! mb_mapping = modbus_mapping_new( 1024, 1024, 1024, 1024); if (mb_mapping == NULL) { cout << "Failed to allocate the mapping:" << modbus_strerror(errno) << endl; modbus_free(ctx); } server_socket = modbus_tcp_listen(ctx, NB_CONNECTION); if (server_socket == -1) { cout << "Unable to listen TCP connection" << endl; modbus_free(ctx); } 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) { cout << "Server select() failure" << endl; 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)) { continue; } 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) { cout << "Server accept() error" << endl; } else { FD_SET(newfd, &refset); if (newfd > fdmax) { /* Keep track of the maximum */ fdmax = newfd; } stime_=localtime(&t_); snprintf(tmp_,sizeof(tmp_),"%04d-%02d-%02d %02d:%02d:%02d", 1900+stime_->tm_year,1+stime_->tm_mon,stime_->tm_mday, stime_->tm_hour,stime_->tm_min,stime_->tm_sec); cout << tmp_ << " New connection from " << inet_ntoa(clientaddr.sin_addr) << " " << clientaddr.sin_port << " on socket " << newfd << endl; } } else { modbus_set_socket(ctx, master_socket); rc = modbus_receive(ctx, query); if(rc > 0){ // TODO! //coil to input memcpy(mb_mapping->tab_input_bits, mb_mapping->tab_bits, 256); //hold to input memcpy(mb_mapping->tab_input_registers, mb_mapping->tab_registers, 256); //send frame modbus_reply(ctx, query, rc, mb_mapping); curr_time = (long long)get_timestamp(); get_format_time_ms((char *)format_time_string); printf("%s Send response frame \n",format_time_string); } } }}
上一篇连接测试:
tcp通信测试报告单1——connect和send
欢迎关注:
如需两个测试工程可留言“Modbus tcp”.