花莲县网站建设_网站建设公司_Java_seo优化
2025/12/18 1:28:48 网站建设 项目流程

一、项目背景与设计目标

在典型的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_family=PF_INET; //IPV4协议 server_address.sin_port=htons(atoi(argv[1])); //端口号赋值 server_address.sin_addr.s_addr=INADDR_ANY; //本地IP地址 /*初始化信号量*/ sem_init(&lock,0,1); //信号量的值初始为 1,表示资源可用 //创建链表头 TCP_ClientInfoListHead=CreateListHead(TCP_ClientInfoListHead); /*1 .创建套接字*/ socketfd=socket(PF_INET,SOCK_STREAM,0); if(socketfd<0) { 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_len=sizeof(struct sockaddr); //计算结构体大小 20 //申请空间,线程并发执行不能使用变量地址传参数 clientfd=malloc(sizeof(int)); /*4. 等待客户端连接*/ if((*clientfd=accept(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 *p=head; //保存链表头 struct SocketTcpClient *tmp=NULL; //新链表节点 /*1. 找到链表结尾*/ while(p->next!=NULL) { p=p->next; } /*2. 申请新节点*/ tmp=malloc(sizeof(struct SocketTcpClient)); //申请新的节点空间 memcpy(tmp,&NewData,sizeof(struct SocketTcpClient)); //拷贝结构体数据 tmp->next=NULL; //结尾指向空 /*3. 添加新节点到链表结尾*/ p->next=tmp; }
  • 客户端断开连接时,从链表中删除

/* 函数功能: 根据文件描述符删除指定的链表节点 函数参数: head :链表头 clientfd:文件描述符 */ void DeleteListNode(struct SocketTcpClient *head,int clientfd) { struct SocketTcpClient *p=head; //保存链表头 struct SocketTcpClient *tmp=NULL; //保存链表地址节点 /*1. 查找要删除的链表节点*/ while(p->next!=NULL) { tmp=p; //保存上一个节点的地址 p=p->next; if(p->clientfd==clientfd) //查找到节点 { /*2. 删除节点*/ tmp->next=tmp->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!=clientfd&&p->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_fd=socket(AF_INET,SOCK_STREAM,0); if(tcp_client_fd<0) { printf("TCP服务器端套接字创建失败!\n"); return -1; } /*2. 连接到指定的服务器*/ tcp_server.sin_family=AF_INET; //IPV4协议类型 tcp_server.sin_port=htons(atoi(argv[2]));//端口号赋值,将本地字节序转为网络字节序 tcp_server.sin_addr.s_addr=inet_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_sec=1; timeout.tv_usec=100; /*监控是否有对应的事件发生*/ rx_len=select(tcp_client_fd+1,&readfds,NULL,NULL,&timeout); if(rx_len>0) //有数据 { rx_len=read(tcp_client_fd,&rx_data,sizeof(struct SocketRxTxData)); if(rx_len==sizeof(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_len==0)break; //客户端断开连接 } if(rx_len<0)break; //出现错误 if(rx_len==0) //没有事件产生,等待超时 { //向服务器发送数据 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->CheckSum=0; p->id[0]=id1; p->id[1]=id2; p->id[2]=id3; p->clientmode = 1;//标志为软件客户端 //赋值校验和 for(i=0;i<30;i++) { p->CheckSum+=p->Databuffer[i]; } } /* 函数功能:判断传输的数据是否正确 函数返回值: 0正确 其他值错误 */ int CheckPackageData(struct SocketRxTxData ClientRxTxData) { int i; unsigned int CheckSum=0; //存放校验和 /*1. 判断帧头是否正确*/ if(ClientRxTxData.FrameHead[0]!=0xA1|| ClientRxTxData.FrameHead[1]!=0xA2|| ClientRxTxData.FrameHead[2]!=0xA3|| ClientRxTxData.FrameHead[3]!=0xA4) { return -1; } /*2. 计算校验和*/ for(i=0;i<30;i++) { CheckSum+=ClientRxTxData.Databuffer[i]; } if(CheckSum!=ClientRxTxData.CheckSum) //校验失败 { return -1; } return 0; //数据校验成功 }

四、IOT服务端客户端源码附件

【免费】IOT物联网服务端和客户端搭建代码资源-CSDN下载https://download.csdn.net/download/qq_34885669/92470649

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询