#include <iostream>
#include <vector>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdint.h>
// 必须链接 ws2_32.lib
#pragma comment(lib, "ws2_32.lib")
//读寄存器,quantity为寄存器数量
std::vector<uint8_t> createModbusTCPRequest03(uint16_t transactionId, uint8_t unitId, uint16_t startAddress, uint16_t quantity) {
std::vector<uint8_t> request;
//*****************MBAP**********************//
// 事务标识符
request.push_back(transactionId >> 8);//位右移
request.push_back(transactionId & 0xFF);//这两行代码通过位操作将16位的transactionId拆分为两个8位的部分,并按高位在前(网络字节序)的顺序加入到报文数据中,这种处理方式常用于网络通信中确保数据的跨平台兼容性和正确解析。
// 协议标识符(固定为 0)
request.push_back(0x00);
request.push_back(0x00);
// 长度(单位标识符 + 功能码 + 数据长度)
request.push_back(0x00);
request.push_back(0x06);
// 单元标识符
request.push_back(unitId);
//*****************MBAP**********************//
//*****************PDU**********************//
// 功能码(读取保持寄存器)
request.push_back(0x03);
// 起始地址高字节
request.push_back(startAddress >> 8);
// 起始地址低字节
request.push_back(startAddress & 0xFF);
// 寄存器数量高字节
request.push_back(quantity >> 8);
// 寄存器数量低字节
request.push_back(quantity & 0xFF);
//*****************PDU**********************//
return request;
}
//写寄存器,quantity为写入的值
std::vector<uint8_t> createModbusTCPRequest06(uint16_t transactionId, uint8_t unitId, uint16_t startAddress, uint16_t quantity) {
std::vector<uint8_t> request;
// 事务标识符
request.push_back(transactionId >> 8);
request.push_back(transactionId & 0xFF);
// 协议标识符(固定为 0)
request.push_back(0x00);
request.push_back(0x00);
// 长度(单位标识符 + 功能码 + 数据长度)
request.push_back(0x00);
request.push_back(0x06);
// 单元标识符
request.push_back(unitId);
// 功能码(读取保持寄存器)
request.push_back(0x06);
// 起始地址高字节
request.push_back(startAddress >> 8);
// 起始地址低字节
request.push_back(startAddress & 0xFF);
// 寄存器写入的值高字节
request.push_back(quantity >> 8);
// 寄存器写入的值低字节
request.push_back(quantity & 0xFF);
return request;
}
void printModbusTCPResponse(const std::vector<uint8_t>& response) {
std::cout << "Response content: ";
for (uint8_t byte : response) {
// 打印当前字节的十六进制形式
std::cout << std::hex << static_cast<int>(byte) << " ";
}
//std::cout << std::dec << std::endl; // 切换回十进制输出模式
}
//void parseModbusTCPResponse(const std::vector<uint8_t>& response, uint8_t expectedUnitId, uint16_t expectedQuantity) {
// // 检查长度
// //if (response.size() < 9) {
// // std::cerr << "Invalid response length" << std::endl;
// // return;
// //}
//
// 检查单元标识符和功能码
// //if (response[6] != expectedUnitId || response[7] != 0x03) {
// // std::cerr << "Invalid response" << std::endl;
// // return;
// //}
//
// 检查数据长度是否正确
// //uint8_t byteCount = response[8];
// //if (byteCount != expectedQuantity * 2) {
// // std::cerr << "Invalid data length" << std::endl;
// // return;
// //}
//
// 解析寄存器数据
// //std::cout << "Received data:" << std::endl;
// //for (size_t i = 0; i < expectedQuantity; i++) {
// // uint16_t regValue = (response[9 + i * 2] << 8) | response[10 + i * 2];
// // std::cout << "Register " << i << ": " << regValue << std::endl;
// //}
// for (int i = 0; i < response.size(); i++)
// {
// std::cout << "Register " << i << ": " << response[i] << std::endl;
// }
//}
int openTCPConnection(const char* ipAddress, int port) {
WSADATA wsaData;
int iResult;
// 初始化 Winsock
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::cerr << "WSAStartup failed: " << iResult << std::endl;
return -1;
}
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == INVALID_SOCKET) {
std::cerr << "Socket创建失败: " << WSAGetLastError() << std::endl;
WSACleanup();
return -1;
}
struct sockaddr_in server_addr;//定义一个sockaddr_in类型结构体变量server_addr
memset(&server_addr, 0, sizeof(server_addr));//这条语句的作用是将sockaddr_in结构体server_addr中的所有字段初始化为0
server_addr.sin_family = AF_INET;//设置了地址族为AF_INET,这是一个宏定义,代表IPv4
server_addr.sin_port = htons(port);//设置了待连接服务器的端口号,使用htons(host to network short)函数将主机字节序转换为网络字节序。这样可以确保在不同架构的计算机之间端口号能被正确解读。
inet_pton(AF_INET, ipAddress, &server_addr.sin_addr);// 用于设置服务器的IP地址
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "连接失败!: " << WSAGetLastError() << std::endl;
closesocket(sockfd);
WSACleanup();
return -1;
}
return sockfd;
}
//发送数据
void sendModbusTCPRequest(int sockfd, const std::vector<uint8_t>& request) {
if (send(sockfd, reinterpret_cast<const char*>(request.data()), request.size(), 0) < 0) {
std::cerr << "Error sending data: " << WSAGetLastError() << std::endl;
}
}
//接收数据
std::vector<uint8_t> receiveModbusTCPResponse(int sockfd, size_t expectedLength) {
std::vector<uint8_t> response(expectedLength);
int bytesRead = recv(sockfd, reinterpret_cast<char*>(response.data()), response.size(), 0);//reinterpret_cast可实现char型数据与char型指针之间的转换
if (bytesRead < 0) {
std::cerr << "Error receiving data: " << WSAGetLastError() << std::endl;
}
response.resize(bytesRead);
return response;
}
int main() {
const char* ipAddress = "127.0.0.1"; // 目标从机 IP 地址
int port = 502; // Modbus TCP 默认端口
int sockfd = openTCPConnection(ipAddress, port);
if (sockfd == -1) {
return -1;
}
uint16_t transactionId = 1;//事务处理标识
uint8_t unitId = 1;//设备地址
uint16_t startAddress = 1;//起始地址
uint16_t quantity = 2;//寄存器数目,或者写入的寄存器值
std::vector<uint8_t> request = createModbusTCPRequest03(transactionId, unitId, startAddress, quantity);
sendModbusTCPRequest(sockfd, request);
// 预期响应长度:事务标识符(2) + 协议标识符(2) + 长度(2) + 单元标识符(1) + 功能码(1) + 字节计数(1) + 数据(2*quantity)
size_t responseLength = 9 + 2 * quantity;
std::vector<uint8_t> response = receiveModbusTCPResponse(sockfd, responseLength);
printModbusTCPResponse(response);
//parseModbusTCPResponse(response, unitId, quantity);
closesocket(sockfd);
WSACleanup();
return 0;
}
使用Modbus Slave检验发送数据以及回应数据
点击菜单栏Disply中的Communcation可查看发送接收数据