Day06_C语言网络编程20250718mobus重点
01.思维导图
1 什么是 modbus
他是一个在工控领域非常好用的通信写 modbus协议本质上是一个 基于 tcp 协议二次封装的一个协议 什么叫做基于tcp二次封装的协议:我们自己写的pack_t(无论静态还是动态),都是属于二次封装的协议
modbus协议是一种 “主从问答式”的协议 主机:向从机发出查询指令或者修改指令 从机:接受到指令之后回答问题 或者 做出对应打完修改
2 modbus 协议包中内容
2.1 协议包组成
第0~1 字节 :存放当前协议包的序号,从0开始
modbus协议会对协议包的序号做自增操作
第2~3 字节 :存放modbus协议的标识符
该数据固定性写 0
第4~5 字节:存放接下来的所有数据的字节数
第6 字节:放从机的序列号
一台主机允许连接多台从机,该字节就是用来明确,我要向第几台从机发送数据
第7个字节:modbus 协议的操作方式
也就是说,现在主机要如何操作从机
到底是读还是写,到底是读什么东西,写什么东西
简称 "操作码"
剩下的所有字节都是数据位
根据操作码的不同,数据位的格式也是不同的
2.2 modbus协议具体操作内容
这里的操作码,其实就类似于我们自己写打完 enum Type type,用来告诉接收端,这个协议包发过去之后干嘛用的
modbus协议一共有4个操作内容
03.测试电脑是大端还是小端???
#include <25051head.h>
int main(int argc, const char *argv[])
{char a[2]={0x03,0x00};//0x0003//0000 0011 0000 0000if(htons(3)==3){printf("大端序列..\n");}else{printf("小端序列..\n");}printf("%d\n",*(short*)a);return 0;
}
04.软件
第一种:modbus
第一种:01.Modbus Poll
第一种情况:写单个线圈(写一个bit位)【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("请输入端口号..\n");return 1;}*///atoi函数,将字符串类型转换成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4结构体,固定写AF_INET// 其功能为:标记位// 用来标记,当前传入的结构体是 ipv4 结构体,还是ipv6结构体,// 还是域套接字本地结构体addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,这里直接写个 = 0 也是没问题的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(0x0006);ms.no=0x01;ms.type=0x05;ms.buf[0]=0x00;ms.buf[1]=0x00;ms.buf[2]=0xff;ms.buf[3]=0x00;write(client,&ms,sizeof(ms));return 0;
}
第二种情况:写多个线圈(写一个bit位)【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("请输入端口号..\n");return 1;}*///atoi函数,将字符串类型转换成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4结构体,固定写AF_INET// 其功能为:标记位// 用来标记,当前传入的结构体是 ipv4 结构体,还是ipv6结构体,// 还是域套接字本地结构体addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,这里直接写个 = 0 也是没问题的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(9);ms.no=0x01;ms.type=0x0f;ms.buf[0]=0x00;ms.buf[1]=0x00;//我们写10个线圈 10->0000 1010 0000 0000 小端字节序列ms.buf[2]=0x00;//0000 0000ms.buf[3]=0x0a;//0000 1010ms.buf[4]=0x02;ms.buf[5]=0x55;ms.buf[6]=0x01;write(client,&ms,sizeof(ms));return 0;
}
第三种情况:写一个寄存器【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("请输入端口号..\n");return 1;}*///atoi函数,将字符串类型转换成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4结构体,固定写AF_INET// 其功能为:标记位// 用来标记,当前传入的结构体是 ipv4 结构体,还是ipv6结构体,// 还是域套接字本地结构体addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,这里直接写个 = 0 也是没问题的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(6);ms.no=0x01;ms.type=0x06;ms.buf[0]=0x00;ms.buf[1]=0x00;//我们写10个线圈 10->0000 1010 0000 0000 小端字节序列ms.buf[2]=0x00;//0000 0000ms.buf[3]=0xff;//0000 1010write(client,&ms,sizeof(ms));return 0;
}
第四种情况:写多个寄存器【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>
typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{/*if(argc<2){printf("请输入端口号..\n");return 1;}*///atoi函数,将字符串类型转换成整形//short port=atoi(argv[1]);int client = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET;// 如果用的是ipv4结构体,固定写AF_INET// 其功能为:标记位// 用来标记,当前传入的结构体是 ipv4 结构体,还是ipv6结构体,// 还是域套接字本地结构体addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");// 如果地址是 0.0.0.0,这里直接写个 = 0 也是没问题的if(connect(client,(struct sockaddr*)&addr,sizeof(addr))==-1){perror("bind_error");return 1;}modbus_t ms={0};ms.id=0x0000;ms.protocol=0x0000;ms.len=htons(17);ms.no=0x01;ms.type=0x10;ms.buf[0]=0x00;ms.buf[1]=0x00;//我们写5个寄存器 10->0000 1010 0000 0000 小端字节序列ms.buf[2]=0x00;//0000 0000ms.buf[3]=0x05;//0000 1010//5个寄存器需要10个字节ms.buf[4]=0x0a;ms.buf[5]=0x00;ms.buf[6]=0x0a;ms.buf[7]=0x00;ms.buf[8]=0x14;ms.buf[9]=0x00;ms.buf[10]=0x1e;ms.buf[11]=0x00;ms.buf[12]=0x28;ms.buf[13]=0x00;ms.buf[14]=0x32;write(client,&ms,sizeof(ms));return 0;
}
第五种情况:读寄存器【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】特别特别注意:读普通线圈和读离散线圈01和02回复的是不允许访问
#include <25051head.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{int client = socket(AF_INET, SOCK_STREAM, 0);if (client == -1) {perror("socket creation failed");return 1;}struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");if (connect(client, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect failed");close(client);return 1;}// 构造读普通线圈请求报文modbus_t read_coils_msg = {0};read_coils_msg.id = 0x0000;read_coils_msg.protocol = 0x0000;read_coils_msg.len = htons(6); // 功能码 + 起始地址 + 线圈数量共 6 字节read_coils_msg.no = 0x01; // 从站地址read_coils_msg.type = 0x03; // 读普通线圈功能码read_coils_msg.buf[0] = 0x00; // 起始地址高字节read_coils_msg.buf[1] = 0x00; // 起始地址低字节,从地址 0 开始读read_coils_msg.buf[2] = 0x00; // 要读取的线圈数量高字节read_coils_msg.buf[3] = 0x08; // 要读取的线圈数量低字节,读取 8 个线圈// 发送读普通线圈请求ssize_t sent_bytes = write(client, &read_coils_msg, sizeof(read_coils_msg));if (sent_bytes == -1) {perror("write read coils request failed");close(client);return 1;}printf("Read coils request sent successfully.\n");// 接收响应uint8_t response[256];ssize_t recv_bytes = read(client, response, sizeof(response));if (recv_bytes == -1) {perror("read response failed");close(client);return 1;} else if (recv_bytes == 0) {printf("No response received.\n");} else {printf("Received %zd bytes response:\n", recv_bytes);for (int i = 0; i < recv_bytes; i++) {printf("%02x ", response[i]);}printf("\n");}close(client);return 0;
}
第六种情况:写单个寄存器【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{int client = socket(AF_INET, SOCK_STREAM, 0);if (client == -1) {perror("socket creation failed");return 1;}struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");if (connect(client, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect failed");close(client);return 1;}// 构造写单个寄存器请求报文modbus_t write_single_reg_msg = {0};write_single_reg_msg.id = 0x0000;write_single_reg_msg.protocol = 0x0000;write_single_reg_msg.len = htons(6); // 功能码 + 寄存器地址 + 寄存器值共 6 字节write_single_reg_msg.no = 0x01; // 从站地址write_single_reg_msg.type = 0x06; // 写单个寄存器功能码write_single_reg_msg.buf[0] = 0x00; // 寄存器地址高字节write_single_reg_msg.buf[1] = 0x00; // 寄存器地址低字节,选择地址 0write_single_reg_msg.buf[2] = 0x00; // 要写入的值高字节write_single_reg_msg.buf[3] = 0x12; // 要写入的值低字节,写入 0x0012// 发送写单个寄存器请求ssize_t sent_bytes = write(client, &write_single_reg_msg, sizeof(write_single_reg_msg));if (sent_bytes == -1) {perror("write single register request failed");close(client);return 1;}printf("Write single register request sent successfully.\n");// 接收响应uint8_t response[256];ssize_t recv_bytes = read(client, response, sizeof(response));if (recv_bytes == -1) {perror("read response failed");close(client);return 1;} else if (recv_bytes == 0) {printf("No response received.\n");} else {printf("Received %zd bytes response:\n", recv_bytes);for (int i = 0; i < recv_bytes; i++) {printf("%02x ", response[i]);}printf("\n");}close(client);return 0;
}
第七种情况:写多个寄存器【特别注意,代码中绑定的ip地址是cmd-->ipconfig-->WLAN(IPV4地址)】
#include <25051head.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;typedef struct modbus{uint16_t id;uint16_t protocol;uint16_t len;uint8_t no;uint8_t type;uint8_t buf[256];
}modbus_t;int main(int argc, const char *argv[])
{int client = socket(AF_INET, SOCK_STREAM, 0);if (client == -1) {perror("socket creation failed");return 1;}struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(502);addr.sin_addr.s_addr = inet_addr("192.168.127.225");if (connect(client, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("connect failed");close(client);return 1;}// 要写入的寄存器数量uint16_t reg_count = 3;// 构造写多个寄存器请求报文modbus_t write_multiple_reg_msg = {0};write_multiple_reg_msg.id = 0x0000;write_multiple_reg_msg.protocol = 0x0000;write_multiple_reg_msg.len = htons(7 + reg_count * 2); // 功能码 + 起始地址 + 寄存器数量 + 字节数 + 数据write_multiple_reg_msg.no = 0x01; // 从站地址write_multiple_reg_msg.type = 0x10; // 写多个寄存器功能码write_multiple_reg_msg.buf[0] = 0x00; // 起始寄存器地址高字节write_multiple_reg_msg.buf[1] = 0x00; // 起始寄存器地址低字节,从地址 0 开始write_multiple_reg_msg.buf[2] = 0x00; // 寄存器数量高字节write_multiple_reg_msg.buf[3] = reg_count & 0xFF; // 寄存器数量低字节write_multiple_reg_msg.buf[4] = reg_count * 2; // 字节数// 要写入的数据uint16_t values[] = {0x0012, 0x0034, 0x0056};for (int i = 0; i < reg_count; i++) {write_multiple_reg_msg.buf[5 + i * 2] = (values[i] >> 8) & 0xFF; // 高字节write_multiple_reg_msg.buf[5 + i * 2 + 1] = values[i] & 0xFF; // 低字节}// 发送写多个寄存器请求ssize_t sent_bytes = write(client, &write_multiple_reg_msg, sizeof(write_multiple_reg_msg));if (sent_bytes == -1) {perror("write multiple registers request failed");close(client);return 1;}printf("Write multiple registers request sent successfully.\n");// 接收响应uint8_t response[256];ssize_t recv_bytes = read(client, response, sizeof(response));if (recv_bytes == -1) {perror("read response failed");close(client);return 1;} else if (recv_bytes == 0) {printf("No response received.\n");} else {printf("Received %zd bytes response:\n", recv_bytes);for (int i = 0; i < recv_bytes; i++) {printf("%02x ", response[i]);}printf("\n");}close(client);return 0;
}
第一种:02.Modbus Slave
第二种:Wireshark【特别注意,绑定的服务器的ip地址是虚拟机中查询出来的ifconfig】
server.c代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;int main(int argc, const char *argv[])
{if(argc < 2){printf("请输入端口号\n");return 1;}short port = atoi(argv[1]);// atoi 函数,将字符串类型转换成整形// "123" -> 123// "123abc" -> 123// "12abc3" -> 12 // "abc123" -> 0int server = socket(AF_INET,SOCK_STREAM,0);struct sockaddr_in addr = {0};addr.sin_family = AF_INET; addr.sin_port = htons(port);addr.sin_addr.s_addr = inet_addr("0.0.0.0");if(bind(server,(struct sockaddr*)&addr,sizeof(addr)) == -1){perror("bind");return 1;}listen(server,10);struct sockaddr_in client_addr;int client_len = sizeof(client_addr);int client = accept(server,(struct sockaddr*)&client_addr,&client_len);// 这个 client 就是成功接受连接的客户端的套接字printf("有客户端连接\n");while(1){char buf[128] = "";int res = read(client,buf,128);// 一旦client客户端关闭,则read(client) 函数就会从阻塞变成非阻塞//int res = recv(client,buf,128,MSG_DONTWAIT);if(res == 0){// 无论如何,read、recv函数只要客户端断开连接,都会返回0printf("客户端断开连接\n");return 0;}printf("客户端发来消息:%s\n",buf);sleep(1);}return 0;
}
第三种:网络调试工具-飞机
特别注意:
小飞机给modbus传数据有问题: