2026/1/7 5:02:59
网站建设
项目流程
威海做网站,sae wordpress 媒体库,网络服务属于什么税目,傻瓜网站制作一、项目背景与设计目标 在典型的IOT物联网应用中#xff0c;嵌入式硬件设备#xff08;如 ESP8266 / ESP32#xff09;往往部署在内网或复杂网络环境中#xff0c;而控制端#xff08;PC / 手机 / 上位机#xff09;需要通过云端服务器与这些设备进行远程通信。
IOT物联…一、项目背景与设计目标在典型的IOT物联网应用中嵌入式硬件设备如 ESP8266 / ESP32往往部署在内网或复杂网络环境中而控制端PC / 手机 / 上位机需要通过云端服务器与这些设备进行远程通信。IOT物联网通常都是要实现以下功能支持多个硬件客户端同时在线支持软件控制端与指定硬件设备通信支持云端服务器对客户端进行统一管理与数据转发协议简单、可扩展、适合 MCU 侧实现二、通信协议设计为了降低 MCU 端解析复杂度协议采用定长二进制结构体并通过#pragma pack(1)保证字节对齐。1.定义存放客户端传输数据的结构体#pragma pack(1) //以下结构体以一个字节对齐 //定义存放客户端传输数据的结构体 struct SocketRxTxData { unsigned char FrameHead[4]; //存放帧头数据 固定为: 0xA1 0xA2 0xA3 0xA4 unsigned char Databuffer[30]; //存放传输的字符串数据 unsigned int id[3]; //存放96位ID号 unsigned char clientmode; //0表示硬件客户端 1表示软件控制客户端 unsigned int CheckSum; //存放数据位的校验和 };协议设计要点固定帧头用于快速丢弃非法数据96 位 ID唯一标识一组通信对象clientmode区分控制端与被控端简单累加校验和适合 MCU 实时计算二、IOT物联网服务端实现1. 服务器核心任务1监听指定TCP端口接受客户端连接。2为每个TCP客户端创建独立线程。3保存客户端的信息ID / fd / 类型。4根据协议规则转发数据。2. 多客户端并发连接架构实现服务器采用一个主线程 多个子线程 的架构一个主线程 负责TCP服务端的创建、监听和接收客户端。多个子线程 负责每一个客户端的数据收发处理。/*TCP服务器代码*/ int main(int argc,char *argv[]) { if(argc ! 2) { printf(传参格式./app 端口号\n); return -1; } //相关变量定义 pthread_t thread_id; //存放线程的标识符 struct sockaddr_in client_address; //存放客户端的信息 socklen_t address_len; //存放客户端结构体信息的长度 int socketfd; //保存TCP服务器的网络套接字 int *clientfd; //保存TCP客户端的网络套接字 struct sockaddr_in server_address; //存放服务器的IP地址信息 memset(server_address,0,sizeof(struct sockaddr_in)); //初始化内存空间 memset(client_address,0,sizeof(struct sockaddr_in)); //初始化内存空间 server_address.sin_familyPF_INET; //IPV4协议 server_address.sin_porthtons(atoi(argv[1])); //端口号赋值 server_address.sin_addr.s_addrINADDR_ANY; //本地IP地址 /*初始化信号量*/ sem_init(lock,0,1); //信号量的值初始为 1表示资源可用 //创建链表头 TCP_ClientInfoListHeadCreateListHead(TCP_ClientInfoListHead); /*1 .创建套接字*/ socketfdsocket(PF_INET,SOCK_STREAM,0); if(socketfd0) { printf(服务器网络套接字创建失败!\n); return -1; } int on 1; setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, on, sizeof(on)); /*2. 绑定端口创建服务器*/ if(bind(socketfd,(const struct sockaddr *)server_address,sizeof(struct sockaddr))!0) { printf(服务器绑定端口失败!\n); return -1; } /*3. 设监听的端口数量*/ if(listen(socketfd,100)!0) { printf(服务器端口监听失败!\n); return -1; } int i; while(1) { address_lensizeof(struct sockaddr); //计算结构体大小 20 //申请空间,线程并发执行不能使用变量地址传参数 clientfdmalloc(sizeof(int)); /*4. 等待客户端连接*/ if((*clientfdaccept(socketfd,(struct sockaddr *)client_address,address_len))0) { printf(等待客户端连接失败!\n); break; } /*打印一些客户端的信息*/ printf(成功连接的客户端端口号:%d\n,ntohs(client_address.sin_port)); printf(成功连接的客户端IP地址:%s\n,inet_ntoa(client_address.sin_addr)); //创建线程 if(pthread_create(thread_id,NULL,pthread_func,(void*)clientfd)!0) { printf(线程_%d_创建失败!\n,i); } pthread_detach(thread_id); //设置分离属性自己回收资源 } sem_destroy(lock); //注销信号量 return 0; }3. 客户端信息管理链表服务器通过单向链表维护所有在线客户端/*------------------------------------------------------------------------------*/ #pragma pack(1) //以下结构体以一个字节对齐 //定义存放客户端信息的结构体 struct SocketTcpClient { unsigned int id[3]; //存放96位ID号 int clientfd; //存放客户端文件描述符 unsigned char clientmode; //存放客户端区分标志 0表示硬件客户端 1表示软件客户端 struct SocketTcpClient *next; //定义存放下一个地址的成员 };客户端首次发送合法数据包后加入链表/* 函数功能:链表结尾添加新的节点 函数参数: head:链表头 NewData:要添加进去的结构体数据 */ void AddNewListNode(struct SocketTcpClient *head,struct SocketTcpClient NewData) { struct SocketTcpClient *phead; //保存链表头 struct SocketTcpClient *tmpNULL; //新链表节点 /*1. 找到链表结尾*/ while(p-next!NULL) { pp-next; } /*2. 申请新节点*/ tmpmalloc(sizeof(struct SocketTcpClient)); //申请新的节点空间 memcpy(tmp,NewData,sizeof(struct SocketTcpClient)); //拷贝结构体数据 tmp-nextNULL; //结尾指向空 /*3. 添加新节点到链表结尾*/ p-nexttmp; }客户端断开连接时从链表中删除/* 函数功能: 根据文件描述符删除指定的链表节点 函数参数: head :链表头 clientfd:文件描述符 */ void DeleteListNode(struct SocketTcpClient *head,int clientfd) { struct SocketTcpClient *phead; //保存链表头 struct SocketTcpClient *tmpNULL; //保存链表地址节点 /*1. 查找要删除的链表节点*/ while(p-next!NULL) { tmpp; //保存上一个节点的地址 pp-next; if(p-clientfdclientfd) //查找到节点 { /*2. 删除节点*/ tmp-nexttmp-next-next; //连接节点 free(p); //释放节点空间 break; } } }4. 数据转发逻辑核心服务器并不关心数据内容仅根据规则转发ID相同的两个客户端但TCP套接字不一样。这使得一个软件客户端可以控制指定硬件设备多组设备之间互不干扰服务器逻辑高度通用/*---------------------------------------遍历链表向符合条件客户端发送数据-----------------------------*/ int SendDataToClient(struct SocketRxTxData data, int clientfd) { int cnt 0; struct SocketTcpClient *p TCP_ClientInfoListHead; while(p ! NULL) { if(p-id[0]data.id[0]p-id[1]data.id[1]p-id[2]data.id[2]p-clientfd!clientfdp-clientmode!data.clientmode) //查找到节点 { //存在就转发数据 write(p-clientfd,(void*)data,sizeof(struct SocketRxTxData)); cnt; } p p-next; } return cnt; }三、IOT物联网客户端实现1. 客户端核心任务TCP 客户端主要用于1连接云端服务器2定期发送数据帧3接收并解析服务器转发的数据2. 客户端实现简单来说就是创建TCP客户端→连接TCP服务端→收发数据→校验数据→解析数据/* TCP客户端创建 */ int main(int argc,char **argv) { struct SocketRxTxData RxTxData; int tcp_client_fd; //客户端套接字描述符 int Server_Port; //服务器端口号 struct sockaddr_in tcp_server; //存放服务器的IP地址信息 int rx_len; struct SocketRxTxData socket_data; struct SocketRxTxData rx_data; fd_set readfds; struct timeval timeout; if(argc!7) { printf(TCP客户端形参格式:./tcp_client 服务器IP地址 端口号 stringData id1 id2 id3\n); return -1; } /*1. 创建网络套接字*/ tcp_client_fdsocket(AF_INET,SOCK_STREAM,0); if(tcp_client_fd0) { printf(TCP服务器端套接字创建失败!\n); return -1; } /*2. 连接到指定的服务器*/ tcp_server.sin_familyAF_INET; //IPV4协议类型 tcp_server.sin_porthtons(atoi(argv[2]));//端口号赋值,将本地字节序转为网络字节序 tcp_server.sin_addr.s_addrinet_addr(argv[1]); //IP地址赋值 if(connect(tcp_client_fd,(const struct sockaddr*)tcp_server,sizeof(const struct sockaddr))0) { printf(TCP客户端: 连接服务器失败!\n); return -1; } //封装结构体 SetPackageData(socket_data,argv[3],atoi(argv[4]),atoi(argv[5]),atoi(argv[6])); while(1) { FD_ZERO(readfds); //清除文件描述符集合 FD_SET(tcp_client_fd,readfds); //设置监听的文件描述符 timeout.tv_sec1; timeout.tv_usec100; /*监控是否有对应的事件发生*/ rx_lenselect(tcp_client_fd1,readfds,NULL,NULL,timeout); if(rx_len0) //有数据 { rx_lenread(tcp_client_fd,rx_data,sizeof(struct SocketRxTxData)); if(rx_lensizeof(struct SocketRxTxData)) { if(CheckPackageData(rx_data)0) { printf(rx%s,%d,%d,%d\n,rx_data.Databuffer,rx_data.id[0],rx_data.id[1],rx_data.id[2]); } } if(rx_len0)break; //客户端断开连接 } if(rx_len0)break; //出现错误 if(rx_len0) //没有事件产生,等待超时 { //向服务器发送数据 write(tcp_client_fd,socket_data,sizeof(struct SocketRxTxData)); } } /*4. 关闭连接*/ close(tcp_client_fd); }数据封包和校验/* 函数功能: 数据封包 */ void SetPackageData(struct SocketRxTxData *p,char *str,int id1,int id2,int id3) { int i; p-FrameHead[0]0xA1; p-FrameHead[1]0xA2; p-FrameHead[2]0xA3; p-FrameHead[3]0xA4; memcpy(p-Databuffer,str,30); p-CheckSum0; p-id[0]id1; p-id[1]id2; p-id[2]id3; p-clientmode 1;//标志为软件客户端 //赋值校验和 for(i0;i30;i) { p-CheckSump-Databuffer[i]; } } /* 函数功能:判断传输的数据是否正确 函数返回值: 0正确 其他值错误 */ int CheckPackageData(struct SocketRxTxData ClientRxTxData) { int i; unsigned int CheckSum0; //存放校验和 /*1. 判断帧头是否正确*/ if(ClientRxTxData.FrameHead[0]!0xA1|| ClientRxTxData.FrameHead[1]!0xA2|| ClientRxTxData.FrameHead[2]!0xA3|| ClientRxTxData.FrameHead[3]!0xA4) { return -1; } /*2. 计算校验和*/ for(i0;i30;i) { CheckSumClientRxTxData.Databuffer[i]; } if(CheckSum!ClientRxTxData.CheckSum) //校验失败 { return -1; } return 0; //数据校验成功 }四、IOT服务端客户端源码附件【免费】IOT物联网服务端和客户端搭建代码资源-CSDN下载https://download.csdn.net/download/qq_34885669/92470649