项目开发一:基于WebServer 的工业数据采集项目

06-01 1203阅读

A.项目文档及运行流程解析(用于了解项目流程/答辩)

一.modbus.c(重点部分)

#include 
#include 
#include 
#include 
#include 
#include "modbus.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
struct msgbuf
{
    long mtype; // 第一个成员必须是long类型变量,表示消息的类型
    int num1;
    int num2;
};
// 全局变量,用于在线程之间共享数据
modbus_t *ctx;
// 数据采集线程函数
void *read_data(void *arg)
{
    // 创建key值
    key_t key;
    key = ftok("./main.c", 'a');
    if (key  
   
  

(1)结构体定义解析

c

struct msgbuf {
    long mtype;  // 消息类型,必须作为第一个成员,用于消息队列的消息筛选
    int num1;    // 存储命令参数1(如控制类型:开灯/关灯等)
    int num2;    // 存储命令参数2(如开关状态:0关/1开)
};

  • 用于消息队列通信,定义了消息的格式,mtype 让接收方可以根据类型筛选消息,num1 和 num2 传递具体控制命令。

    (2)全局变量解析

    modbus_t *ctx;  // Modbus通信上下文句柄,贯穿整个程序,用于保持Modbus连接状态
    

    • 作为全局变量,在多个函数(线程)中共享,避免重复创建 Modbus 连接。

      (3)read_data 线程函数解析(数据采集)

      void *read_data(void *arg) {
          // 1. 共享内存创建与映射
          key_t key = ftok("./main.c", 'a');  // 生成共享内存的key值,用于标识共享内存
          int shmid;
          // 创建共享内存,若已存在则重新获取
          shmid = shmget(key, 64, IPC_CREAT | IPC_EXCL | 0777);
          if (shmid  
         
        

      • 功能:通过 Modbus 读取设备数据,存入共享内存,供其他模块(如 WebServer)读取。
      • 关键操作:
        • ftok + shmget:创建或获取共享内存,实现跨进程数据共享。
        • modbus_read_registers:核心 Modbus 读操作,获取设备传感器数据。

          (4)write_command 线程函数解析(命令处理)

          void *write_command(void *arg) {
              // 1. 消息队列创建与获取
              key_t key = ftok("./main.c", 'a');
              int msgid = msgget(key, IPC_CREAT | IPC_EXCL | 0666);
              if (msgid  
             
            

          • 功能:通过消息队列接收命令,解析后控制 Modbus 设备(如 LED、蜂鸣器)。
          • 关键操作:

            msgget + msgrcv:创建消息队列并接收命令消息。

            modbus_write_bit:核心 Modbus 写操作,向设备写入控制指令。

            (5)main 函数解析

            int main(int argc, char const *argv[]) {
                // 1. Modbus初始化创建实例(
                ctx = modbus_new_tcp("192.168.50.82", 502);  // 创建Modbus TCP连接
                if (ctx == NULL) { perror("modbus_new_tcp err"); return -1; }
                int slave_id = 1;
                modbus_set_slave(ctx, slave_id);  // 设置Modbus从机ID
                if (modbus_connect(ctx) == -1) {  // 建立连接
                    perror("connect err");
                    modbus_free(ctx);
                    return -1;
                }
                // 2. 线程创建
                pthread_t data_thread, command_thread;
                if (pthread_create(&data_thread, NULL, read_data, NULL) != 0) {  // 创建数据采集线程
                    perror("create data err");
                    modbus_close(ctx); 
                    modbus_free(ctx);
                    return -1;
                }
                if (pthread_create(&command_thread, NULL, write_command, NULL) != 0) {  // 创建命令处理线程
                    perror("create command err");
                    modbus_close(ctx); modbus_free(ctx);
                    return -1;
                }
                // 3. 等待线程结束
                pthread_join(data_thread, NULL);
                pthread_join(command_thread, NULL);
                // 4. 资源释放
                modbus_close(ctx);  // 关闭Modbus连接
                modbus_free(ctx);   // 释放Modbus上下文
                return 0;
            }
            

            • 功能:初始化 Modbus 通信,创建数据采集和命令处理线程,协调程序运行与资源释放。
            • 关键流程:
              • Modbus Tcp :Modbus 连接初始化:modbus_new_tcp → modbus_set_slave → modbus_connect。
              • 线程管理:pthread_create 创建线程,pthread_join 等待线程结束。
              • 资源清理:确保 Modbus 连接关闭和上下文释放,避免资源泄漏。

                (6)相关扩展

                一、代码模块与原理图组件的映射关系

                1. read_data 线程(数据采集)

                代码功能:

                通过 modbus_read_registers 读取 Modbus 设备寄存器数据(光线、加速度 XYZ)。

                利用共享内存(shmget、shmat)存储采集数据,实现数据共享。

                与原理图关联:

                对应原理图中 “Modbus 采集控制程序” 与 “Modbus 设备” 的交互。该线程模拟了采集程序主动读取 Modbus 设备数据的过程,采集后的数据通过共享内存存储,为 WebServer 读取(如网页展示数据)提供基础,契合原理图中 “采集控制程序采集 Modbus 设备数据” 的逻辑。

                2.write_command 线程(命令处理)

                代码功能:

                通过消息队列(msgget、msgrcv)接收命令数据。根据命令值(如开灯、关灯等),使用 modbus_write_bit 向 Modbus 设备写入控制指令。

                与原理图关联:

                对应原理图中 “Modbus 采集控制程序” 处理外部命令的功能。消息队列作为进程间通信工具,可接收来自WebServer 通过进程间通信,最终通过 Modbus/TCP 控制 Modbus 设备,实现 “命令输入 → 采集控制程序处理 → Modbus 设备执行” 的流程。

                3.main 函数(整体协调)

                代码功能:

                ModbusTcp实例化, modbus_new_tcp,modbus_set_slave,modbus_connect,建立与 Modbus 设备的通信链路。

                创建数据采集线程和命令处理线程,协调两者运行。

                与原理图关联:

                作为程序入口,统筹 “Modbus 采集控制程序” 的核心功能,确保数据采集和命令处理两个关键流程并行执行,完整实现原理图中 “Modbus 采集控制程序” 的核心职责。

                二、关键技术与原理图的交互逻辑

                1. 共享内存(数据共享)
                  1. 代码实现:通过 shmget 创建共享内存,shmat 映射内存空间,将采集的传感器数据写入共享内存(sprintf(p, "%d#%d #%d #%d\n", reg[0], reg[1], reg[2], reg[3]))。
                  2. 与原理图关联:若将原理图中 “WebServer” 与 “Modbus 采集控制程序” 结合,共享内存可作为两者的通信桥梁。例如,WebServer 读取共享内存中的数据,转发给网页展示,实现 “Modbus 设备 → 采集程序(共享内存) → WebServer → 网页” 的数据展示链路。
                2. 消息队列(命令传递)
                  1. 代码实现:通过 msgget 创建消息队列,msgrcv 接收消息类型为 1 的命令数据,解析后控制 Modbus 设备。
                  2. 与原理图关联:对应原理图中 “进程间通信” 机制。假设 WebServer 接收网页的控制命令(如用户点击网页按钮),可通过消息队列将命令传递给 “Modbus 采集控制程序”,最终实现 “网页 → WebServer → 消息队列 → 采集程序 → Modbus 设备” 的命令控制链路。
                3. Modbus 通信(核心交互)
                  1. 代码实现:使用 Modbus 库函数(modbus_read_registers 读数据、modbus_write_bit 写指令)与 Modbus 设备交互。
                  2. 与原理图关联:直接对应原理图中 “Modbus 采集控制程序” 通过 Modbus/TCP 与 “Modbus 设备” 的通信,是整个系统数据采集和设备控制的核心交互层。

                三、整体流程与原理图的匹配

                1. 数据采集流程:

                代码中 read_data 线程循环读取 Modbus 设备数据 → 存入共享内存,对应原理图中 “Modbus 设备 → Modbus 采集控制程序” 的数据采集路径,为上层应用(如 WebServer 驱动的网页)提供数据来源。

                1. 命令控制流程:

                消息队列接收命令 → write_command 线程解析命令 → 通过 Modbus 写入设备,对应原理图中 “外部命令(如网页触发) → 进程间通信 → Modbus 采集控制程序 → Modbus 设备” 的控制路径,实现对 Modbus 设备的远程操作。

                二.thttpd.c(最重点)

                #include "thttpd.h"
                #include "custom_handle.h"
                #include 
                #include 
                #include 
                int init_server(int _port) //创建监听套接字
                {
                	int sock=socket(AF_INET,SOCK_STREAM,0);
                	if(sock
                		perror("socket failed");
                		exit(2);
                	}
                	//设置地址重用
                	int opt=1;                     
                	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
                	struct sockaddr_in local;
                	local.sin_family=AF_INET;
                	local.sin_port=htons(_port);
                	local.sin_addr.s_addr=INADDR_ANY;
                	
                	if(bind(sock,(struct sockaddr*)&local,sizeof(local))
                		perror("bind failed");
                		exit(3);
                	}
                	if(listen(sock,5)
                		perror("listen failed");
                		exit(4);
                	}
                	return sock;
                }
                static int get_line(int sock,char* buf)   //按行读取请求报头
                {
                	char ch='\0';
                	int i=0;
                	ssize_t ret=0;
                	while(i
                		ret=recv(sock,&ch,1,0);
                		if(ret0&&ch=='\r')
                		{
                			ssize_t s=recv(sock,&ch,1,MSG_PEEK);
                			if(s0&&ch=='\n')
                			{
                				recv(sock,&ch,1,0);
                			}
                			else
                			{
                				ch='\n';
                			}
                		}
                		buf[i++]=ch;
                	}
                	buf[i]='\0';
                	return i;
                }
                static void clear_header(int sock)    //清空消息报头
                {
                	char buf[SIZE];
                	int ret=0;
                	do
                	{
                		ret=get_line(sock,buf);
                	}while(ret!=1&&(strcmp(buf,"\n")!=0));
                }
                static void show_404(int sock)      //404错误处理
                {
                	clear_header(sock);
                	char* msg="HTTP/1.0 404	Not Found\r\n";
                	send(sock,msg,strlen(msg),0);         //发送状态行
                	send(sock,"\r\n",strlen("\r\n"),0);      //发送空行
                	struct stat st;
                	stat("wwwroot/404.html",&st);//获取网页属性
                	int fd=open("wwwroot/404.html",O_RDONLY);
                	sendfile(sock,fd,NULL,st.st_size);//发送文件;st.st_size:文件的大小
                	close(fd);
                }
                void echo_error(int sock,int err_code)    //错误处理,只有404能进行错误处理
                {
                	switch(err_code)
                	{
                	case 403:
                		break;
                	case 404:
                		show_404(sock);
                		break;
                	case 405:
                		break;
                	case 500:
                		break;
                	defaut:
                		break;
                	}
                }
                static int echo_www(int sock,const char * path,size_t s)  //处理非CGI的请求
                {
                	int fd=open(path,O_RDONLY);
                	if(fd
                		echo_error(sock,403);
                		return 7;
                	}
                	char* msg="HTTP/1.0 200 OK\r\n";
                	send(sock,msg,strlen(msg),0);         //发送状态行
                	send(sock,"\r\n",strlen("\r\n"),0);      //发送空行
                	
                	//sendfile方法可以直接把文件发送到网络对端
                	if(sendfile(sock,fd,NULL,s)
                		echo_error(sock,500);
                		return 8;	
                	}
                	close(fd);
                	return 0;
                }
                static int handle_request(int sock,const char* method,
                		const char* path,const char* query_string)
                {
                	char line[SIZE];
                	int ret=0;
                	int content_len=-1;
                	if(strcasecmp(method,"GET")==0)
                	{
                		//清空消息报头
                		clear_header(sock);
                	}
                	else
                	{
                		//获取post方法的参数大小
                		do
                		{
                			ret=get_line(sock,line);
                			if(strncasecmp(line,"content-length",14)==0)  //post的消息体记录正文长度的字段
                			{
                				content_len=atoi(line+16);	//求出正文的长度
                			}
                		}while(ret!=1&&(strcmp(line,"\n")!=0));
                	}
                	printf("method = %s\n", method);
                	printf("query_string = %s\n", query_string);
                	printf("content_len = %d\n", content_len);
                	char req_buf[4096] = {0};
                	//如果是POST方法,那么肯定携带请求数据,那么需要把数据解析出来
                	if(strcasecmp(method,"POST")==0)
                	{
                		int len = recv(sock, req_buf, content_len, 0);
                		printf("len = %d\n", len);
                		printf("req_buf = %s\n", req_buf);
                	}
                	//先发送状态码
                	char* msg="HTTP/1.1 200 OK\r\n\r\n";
                	send(sock,msg,strlen(msg),0);
                	//请求交给自定义代码来处理,这是业务逻辑
                	parse_and_process(sock, query_string, req_buf);
                	return 0;
                }
                int handler_msg(int sock)       //浏览器请求处理函数
                {
                	char del_buf[SIZE] = {};
                	//通常recv()函数的最后一个参数为0,代表从缓冲区取走数据
                	//而当为MSG_PEEK时代表只是查看数据,而不取走数据。
                	recv(sock,del_buf,SIZE,MSG_PEEK);//MSG_PEEK表示只是吧客户端发过来的数据看一下,recv接收完以后仍在缓存区
                #if 1 //初学者强烈建议打开这个开关,看看tcp实际请求的协议格式
                	puts("---------------------------------------");
                	printf("recv:%s\n",del_buf);
                	puts("---------------------------------------");
                #endif
                //接下来method方法判断之前的代码,可以不用重点关注
                	//知道是处理字符串,把需要的信息过滤出来即可
                	char buf[SIZE];
                	int count=get_line(sock,buf);
                	int ret=0;
                	char method[32];//存放请求方法
                	char url[SIZE];//存放URL
                	char *query_string=NULL;
                	int i=0;
                	int j=0;
                	int need_handle=0;//判断是否需要处理
                	//获取请求方法和请求路径(liaojie)
                	while(j
                		if(isspace(buf[j]))
                		{
                			break;
                		}
                		method[i]=buf[j];	
                		i++;
                		j++;
                	}
                	method[i]='\0';
                	while(isspace(buf[j])&&j
                		j++;
                	}
                	//这里开始就开始判断发过来的请求是GET还是POST了
                	if(strcasecmp(method,"POST")&&strcasecmp(method,"GET"))//strcasecmp可以忽略大小写的区别,其余和strcmp一样
                	{
                		printf("method failed\n");  //如果都不是,那么提示一下
                		echo_error(sock,405);//405是状态码,表示方法不对
                		ret=5;
                		goto end;//掉转到end,运行end下面的函数
                	}
                	if(strcasecmp(method,"POST")==0)
                	{
                		need_handle=1;
                	}	
                	
                	i=0;
                	while(j
                		if(isspace(buf[j]))
                		{
                			break;
                		}
                		if(buf[j]=='?')
                		{
                			//将资源路径(和附带数据,如果有的话)保存在url中,并且query_string指向附带数据
                			query_string=&url[i];
                			query_string++;
                			url[i]='\0';
                		}
                		else{
                			url[i]=buf[j];
                		}
                		j++;
                		i++;
                	}
                	url[i]='\0';
                	printf("query_string = %s\n", query_string);
                	//浏览器通过http://192.168.8.208:8080/?test=1234这种形式请求
                	//是携带参数的意思,那么就需要额外处理了
                	if(strcasecmp(method,"GET")==0&&query_string!=NULL)//前面的函数已经将指针query_string定位到?,如果问号不等于空,说明有参数,需要进一步处理
                	{
                		need_handle=1;
                	}
                	//我们把请求资源的路径固定为wwwroot/下的资源,这个自己可以改
                	char path[SIZE];
                	sprintf(path,"wwwroot%s",url);     //wwwroot/404.html,寻找wwwroot里的404.html  
                	
                	printf("path = %s\n", path);
                	
                	//如果请求地址没有携带任何资源,那么默认返回index.html
                	if(path[strlen(path)-1]=='/')              //判断浏览器请求的是不是目录
                	{
                		strcat(path,"index.html");	     //如果请求的是目录,则就把该目录下的首页返回回去,strcat是拼接函数
                	}
                	//如果请求的资源不存在,就要返回传说中的404页面了
                	struct stat st;   
                	if(stat(path,&st)
                		printf("can't find file\n");
                		echo_error(sock,404);
                		ret=6;
                		goto end;
                		}
                	//到这里基本就能确定是否需要自己的程序来处理后续请求了
                	printf("need progress handle:%d\n",need_handle);
                	//如果是POST请求或者带参数的GET请求,就需要我们自己来处理了
                	//这些是业务逻辑,所以需要我们自己写代码来决定怎么处理
                	if(need_handle)
                	{
                		ret=handle_request(sock,method,path,query_string);
                	}
                	else
                	{
                		clear_header(sock);
                		//如果是GETt,而且没有参数,则直接返回资源	
                		ret=echo_www(sock,path,st.st_size);  
                	}
                	
                end:
                	close(sock);
                	return ret;
                }
                
                    int sock = socket(AF_INET, SOCK_STREAM, 0);
                    if (sock  0)且下一个字符是换行符 '\n',则再次调用 recv 函数(不带 MSG_PEEK 标志)将该字符从缓冲区中读取出来。
              • else:如果预读的字符不是 '\n',则将 ch 设为 '\n',以确保后续处理能够正确识别换行。
              • 注释:MSG_PEEK 的主要功能是 “预读” 套接字接收缓冲区中的数据。当使用 recv 函数并带上 MSG_PEEK 标志时,它会从接收缓冲区中读取数据,但并不会将这些数据从缓冲区中移除。这意味着后续再次调用 recv 函数(不带 MSG_PEEK 标志 )时,仍然可以读取到这些数据 。

                5.存储读取的字符

                buf[i++] = ch;
                

                将当前读取的字符 ch 存储到 buf 数组中,并将索引 i 加 1。

                6.结束字符串并返回结果

                buf[i] = '\0';
                return i;
                

                • buf[i] = '\0':在 buf 数组的末尾添加字符串结束符 '\0',将其转换为一个以空字符结尾的 C 字符串。
                • return i:返回本次读取的字符数量,不包括字符串结束符。

                  (3)clear_header 函数:清空消息报头

                  static void clear_header(int sock)    //清空消息报头
                  {
                      char buf[SIZE];
                      int ret = 0;
                      do
                      {
                          ret = get_line(sock, buf);
                      } while (ret != 1 && (strcmp(buf, "\n") != 0));
                  }
                  

                  • 循环读取报头:使用 get_line() 函数循环读取请求报头,直到读取到空行(即 \n)或者读取的字符数为 1。
                    (4)show_404 函数:404 错误处理

                    show_404 函数的主要功能是处理客户端请求资源不存在的情况,向客户端发送一个 HTTP 404 错误响应,并将 wwwroot/404.html 文件的内容作为错误页面返回给客户端。整个过程遵循 HTTP 协议的规范,包括发送状态行、空行和文件内容。

                    static void show_404(int sock)      //404错误处理
                    {
                        clear_header(sock);
                        char *msg = "HTTP/1.0 404 Not Found\r\n";
                        send(sock, msg, strlen(msg), 0);         //发送状态行
                        send(sock, "\r\n", strlen("\r\n"), 0);      //发送空行
                        struct stat st;
                        stat("wwwroot/404.html", &st); //获取网页属性
                        int fd = open("wwwroot/404.html", O_RDONLY);
                        sendfile(sock, fd, NULL, st.st_size); //发送文件;st.st_size:文件的大小
                        close(fd);
                    }
                    

                    • 清空报头:调用 clear_header() 函数清空请求报头。
                    • 发送状态行和空行:使用 send() 函数发送 HTTP 状态行 HTTP/1.0 404 Not Found 和空行。
                    • 获取文件属性:使用 stat() 函数获取 wwwroot/404.html 文件的属性,包括文件大小。
                    • 发送文件内容:使用 open() 函数打开文件,使用 sendfile() 函数将文件内容直接发送到套接字,最后关闭文件描述符。

                      精解析:

                      (1)函数定义与参数

                      static void show_404(int sock)
                      

                      • static:表示该函数是一个静态函数,其作用域仅限于当前源文件,其他文件无法调用此函数。
                      • void:表明该函数没有返回值。
                      • int sock:是一个套接字描述符,代表与客户端建立的网络连接,函数将通过这个套接字向客户端发送 404 错误响应。

                        (2)清除请求头

                        clear_header(sock);
                        

                        调用 clear_header 函数作用:清除客户端发送的请求头信息。在发送响应之前,通常需要先处理并清除这些请求头,以确保后续的响应能够正确发送。

                        (3)发送状态行

                        char *msg = "HTTP/1.0 404 Not Found\r\n";
                        send(sock, msg, strlen(msg), 0);
                        

                        • char *msg = "HTTP/1.0 404 Not Found\r\n";:定义一个指向字符串的指针 msg,该字符串是 HTTP 响应的状态行,表示当前响应遵循 HTTP 1.0 协议(非持续连接),请求的资源未找到(状态码 404)。
                        • send(sock, msg, strlen(msg), 0);:调用 send 函数将状态行信息通过套接字 sock 发送给客户端。strlen(msg) 用于获取状态行字符串的长度,最后一个参数 0 表示使用默认的发送标志。

                          (4)发送空行

                          send(sock, "\r\n", strlen("\r\n"), 0);
                          

                          在 HTTP 协议中,状态行和响应头之后需要有一个空行来分隔请求头和请求体。这里调用 send 函数发送一个空行(\r\n)给客户端。

                          (5)获取 404 页面文件属性

                          struct stat st;
                          stat("wwwroot/404.html", &st);
                          

                          • struct stat st;:定义一个 stat 结构体变量 st,该结构体用于存储文件的各种属性信息,如文件大小、文件权限等。
                          • stat("wwwroot/404.html", &st);:调用 stat 函数获取 wwwroot/404.html 文件的属性信息,并将结果存储在 st 结构体中。

                            (6)打开 404 页面文件

                            int fd = open("wwwroot/404.html", O_RDONLY);
                            

                            wwwroot:通常是网站项目中存放网页资源的目录名称

                            调用 open 函数以只读模式(O_RDONLY)打开 wwwroot/404.html 文件,并返回一个文件描述符 fd,后续将使用该文件描述符读取文件内容。

                            (7)发送 404 页面文件内容

                            sendfile(sock, fd, NULL, st.st_size);
                            

                            调用 sendfile 函数将 wwwroot/404.html 文件的内容直接发送给客户端。sendfile 函数是一种高效的文件传输方式,它可以避免在用户空间和内核空间之间进行数据拷贝,从而提高传输效率。

                            • sock:目标套接字描述符,即要将文件内容发送到的客户端连接。
                            • fd:源文件描述符,即要发送的 404 页面文件的描述符。
                            • NULL:偏移量参数,这里设置为 NULL 表示从文件的开头开始发送。
                            • st.st_size:要发送的文件大小,通过之前调用 stat 函数获取。

                              (8)关闭文件描述符

                              close(fd);
                              

                              调用 close 函数关闭之前打开的 wwwroot/404.html 文件的文件描述符,释放系统资源。

                              (5)echo_error 函数:错误处理

                              void echo_error(int sock, int err_code)    //错误处理,只有404能进行错误处理
                              {
                                  switch (err_code)
                                  {
                                  case 403:
                                      break;
                                  case 404:
                                      show_404(sock);
                                      break;
                                  case 405:
                                      break;
                                  case 500:
                                      break;
                                  default:
                                      break;
                                  }
                              }
                              

                              • 根据错误码处理错误:使用 switch 语句根据错误码进行相应的处理,目前仅处理 404 错误,调用 show_404() 函数返回 404 错误页面。
                                (6)echo_www 函数:处理非 CGI 的请求(直接将客户端请求的文件内容发送给客户端)

                                echo_www 函数的主要功能是处理非 CGI的请求,即直接将客户端请求的文件内容发送给客户端。

                                整个过程遵循 HTTP 协议的规范,包括发送状态行、空行和文件内容。如果在打开文件或发送文件内容时出现错误,会向客户端发送相应的错误响应。

                                static int echo_www(int sock, const char *path, size_t s)  //处理非CGI的请求
                                {
                                    int fd = open(path, O_RDONLY);
                                    if (fd  
                                   
                                  

                                • 打开文件:使用 open() 函数以只读模式打开请求的文件。
                                • 发送状态行和空行:使用 send() 函数发送 HTTP 状态行 HTTP/1.0 200 OK 和空行。
                                • 发送文件内容:使用 sendfile() 函数将文件内容直接发送到套接字,如果发送失败,调用 echo_error() 函数返回 500 错误。
                                • 关闭文件描述符:最后关闭文件描述符。

                                  精解析:

                                  (1)打开请求的文件

                                  int fd = open(path, O_RDONLY);
                                  if (fd  
                                     
                                    

                                  • open(path, O_RDONLY):调用 open 函数以只读模式(O_RDONLY)打开由 path 指定的文件。如果文件成功打开,open 函数将返回一个非负的文件描述符;如果打开失败,将返回 -1。
                                  • if (fd (2)发送 HTTP 状态行和空行

                                    char *msg = "HTTP/1.0 200 OK\r\n";//状态行
                                    send(sock, msg, strlen(msg), 0);
                                    send(sock, "\r\n", strlen("\r\n"), 0);
                                    

                                    • char *msg = "HTTP/1.0 200 OK\r\n";:定义一个指向字符串的指针 msg,该字符串是 HTTP 响应的状态行,表示当前响应遵循 HTTP 1.0 协议,请求成功处理(状态码 200)。
                                    • send(sock, msg, strlen(msg), 0):调用 send 函数将状态行信息通过套接字 sock 发送给客户端。strlen(msg) 用于获取状态行字符串的长度,最后一个参数 0 表示使用默认的发送标志。
                                    • send(sock, "\r\n", strlen("\r\n"), 0):在 HTTP 协议中,状态行和响应头之后需要有一个空行来分隔请求头和请求体。这里调用 send 函数发送一个空行(\r\n)给客户端。

                                      (3)发送文件内容

                                      if (sendfile(sock, fd, NULL, s)  
                                         
                                        

                                      • sendfile(sock, fd, NULL, s):调用 sendfile 函数将文件内容直接从文件描述符 fd 发送到套接字 sock。sendfile 函数是一种高效的文件传输方式,它可以避免在用户空间和内核空间之间进行数据拷贝,从而提高传输效率。NULL 表示从文件的开头开始发送,s 表示要发送的文件大小。
                                      • if (sendfile(sock, fd, NULL, s) (4)关闭文件描述符

                                        close(fd);
                                        return 0;
                                        

                                        • close(fd):调用 close 函数关闭之前打开的文件描述符,释放系统资源。
                                        • return 0:如果函数执行成功,返回 0 表示正常结束。

                                          (6)handle_request 函数:处理请求

                                          static int handle_request(int sock, const char *method,
                                                                    const char *path, const char *query_string)
                                          {
                                              char line[SIZE];
                                              int ret = 0;
                                              int content_len = -1;
                                              if (strcasecmp(method, "GET") == 0)
                                              {
                                                  //清空消息报头
                                                  clear_header(sock);
                                              }
                                              else
                                              {
                                                  //获取post方法的参数大小
                                                  do
                                                  {
                                                      ret = get_line(sock, line);
                                                      if (strncasecmp(line, "content-length", 14) == 0)  //post的消息体记录正文长度的字段
                                                      {
                                                          content_len = atoi(line + 16);    //求出正文的长度
                                                      }
                                                  } while (ret != 1 && (strcmp(line, "\n") != 0));
                                              }
                                              printf("method = %s\n", method);
                                              printf("query_string = %s\n", query_string);
                                              printf("content_len = %d\n", content_len);
                                              char req_buf[4096] = {0};
                                              //如果是POST方法,那么肯定携带请求数据,那么需要把数据解析出来
                                              if (strcasecmp(method, "POST") == 0)
                                              {
                                                  int len = recv(sock, req_buf, content_len, 0);
                                                  printf("len = %d\n", len);
                                                  printf("req_buf = %s\n", req_buf);
                                              }
                                              //先发送状态码
                                              char *msg = "HTTP/1.1 200 OK\r\n\r\n";
                                              send(sock, msg, strlen(msg), 0);
                                              //请求交给自定义代码来处理,这是业务逻辑
                                              parse_and_process(sock, query_string, req_buf);
                                              return 0;
                                          }
                                          

                                          • 处理 GET 请求:如果请求方法是 GET,调用 clear_header() 函数清空请求报头。
                                          • 处理 POST 请求:如果请求方法是 POST,循环读取请求报头,查找 Content-Length 字段,获取请求体的长度。
                                          • 读取请求体:如果是 POST 请求,使用 recv() 函数根据 Content-Length 读取请求体数据。
                                          • 发送状态码:使用 send() 函数发送 HTTP 状态行 HTTP/1.1 200 OK。
                                          • 调用自定义处理函数:调用 parse_and_process() 函数处理请求,该函数可能包含具体的业务逻辑。

                                            (7)handler_msg 函数:浏览器请求处理函数

                                            handler_msg 函数的主要作用是处理来自浏览器的 HTTP 请求。它会解析请求的方法(如 GET 或 POST)、请求的 URL,检查请求的资源是否存在,然后根据请求的类型和资源情况做出相应的处理,比如返回静态文件或者调用自定义的处理函数。

                                            int handler_msg(int sock)       //浏览器请求处理函数
                                            {
                                                char del_buf[SIZE] = {};
                                                //通常recv()函数的最后一个参数为0,代表从缓冲区取走数据
                                                //而当为MSG_PEEK时代表只是查看数据,而不取走数据。
                                                recv(sock, del_buf, SIZE, MSG_PEEK); //MSG_PEEK表示只是把客户端发过来的数据看一下,recv接收完以后仍在缓存区
                                            #if 1 //初学者强烈建议打开这个开关,看看tcp实际请求的协议格式
                                                puts("---------------------------------------");
                                                printf("recv:%s\n", del_buf);
                                                puts("---------------------------------------");
                                            #endif
                                                //接下来method方法判断之前的代码,可以不用重点关注
                                                //知道是处理字符串,把需要的信息过滤出来即可
                                                char buf[SIZE];
                                                int count = get_line(sock, buf);
                                                int ret = 0;
                                                char method[32]; //存放请求方法
                                                char url[SIZE]; //存放URL
                                                char *query_string = NULL;
                                                int i = 0;
                                                int j = 0;
                                                int need_handle = 0; //判断是否需要处理
                                                //获取请求方法和请求路径(liaojie)
                                                while (j  
                                               
                                              

                                            • 预读请求数据:使用 recv() 函数和 MSG_PEEK 标志预读客户端发送的请求数据,方便调试。
                                            • 解析请求行:使用 get_line() 函数读取请求行,提取请求方法和请求路径。
                                            • 判断请求方法:使用 strcasecmp() 函数判断请求方法是否为 GET 或 POST,如果不是,调用 echo_error() 函数返回 405 错误。
                                            • 解析请求路径:遍历请求路径,查找 ? 字符,如果存在,将 query_string 指向 ? 后面的字符串,并在 ? 处截断 url 字符串。
                                            • 处理带参数的 GET 请求和 POST 请求:如果是 POST 请求或者带参数的 GET 请求,将 need_handle 标志置为 1。
                                            • 构造请求资源路径:将请求路径拼接上 wwwroot/ 前缀,如果请求的是目录,默认返回 index.html。
                                            • 检查资源是否存在:使用 stat() 函数检查请求的资源是否存在,如果不存在,调用 echo_error() 函数返回 404 错误。
                                            • 处理请求:根据 need_handle 标志,调用 handle_request() 函数处理带参数的 GET 请求和 POST 请求,或者调用 echo_www() 函数直接返回资源。
                                            • 关闭套接字:最后关闭套接字。

                                              代码精解析:

                                              (1)查看客户端发送的请求数据

                                              int handler_msg(int sock)       //浏览器请求处理函数
                                              {
                                                  char del_buf[SIZE] = {};
                                                  //通常recv()函数的最后一个参数为0,代表从缓冲区取走数据
                                                  //而当为MSG_PEEK时代表只是查看数据,而不取走数据。
                                                  recv(sock, del_buf, SIZE, MSG_PEEK); //MSG_PEEK表示只是把客户端发过来的数据看一下,recv接收完以后仍在缓存区
                                              #if 1 //初学者强烈建议打开这个开关,看看tcp实际请求的协议格式
                                                  puts("---------------------------------------");
                                                  printf("recv:%s\n", del_buf);
                                                  puts("---------------------------------------");
                                              #endif
                                              

                                              • del_buf 是一个字符数组,用于临时存储从客户端接收的数据。
                                              • recv 函数使用 MSG_PEEK 标志,这意味着只是查看客户端发送的数据,而不会从接收缓冲区中移除这些数据。
                                              • #if 1 部分用于调试,会打印接收到的请求数据,方便查看 HTTP 请求的格式。

                                                (2)解析请求方法和 URL

                                                    //接下来method方法判断之前的代码,可以不用重点关注
                                                    //知道是处理字符串,把需要的信息过滤出来即可
                                                    char buf[SIZE];
                                                    int count = get_line(sock, buf);
                                                    int ret = 0;
                                                    char method[32]; //存放请求方法(get  post 方法)
                                                    char url[SIZE]; //存放URL
                                                    char *query_string = NULL;
                                                    int i = 0;
                                                    int j = 0;
                                                    int need_handle = 0; //判断是否需要处理
                                                

                                                • buf 数组用于存储从客户端接收的一行数据。
                                                • get_line 函数从套接字中读取一行数据,并返回读取的字符数。
                                                • ret 用于存储函数的返回值。
                                                • method 数组用于存储请求方法(如 GET 或 POST)。
                                                • url 数组用于存储请求的 URL。
                                                • query_string 指针用于指向 URL 中的查询字符串(如果有的话)。
                                                • need_handle 是一个标志,用于判断是否需要对请求进行额外处理(资源处理)。

                                                  (3)检查请求方法是否为 GET 或 POST

                                                      //获取请求方法和请求路径(liaojie)
                                                      while (j  
                                                     
                                                    

                                                  • 第一个 while 循环从 buf 中提取请求方法,直到遇到空格为止。
                                                  • method[i] = '\0' 用于在方法字符串末尾添加终止符。
                                                  • 第二个 while 循环用于过滤掉请求方法和 URL 之间的空格。

                                                    (4)提取 URL 中的查询字符串

                                                        //这里开始就开始判断发过来的请求是GET还是POST了
                                                        if (strcasecmp(method, "POST") && strcasecmp(method, "GET")) //strcasecmp可以忽略大小写的区别,其余和strcmp一样
                                                        {
                                                            printf("method failed\n");  //如果都不是,那么提示一下
                                                            echo_error(sock, 405); //405是状态码,表示方法不对
                                                            ret = 5;
                                                            goto end; //掉转到end,运行end下面的函数
                                                        }
                                                        if (strcasecmp(method, "POST") == 0)
                                                        {
                                                            need_handle = 1;
                                                        }
                                                    

                                                    • strcasecmp 函数用于比较字符串,忽略大小写。
                                                    • 如果请求方法既不是 GET 也不是 POST,则调用 echo_error 函数返回 405 状态码,表示不支持该请求方法。
                                                    • 如果请求方法是 POST,则将 need_handle 标志设置为 1,表示需要对请求进行额外处理。

                                                      (5)构建请求资源的实际路径

                                                          i = 0;
                                                          while (j  
                                                         
                                                        

                                                      • 这个 while 循环从 buf 中提取请求的 URL。
                                                      • 如果遇到 ? 字符,则将 query_string 指针指向 ? 后面的查询字符串,并在 url 中添加终止符。
                                                      • 最后在 url 末尾添加终止符,并打印查询字符串。

                                                        (6)检查资源是否存在

                                                            //浏览器通过http://192.168.8.208:8080/?test=1234这种形式请求
                                                            //是携带参数的意思,那么就需要额外处理了
                                                            if (strcasecmp(method, "GET") == 0 && query_string != NULL) //前面的函数已经将指针query_string定位到?,如果问号不等于空,说明有参数,需要进一步处理
                                                            {
                                                                need_handle = 1;
                                                            }
                                                        

                                                        • 如果请求方法是 GET 且存在查询字符串,则将 need_handle 标志设置为 1,表示需要对请求进行额外处理。

                                                          (7)根据请求类型和资源情况,决定是处理请求还是返回静态文件

                                                              //我们把请求资源的路径固定为wwwroot/下的资源,这个自己可以改
                                                              char path[SIZE];
                                                              sprintf(path, "wwwroot%s", url);     //wwwroot/404.html,寻找wwwroot里的404.html
                                                              printf("path = %s\n", path);
                                                              //如果请求地址没有携带任何资源,那么默认返回index.html
                                                              if (path[strlen(path) - 1] == '/')              //判断浏览器请求的是不是目录
                                                              {
                                                                  strcat(path, "index.html");     //如果请求的是目录,则就把该目录下的首页返回回去,strcat是拼接函数
                                                              }
                                                          

                                                          • path 数组用于存储请求资源的实际路径,将 wwwroot 和请求的 URL 拼接起来。
                                                          • 如果请求的路径以 / 结尾,则表示请求的是一个目录,将 index.html 拼接到路径后面。

                                                            (8)关闭套接字并返回处理结果

                                                                //如果请求的资源不存在,就要返回传说中的404页面了
                                                                struct stat st;
                                                                if (stat(path, &st)  
                                                               
                                                              

                                                            • stat 函数用于获取请求资源的相关属性,如文件大小、权限等。
                                                            • 如果 stat 函数返回值小于 0,表示文件不存在,调用 echo_error 函数返回 404 状态码。

                                                              c

                                                                  //到这里基本就能确定是否需要自己的程序来处理后续请求了
                                                                  printf("need progress handle:%d\n", need_handle);
                                                                  //如果是POST请求或者带参数的GET请求,就需要我们自己来处理了
                                                                  //这些是业务逻辑,所以需要我们自己写代码来决定怎么处理
                                                                  if (need_handle)
                                                                  {
                                                                      ret = handle_request(sock, method, path, query_string);
                                                                  }
                                                                  else
                                                                  {
                                                                      clear_header(sock);
                                                                      //如果是GET,而且没有参数,则直接返回资源
                                                                      ret = echo_www(sock, path, st.st_size);
                                                                  }
                                                              end:
                                                                  close(sock);
                                                                  return ret;
                                                              }
                                                              

                                                              • 根据 need_handle 标志的值,决定是调用 handle_request 函数处理请求,还是调用 echo_www 函数直接返回静态文件。
                                                              • clear_header 函数用于清除请求头。
                                                              • 最后关闭套接字并返回处理结果。

                                                                (8)get与post请求

                                                                代码逻辑图:

                                                                8.1GET 请求处理(结合原理图的交互逻辑)

                                                                8.1.1无参数 GET 请求

                                                                  1. 原理对应:网页通过 HTTP 向 WebServer 发送无参数的资源请求(如直接访问页面)。
                                                                  2. 代码逻辑:WebServer 检查请求资源是否存在(如 index.html 或其他静态文件)。若存在,通过 echo_www 函数直接返回资源内容(对应原理图中 WebServer 直接响应网页请求);若资源不存在,返回 404 页面,模拟原理图中 “请求资源异常” 的处理。

                                                                8.1.2有参数 GET 请求

                                                                  1. 原理对应:网页通过 HTTP 携带参数请求(如 http://xxx?key=value),希望 WebServer 处理特定业务逻辑。
                                                                  2. 代码逻辑:解析到 URL 有参数后,need_handle = 1,触发 handle_request 函数。

                                                                在 handle_request 中,清空报头后,通过 parse_and_process 处理参数(类似原理图中 WebServer 接收请求后,可能通过进程间通信将参数传递给 Modbus 采集控制程序,实现对 Modbus 设备的参数化控制或数据查询)。

                                                                8.2POST 请求处理(结合原理图的交互逻辑)

                                                                • 原理对应:网页通过 POST 提交数据(如表单提交),数据需经 WebServer 处理后,可能进一步作用于 Modbus 设备(如控制设备参数、上传设备配置等)。
                                                                • 代码逻辑:解析请求时,识别为 POST 后,need_handle = 1,进入 handle_request。
                                                                  • 读取 Content-Length 获取请求体长度,再用 recv 读取 POST 数据。
                                                                  • 最后通过 parse_and_process 处理数据(对应原理图中 WebServer 接收网页 POST 数据后,通过进程间通信将数据交给 Modbus 采集控制程序,由该程序通过 Modbus/TCP 与 Modbus 设备交互,完成数据采集或控制操作)。

                                                                    8.3 GET 请求处理(结合代码与原理图)

                                                                    8.3.1. 无参数 GET 请求
                                                                    • 代码逻辑:

                                                                      在 handler_msg 函数中,若解析到 GET 请求且无参数(query_string == NULL),则执行 echo_www 函数。例如:

                                                                      if (!need_handle) {  
                                                                          clear_header(sock);  
                                                                          ret = echo_www(sock, path, st.st_size);  
                                                                      }  
                                                                      

                                                                      • echo_www 会直接打开请求的文件(如 wwwroot 下的静态资源),通过 sendfile 发送给网页。若资源不存在(如 stat 检测失败),则调用 echo_error 返回 404 页面。
                                                                      • 与原理图的关联:
                                                                      • 对应原理图中 “网页 → WebServer” 的基础交互。网页请求静态资源(如 HTML、图片),WebServer 直接响应,无需复杂业务处理,如同原理图中 WebServer 作为资源提供者,直接返回网页所需内容。
                                                                        8.3.2. 有参数 GET 请求
                                                                        • 代码逻辑:

                                                                          当 GET 请求带参数(如 http://xxx?key=value),query_string != NULL,need_handle = 1,触发 handle_request 函数:

                                                                          if (need_handle) {  
                                                                              ret = handle_request(sock, method, path, query_string);  
                                                                          }  
                                                                          

                                                                          在 handle_request 中,清空报头后,通过 parse_and_process 处理参数。例如:

                                                                          parse_and_process(sock, query_string, req_buf);  
                                                                          

                                                                          • 这部分可对接业务逻辑,如根据参数查询 Modbus 设备数据(需结合 Modbus采集控制程序)。
                                                                          • 与原理图的关联:
                                                                          • 体现原理图中 WebServer 的 “桥梁” 作用。网页通过带参数的 GET 请求,希望触发特定操作(如查询 Modbus 设备状态)。WebServer 解析参数后,可通过进程间通信,将参数传递给 Modbus采集控制程序,最终与 Modbus 设备交互,再将结果返回网页。

                                                                            8.4POST 请求处理(结合代码与原理图)

                                                                            • 代码逻辑:

                                                                              在 handler_msg 中识别 POST 请求后,need_handle = 1,进入 handle_request。代码先解析 Content-Length 确定请求体长度,再读取数据:

                                                                              if (strcasecmp(method, "POST") == 0) {  
                                                                                  int len = recv(sock, req_buf, content_len, 0);  
                                                                                  // 读取请求体数据  
                                                                              }  
                                                                              parse_and_process(sock, query_string, req_buf);  
                                                                              

                                                                              • parse_and_process 负责处理 POST 数据,如解析表单字段、命令等。
                                                                              • 与原理图的关联:

                                                                                对应原理图中 “网页 → WebServer → Modbus 采集控制程序” 的复杂交互。网页通过 POST 提交数据(如设备控制指令),WebServer 接收后,通过进程间通信将数据交给 Modbus采集控制程序。该程序再通过 Modbus/TCP 与 Modbus 设备通信,执行控制操作,最终将结果反馈给网页,形成完整的数据处理闭环。

                                                                                8.5代码与原理图的整体映射

                                                                                8.5.1WebServer 的核心职能:

                                                                                代码中的 handler_msg、handle_request 等函数,实现了原理图中 WebServer 的 HTTP 交互功能。无论是 GET 还是 POST,WebServer 都承担请求解析、初步处理的角色。

                                                                                8.5.2业务延伸(Modbus 交互):

                                                                                代码中的 parse_and_process 是扩展接口,对应原理图中 WebServer 与 Modbus采集控制程序 的进程间通信。通过这一接口,可将 HTTP 请求转化为对 Modbus 设备的操作(如采集数据、下发指令),最终实现网页对 Modbus 设备的间接控制,完整串联起原理图的交互链路。

                                                                                三.main.c

                                                                                #include"thttpd.h"
                                                                                #include 
                                                                                #include 
                                                                                #include 
                                                                                #include 
                                                                                #include 
                                                                                #include 
                                                                                #include 
                                                                                static void* msg_request(void *arg)
                                                                                {
                                                                                	//这里客户端描述符通过参数传进来了
                                                                                	int sock=(int)arg;//强转成int型
                                                                                	// 一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。
                                                                                	//但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
                                                                                	pthread_detach(pthread_self());	
                                                                                	//handler_msg作为所有的请求处理入口
                                                                                	return (void*)handler_msg(sock);
                                                                                }
                                                                                int main(int argc,char* argv[])
                                                                                {
                                                                                	//如果不传递端口,那么使用默认端口80
                                                                                	int port = 80;
                                                                                	if(argc > 1)
                                                                                	{
                                                                                		port = atoi(argv[1]);
                                                                                	}
                                                                                	//初始化服务器
                                                                                	int lis_sock=init_server(port);
                                                                                	while(1)
                                                                                	{
                                                                                		struct sockaddr_in peer;
                                                                                		socklen_t len=sizeof(peer);
                                                                                		
                                                                                		int sock=accept(lis_sock,(struct sockaddr*)&peer,&len);
                                                                                		
                                                                                		if(sock
                                                                                			perror("accept failed");
                                                                                			continue;
                                                                                		}
                                                                                		
                                                                                		//每次接收一个链接后,会自动创建一个线程,这实际上就是线程服务器模型的应用
                                                                                		pthread_t tid;
                                                                                		if(pthread_create(&tid,NULL,msg_request,(void*)sock)0)
                                                                                		{
                                                                                			perror("pthread_create failed");
                                                                                			close(sock);
                                                                                		}
                                                                                	}
                                                                                	return 0;
                                                                                }
                                                                                

                                                                                (1)msg_request 函数

                                                                                static void* msg_request(void *arg)
                                                                                {
                                                                                    //这里客户端描述符通过参数传进来了
                                                                                    int sock=(int)arg;//强转成int型
                                                                                    // 一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。
                                                                                    //但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
                                                                                    pthread_detach(pthread_self());
                                                                                    //handler_msg作为所有的请求处理入口
                                                                                    return (void*)handler_msg(sock);
                                                                                }
                                                                                

                                                                                • 功能:该函数是每个线程的入口函数,用于处理客户端的请求。
                                                                                • 参数:arg 是一个 void* 类型的指针,实际传入的是客户端套接字描述符,通过强制类型转换将其转换为 int 类型。
                                                                                • pthread_detach 函数:将当前线程设置为分离状态,这样线程结束后会自动释放其占用的资源,无需其他线程调用 pthread_join 来回收资源。
                                                                                • handler_msg 函数:作为请求处理的入口,负责处理客户端的具体请求。函数返回值被强制转换为 void* 类型并返回。

                                                                                  (2)main 函数

                                                                                  int main(int argc,char* argv[])
                                                                                  {
                                                                                      //如果不传递端口,那么使用默认端口80
                                                                                      int port = 80;
                                                                                      if(argc > 1)
                                                                                      {
                                                                                          port = atoi(argv[1]);
                                                                                      }
                                                                                      //初始化服务器
                                                                                      int lis_sock=init_server(port);
                                                                                      while(1)
                                                                                      {
                                                                                          struct sockaddr_in peer;
                                                                                          socklen_t len=sizeof(peer);
                                                                                          int sock=accept(lis_sock,(struct sockaddr*)&peer,&len);
                                                                                          if(sock
                                                                                              perror("accept failed");
                                                                                              continue;
                                                                                          }
                                                                                          //每次接收一个链接后,会自动创建一个线程,这实际上就是线程服务器模型的应用
                                                                                          pthread_t tid;
                                                                                          if(pthread_create(&tid,NULL,msg_request,(void*)sock)0)
                                                                                          {
                                                                                              perror("pthread_create failed");
                                                                                              close(sock);
                                                                                          }
                                                                                      }
                                                                                      return 0;
                                                                                  }
                                                                                  

                                                                                  • 端口设置:首先设置默认端口为 80,如果在命令行中传递了参数,则将第一个参数转换为整数作为端口号。
                                                                                  • 服务器初始化:调用 init_server 函数初始化服务器,该函数可能会创建监听套接字、绑定地址和端口,并开始监听客户端的连接请求。返回的 lis_sock 是监听套接字描述符。
                                                                                  • 主循环:
                                                                                    • 使用 while(1) 进入一个无限循环,不断等待客户端的连接请求。accept 函数:用于接受客户端的连接请求,返回一个新的套接字描述符 sock,该描述符用于与客户端进行通信。如果 accept 函数调用失败,会输出错误信息并继续循环。
                                                                                    • pthread_create 函数:每当接收到一个新的客户端连接,就创建一个新的线程来处理该客户端的请求。msg_request 函数作为线程的入口函数,将客户端套接字描述符作为参数传递给它。如果线程创建失败,会输出错误信息并关闭客户端套接字。

                                                                                      (3)功能概述

                                                                                      这段代码实现了一个多线程的 HTTP 服务器,通过创建多个线程来处理客户端的请求,提高了服务器的并发处理能力。每个线程独立处理一个客户端的请求,并且在结束后自动释放资源。服务器会不断监听客户端的连接请求,直到程序被手动终止。

                                                                                      四.Makefile

                                                                                      thttpd.out:main.c thttpd.c custom_handle.c
                                                                                      	gcc -o $@ $^ -lpthread
                                                                                      clean:
                                                                                      	rm -rf *.out
                                                                                      

                                                                                      五. custom_handle.c

                                                                                      /***********************************************************************************
                                                                                      Copy right:	    hqyj Tech.
                                                                                      Author:         jiaoyue
                                                                                      Date:           2023.07.01
                                                                                      Description:    http请求处理
                                                                                      ***********************************************************************************/
                                                                                      #include 
                                                                                      #include 
                                                                                      #include "custom_handle.h"
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #include 
                                                                                      #define KB 1024
                                                                                      #define HTML_SIZE (64 * KB)
                                                                                         struct msgbuf
                                                                                      {
                                                                                          long mtype; // 第一个成员必须是long类型变量,表示消息的类型
                                                                                          int num1;
                                                                                          int num2;
                                                                                      };
                                                                                      // 普通的文本回复需要增加html头部
                                                                                      #define HTML_HEAD "Content-Type: text/html\r\n" \
                                                                                                        "Connection: close\r\n"
                                                                                      static int handle_login(int sock, const char *input)
                                                                                      {
                                                                                          char reply_buf[HTML_SIZE] = {0};
                                                                                          char *uname = strstr(input, "username=");
                                                                                          uname += strlen("username=");
                                                                                          char *p = strstr(input, "password");
                                                                                           *(p - 1) = '\0';
                                                                                          printf("username = %s\n", uname);
                                                                                          char *passwd = p + strlen("password=");
                                                                                          printf("passwd = %s\n", passwd);
                                                                                          if (strcmp(uname, "admin") == 0 && strcmp(passwd, "admin") == 0)
                                                                                          {
                                                                                              sprintf(reply_buf, "localStorage.setItem('usr_user_name', '%s');", uname);
                                                                                              strcat(reply_buf, "window.location.href = '/index.html';");
                                                                                              send(sock, reply_buf, strlen(reply_buf), 0);
                                                                                          }
                                                                                          else
                                                                                          {
                                                                                              printf("web login failed\n");
                                                                                              //"用户名或密码错误"提示,chrome浏览器直接输送utf-8字符流乱码,没有找到太好解决方案,先过渡
                                                                                              char out[128] = {0xd3, 0xc3, 0xbb, 0xa7, 0xc3, 0xfb, 0xbb, 0xf2, 0xc3, 0xdc, 0xc2, 0xeb, 0xb4, 0xed, 0xce, 0xf3};
                                                                                              sprintf(reply_buf, "alert('%s');", out);
                                                                                              strcat(reply_buf, "window.location.href = '/login.html';");
                                                                                              send(sock, reply_buf, strlen(reply_buf), 0);
                                                                                          }
                                                                                          return 0;
                                                                                      }
                                                                                      static int handle_add(int sock, const char *input)
                                                                                      {
                                                                                          int number1, number2;
                                                                                          // input必须是"data1=1data2=6"类似的格式,注意前端过来的字符串会有双引号
                                                                                          sscanf(input, "\"data1=%ddata2=%d\"", &number1, &number2); // 从input里面提取两个数值赋值给number1,number2m,
                                                                                          printf("num1 = %d\n", number1);
                                                                                          char reply_buf[HTML_SIZE] = {0};
                                                                                          printf("num = %d\n", number1 + number2);
                                                                                          sprintf(reply_buf, "%d", number1 + number2);
                                                                                          printf("resp = %s\n", reply_buf);
                                                                                          send(sock, reply_buf, strlen(reply_buf), 0);
                                                                                          return 0;
                                                                                      }
                                                                                      /**
                                                                                       * @brief 处理自定义请求,在这里添加进程通信
                                                                                       * @param input
                                                                                       * @return
                                                                                       */
                                                                                      static int handle_recv(int sock)
                                                                                         {
                                                                                          key_t key;
                                                                                          key = ftok("./main.c", 'a');
                                                                                          int shmid = shmget(key, 64, 0777); // 调用scanf.c文件中的key和shmid
                                                                                             if (shmid  
                                                                                         
                                                                                        

                                                                                      (1)handle_login 函数

                                                                                      • 功能:处理登录请求,验证用户名和密码。
                                                                                      • 步骤:从 input 中提取用户名和密码。
                                                                                        • 验证用户名和密码是否为 admin。
                                                                                        • 如果验证通过,使用 JavaScript 脚本将用户名存储到本地存储,并跳转到 index.html 页面。
                                                                                        • 如果验证失败,使用 JavaScript 脚本弹出提示框,显示 “用户名或密码错误”,并跳转到 login.html 页面。

                                                                                          (2)handle_add 函数

                                                                                          static int handle_add(int sock, const char *input)
                                                                                          {
                                                                                              int number1, number2;
                                                                                              // input必须是"data1=1data2=6"类似的格式,注意前端过来的字符串会有双引号
                                                                                              sscanf(input, "\"data1=%ddata2=%d\"", &number1, &number2);
                                                                                              printf("num1 = %d\n", number1);
                                                                                              char reply_buf[HTML_SIZE] = {0};
                                                                                              printf("num = %d\n", number1 + number2);
                                                                                              sprintf(reply_buf, "%d", number1 + number2);
                                                                                              printf("resp = %s\n", reply_buf);
                                                                                              send(sock, reply_buf, strlen(reply_buf), 0);
                                                                                              return 0;
                                                                                          }
                                                                                          

                                                                                          • 功能:处理求和请求,从 input 中提取两个整数并求和,将结果发送给客户端。
                                                                                          • 步骤:使用 sscanf 函数从 input 中提取两个整数 number1 和 number2。
                                                                                            • 计算两个整数的和。
                                                                                            • 将结果存储在 reply_buf 中,并发送给客户端。

                                                                                              (3)handle_recv 函数

                                                                                              static int handle_recv(int sock)
                                                                                              {
                                                                                                  key_t key;
                                                                                                  key = ftok("./main.c", 'a');
                                                                                                  int shmid = shmget(key, 64, 0777);
                                                                                                  if (shmid  
                                                                                                
                                                                                              • 功能:从共享内存中读取数据,并将其发送给客户端。
                                                                                              • 步骤:
                                                                                              • 使用 ftok 函数生成共享内存的键值。
                                                                                                • 使用 shmget 函数获取共享内存的标识符。
                                                                                                • 使用 shmat 函数将共享内存映射到当前进程的地址空间。
                                                                                                • 如果共享内存中有数据,将其复制到 buf 中,并发送给客户端。

                                                                                                  (4)handle_send 函数

                                                                                                  static int handle_send(int sock, const char *input)
                                                                                                  {
                                                                                                      int number1, number2;
                                                                                                      // input必须是"data1=1data2=6"类似的格式,注意前端过来的字符串会有双引号
                                                                                                      sscanf(input, "mange_set=%d %d", &number1, &number2);
                                                                                                      // 创建key值
                                                                                                      key_t key;
                                                                                                      key = ftok("./main.c", 'a');
                                                                                                      if (key  
                                                                                                       
                                                                                                      

                                                                                                  • 功能:从 input 中提取两个整数,将其封装成消息发送到消息队列中,并向客户端发送确认信息。
                                                                                                  • 步骤:使用 sscanf 函数从 input 中提取两个整数 number1 和 number2。
                                                                                                    • 使用 ftok 函数生成消息队列的键值。
                                                                                                    • 使用 msgget 函数创建或获取消息队列的标识符。
                                                                                                    • 将 number1 和 number2 封装成 msgbuf 结构体,并使用 msgsnd 函数将消息发送到消息队列中。
                                                                                                    • 向客户端发送确认信息 send ok。

                                                                                                      (5)parse_and_process 函数

                                                                                                      int parse_and_process(int sock, const char *query_string, const char *input)
                                                                                                      {
                                                                                                          // query_string不一定能用的到
                                                                                                          // 先处理登录操作
                                                                                                          if (strstr(input, "username=") && strstr(input, "password=")) // 检测是否存在username
                                                                                                          {
                                                                                                              return handle_login(sock, input);
                                                                                                          }
                                                                                                          // 处理求和请求
                                                                                                          else if (strstr(input, "data1=") && strstr(input, "data2="))
                                                                                                          {
                                                                                                              return handle_add(sock, input);
                                                                                                          }
                                                                                                          else if (strstr(input, "mange_get"))
                                                                                                          {
                                                                                                              return handle_recv(sock);
                                                                                                          }
                                                                                                          else if (strstr(input, "mange_set="))
                                                                                                          {
                                                                                                              return handle_send(sock, input);
                                                                                                          }
                                                                                                          else // 剩下的都是json请求,这个和协议有关了
                                                                                                          {
                                                                                                              // 构建要回复的JSON数据
                                                                                                              const char *json_response = "{\"message\": \"Hello, client!\"}";
                                                                                                              // 发送HTTP响应给客户端
                                                                                                              send(sock, json_response, strlen(json_response), 0);
                                                                                                          }
                                                                                                          return 0;
                                                                                                      }
                                                                                                      

                                                                                                      • 功能:根据 input 的内容,调用不同的处理函数来处理请求。
                                                                                                      • 步骤:检查 input 中是否包含 username= 和 password=,如果是,则调用 handle_login 函数处理登录请求。
                                                                                                        • 检查 input 中是否包含 data1= 和 data2=,如果是,则调用 handle_add 函数处理求和请求。
                                                                                                        • 检查 input 中是否包含 mange_get,如果是,则调用 handle_recv 函数从共享内存中读取数据。
                                                                                                        • 检查 input 中是否包含 mange_set=,如果是,则调用 handle_send 函数将数据发送到消息队列中。
                                                                                                        • 如果以上条件都不满足,则发送一个 JSON 响应给客户端。

                                                                                                          六.example.c

                                                                                                          整体功能概述

                                                                                                          此代码构建了一个名为 “工业信息采集系统” 的网页,其主要功能为采集工业相关的数据(像光照数据、加速度数据),同时可对蜂鸣器和 LED 灯进行控制。

                                                                                                          (1)源码示例

                                                                                                          
                                                                                                          
                                                                                                              
                                                                                                              
                                                                                                              基于WebServer 的工业数据采集项目
                                                                                                              
                                                                                                                  /* 全局样式 */
                                                                                                                  body {
                                                                                                                      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                                                                                                                      background-color: #f4f4f9;
                                                                                                                      color: #333;
                                                                                                                      margin: 0;
                                                                                                                      padding: 20px;
                                                                                                                      display: flex;
                                                                                                                      justify-content: center;
                                                                                                                      align-items: center;
                                                                                                                      min-height: 100vh;
                                                                                                                  }
                                                                                                                 .container {
                                                                                                                      background-color: #fff;
                                                                                                                      padding: 30px;
                                                                                                                      border-radius: 10px;
                                                                                                                      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
                                                                                                                      max-width: 600px;
                                                                                                                      width: 100%;
                                                                                                                  }
                                                                                                                  h1 {
                                                                                                                      color: #007bff;
                                                                                                                      text-align: center;
                                                                                                                      margin-bottom: 20px;
                                                                                                                      font-size: 2rem;
                                                                                                                  }
                                                                                                                  h2 {
                                                                                                                      color: #007bff;
                                                                                                                      margin-top: 20px;
                                                                                                                      font-size: 1.5rem;
                                                                                                                  }
                                                                                                                  input[type="text"] {
                                                                                                                      width: calc(100% - 120px);
                                                                                                                      padding: 10px;
                                                                                                                      border: 1px solid #ddd;
                                                                                                                      border-radius: 5px;
                                                                                                                      font-size: 1rem;
                                                                                                                      margin-right: 10px;
                                                                                                                  }
                                                                                                                  input[type="button"] {
                                                                                                                      padding: 10px 20px;
                                                                                                                      background-color: #007bff;
                                                                                                                      color: #fff;
                                                                                                                      border: none;
                                                                                                                      border-radius: 5px;
                                                                                                                      cursor: pointer;
                                                                                                                      font-size: 1rem;
                                                                                                                      transition: background-color 0.3s ease;
                                                                                                                  }
                                                                                                                  input[type="button"]:hover {
                                                                                                                      background-color: #0056b3;
                                                                                                                  }
                                                                                                                  input[type="radio"] {
                                                                                                                      margin-right: 10px;
                                                                                                                  }
                                                                                                                  label {
                                                                                                                      margin-right: 20px;
                                                                                                                      font-size: 1rem;
                                                                                                                  }
                                                                                                                 .blink {
                                                                                                                      animation: blink 1s infinite;
                                                                                                                  }
                                                                                                                  @keyframes blink {
                                                                                                                      0% {
                                                                                                                          opacity: 1;
                                                                                                                      }
                                                                                                                      50% {
                                                                                                                          opacity: 0.5;
                                                                                                                      }
                                                                                                                      100% {
                                                                                                                          opacity: 1;
                                                                                                                      }
                                                                                                                  }
                                                                                                              
                                                                                                              
                                                                                                                  function getinfof() {
                                                                                                                      // 获取光照数据输入框
                                                                                                                      var input = document.querySelector('input[name="usrname"][value=""]');
                                                                                                                      if (input) {
                                                                                                                          var xhr = new XMLHttpRequest();
                                                                                                                          var url = "your_server_url";//请替换为实际的服务器URL
                                                                                                                          xhr.open("post", url, true);
                                                                                                                          xhr.send("mange_get");
                                                                                                                          xhr.onreadystatechange = function () {
                                                                                                                              if (xhr.readyState == 4 && xhr.status == 200) {
                                                                                                                                  var t = xhr.responseText;
                                                                                                                                  var data = t.split('#');
                                                                                                                                  input.value = data[0];
                                                                                                                              }
                                                                                                                          }
                                                                                                                      }
                                                                                                                  }
                                                                                                                  function getinfos() {
                                                                                                                      //获取加速度数据输入框
                                                                                                                      var inputs = document.querySelectorAll('input[name="usrname"]');
                                                                                                                      if (inputs.length > 1) {
                                                                                                                          var input = inputs[1];
                                                                                                                          var xhr = new XMLHttpRequest();
                                                                                                                          var url = "your_server_url";//请替换为实际的服务器URL
                                                                                                                          xhr.open("post", url, true);
                                                                                                                          xhr.send("mange_get");
                                                                                                                          xhr.onreadystatechange = function () {
                                                                                                                              if (xhr.readyState == 4 && xhr.status == 200) {
                                                                                                                                  var t = xhr.responseText;
                                                                                                                                  var data = t.split('#');
                                                                                                                                  input.value = data[1] + data[2] + data[3];
                                                                                                                              }
                                                                                                                          }
                                                                                                                      }
                                                                                                                  }
                                                                                                                  function fun1() {
                                                                                                                      var xhr = new XMLHttpRequest();
                                                                                                                      var url = "your_server_url";//请替换为实际的服务器URL
                                                                                                                      xhr.open("post", url, true);
                                                                                                                      xhr.send("mange_set=1 1"); // 蜂鸣器开
                                                                                                                  }
                                                                                                                  function fun2() {
                                                                                                                      var xhr = new XMLHttpRequest();
                                                                                                                      var url = "your_server_url";//请替换为实际的服务器URL
                                                                                                                      xhr.open("post", url, true);
                                                                                                                      xhr.send("mange_set=1 0"); // 蜂鸣器关
                                                                                                                  }
                                                                                                                  function fun3() {
                                                                                                                      var xhr = new XMLHttpRequest();
                                                                                                                      var url = "your_server_url";//请替换为实际的服务器URL
                                                                                                                      xhr.open("post", url, true);
                                                                                                                      xhr.send("mange_set=0 1"); // LED开
                                                                                                                  }
                                                                                                                  function fun4() {
                                                                                                                      var xhr = new XMLHttpRequest();
                                                                                                                      var url = "your_server_url";//请替换为实际的服务器URL
                                                                                                                      xhr.open("post", url, true);
                                                                                                                      xhr.send("mange_set=0 0"); // LED关
                                                                                                                  }
                                                                                                              
                                                                                                          
                                                                                                          
                                                                                                              
                                                                                                                  

                                                                                                          工业信息采集系统

                                                                                                          光照数据:

                                                                                                          加速度数据:

                                                                                                          蜂鸣器:

                                                                                                          开 关

                                                                                                          LED灯:

                                                                                                          开 关

                                                                                                          (2)拆解分析

                                                                                                          1. HTML 部分

                                                                                                          整体概述

                                                                                                          这段 HTML 代码构建了一个名为 “工业信息采集系统” 的网页,该网页提供了数据采集(光照数据、加速度数据)以及设备控制(蜂鸣器、LED 灯)的功能界面。

                                                                                                          
                                                                                                          
                                                                                                              
                                                                                                              
                                                                                                              工业信息采集系统
                                                                                                              
                                                                                                          
                                                                                                          
                                                                                                              
                                                                                                                  

                                                                                                          工业信息采集系统

                                                                                                          光照数据:

                                                                                                          加速度数据:

                                                                                                          蜂鸣器:

                                                                                                          开 关

                                                                                                          LED灯:

                                                                                                          开 关

                                                                                                          解析:

                                                                                                          • 标题:工业信息采集系统 设置了网页标题。
                                                                                                          • 主体内容:
                                                                                                            • 借助 对内容进行包裹,起到布局的作用。
                                                                                                            • 标题

                                                                                                              运用了 blink 类,可实现闪烁效果。

                                                                                                            • 包含两个输入框与对应的按钮,用于获取光照数据和加速度数据。
                                                                                                            • 设有两组单选框,分别用于控制蜂鸣器和 LED 灯的开关。
                                                                                                            • 重难点语句:
                                                                                                              (1)整体容器
                                                                                                                  
                                                                                                                      
                                                                                                                  
                                                                                                              
                                                                                                              

                                                                                                              是一个通用的容器元素(盒子), 表示该元素应用了名为 container 的类样式,通常用于对页面内容进行布局和样式设置。

                                                                                                              (2)页面标题

                                                                                                              html

                                                                                                              工业信息采集系统

                                                                                                              是一级标题标签,用于显示页面的主要标题。 表明该标题应用了名为 blink 的类样式,可能会实现闪烁效果。

                                                                                                              (3)光照数据部分

                                                                                                              html

                                                                                                              光照数据:

                                                                                                              • :二级标题标签,用于显示光照数据的标题。

                                                                                                              • :创建一个文本输入框,name="usrname" 为该输入框指定名称,value="" 表示输入框初始为空。
                                                                                                              • :创建一个按钮,按钮上显示 “获取数据”。name="flash" 为按钮指定名称,οnclick="getinfof()" 表示当用户点击该按钮时,会调用名为 getinfof() 的 JavaScript 函数。
                                                                                                                (4)加速度数据部分

                                                                                                                html

                                                                                                                加速度数据:

                                                                                                                这部分与光照数据部分类似,只是按钮点击时调用的 JavaScript 函数为 getinfos()。

                                                                                                                (5)蜂鸣器控制部分

                                                                                                                html

                                                                                                                蜂鸣器:

                                                                                                                开 关
                                                                                                                • :二级标题标签,用于显示蜂鸣器控制的标题。

                                                                                                                • :创建一个单选框,name="蜂鸣器" 表示这些单选框属于同一组,用户只能选择其中一个。id="on" 为该单选框指定唯一标识符,οnclick="fun1()" 表示当用户选择该单选框时,会调用名为 fun1() 的 JavaScript 函数。
                                                                                                                • 标签用于为表单元素提供文本描述,点击标签内的文本也能选中对应的单选框。
                                                                                                                  (6)LED 控制部分

                                                                                                                  LED灯:

                                                                                                                  开 关

                                                                                                                  这部分与蜂鸣器控制部分类似,用于控制 LED 灯的开关,点击单选框时分别调用 fun3() 和 fun4() 函数。

                                                                                                                  2. CSS 部分
                                                                                                                  /* 全局样式 */
                                                                                                                  body {
                                                                                                                      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                                                                                                                      background-color: #f4f4f9;
                                                                                                                      color: #333;
                                                                                                                      margin: 0;
                                                                                                                      padding: 20px;
                                                                                                                      display: flex;
                                                                                                                      justify-content: center;
                                                                                                                      align-items: center;
                                                                                                                      min-height: 100vh;
                                                                                                                  }
                                                                                                                  .container {
                                                                                                                      background-color: #fff;
                                                                                                                      padding: 30px;
                                                                                                                      border-radius: 10px;
                                                                                                                      box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
                                                                                                                      max-width: 600px;
                                                                                                                      width: 100%;
                                                                                                                  }
                                                                                                                  h1 {
                                                                                                                      color: #007bff;
                                                                                                                      text-align: center;
                                                                                                                      margin-bottom: 20px;
                                                                                                                      font-size: 2rem;
                                                                                                                  }
                                                                                                                  h2 {
                                                                                                                      color: #007bff;
                                                                                                                      margin-top: 20px;
                                                                                                                      font-size: 1.5rem;
                                                                                                                  }
                                                                                                                  input[type="text"] {
                                                                                                                      width: calc(100% - 120px);
                                                                                                                      padding: 10px;
                                                                                                                      border: 1px solid #ddd;
                                                                                                                      border-radius: 5px;
                                                                                                                      font-size: 1rem;
                                                                                                                      margin-right: 10px;
                                                                                                                  }
                                                                                                                  input[type="button"] {
                                                                                                                      padding: 10px 20px;
                                                                                                                      background-color: #007bff;
                                                                                                                      color: #fff;
                                                                                                                      border: none;
                                                                                                                      border-radius: 5px;
                                                                                                                      cursor: pointer;
                                                                                                                      font-size: 1rem;
                                                                                                                      transition: background-color 0.3s ease;
                                                                                                                  }
                                                                                                                  input[type="button"]:hover {
                                                                                                                      background-color: #0056b3;
                                                                                                                  }
                                                                                                                  input[type="radio"] {
                                                                                                                      margin-right: 10px;
                                                                                                                  }
                                                                                                                  label {
                                                                                                                      margin-right: 20px;
                                                                                                                      font-size: 1rem;
                                                                                                                  }
                                                                                                                  .blink {
                                                                                                                      animation: blink 1s infinite;
                                                                                                                  }
                                                                                                                  @keyframes blink {
                                                                                                                      0% { opacity: 1; }
                                                                                                                      50% { opacity: 0.5; }
                                                                                                                      100% { opacity: 1; }
                                                                                                                  }
                                                                                                                  
                                                                                                                  • 全局样式:
                                                                                                                    • 对 body 标签的字体、背景颜色、内边距、布局等样式进行了设置。
                                                                                                                    • 利用 display: flex 让内容在页面中垂直和水平居中。
                                                                                                                    • 容器样式:
                                                                                                                      • .container 类定义了内容容器的样式,包含背景颜色、内边距、圆角和阴影等。
                                                                                                                      • 输入框与按钮样式:
                                                                                                                        • 对输入框和按钮的样式进行了设置,并且为按钮添加了悬停效果。
                                                                                                                        • 闪烁动画:
                                                                                                                          • blink 类和 @keyframes 规则实现了标题的闪烁效果。
                                                                                                                            • 全局样式:对 body 标签的字体、背景颜色、内边距、布局等样式进行了设置。
                                                                                                                              • 利用 display: flex 让内容在页面中垂直和水平居中。
                                                                                                                              • 容器样式:.container 类定义了内容容器的样式,包含背景颜色、内边距、圆角和阴影等。
                                                                                                                              • 输入框与按钮样式:对输入框和按钮的样式进行了设置,并且为按钮添加了悬停效果。
                                                                                                                              • 闪烁动画:blink 类和 @keyframes 规则实现了标题的闪烁效果。
                                                                                                                                (1)全局样式(body)
                                                                                                                                body {
                                                                                                                                    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                                                                                                                                    background-color: #f4f4f9;
                                                                                                                                    color: #333;
                                                                                                                                    margin: 0;
                                                                                                                                    padding: 20px;
                                                                                                                                    display: flex;
                                                                                                                                    justify-content: center;
                                                                                                                                    align-items: center;
                                                                                                                                    min-height: 100vh;
                                                                                                                                }
                                                                                                                                
                                                                                                                                • 字体与颜色:
                                                                                                                                  • font-family: 设置无衬线字体栈,确保不同设备上的可读性。
                                                                                                                                  • background-color: 背景色为浅灰色(#f4f4f9),与容器的白色形成对比。
                                                                                                                                  • color: 文本颜色为深灰色(#333),提升可读性。
                                                                                                                                  • 布局与间距:
                                                                                                                                    • margin: 0: 移除 body 的默认外边距,避免页面边缘留白。
                                                                                                                                    • padding: 20px: 添加内边距,防止内容紧贴浏览器边缘。
                                                                                                                                    • display: flex: 使用 Flexbox 布局,使子元素在容器内灵活排列。
                                                                                                                                    • justify-content: center 和 align-items: center: 使内容在水平和垂直方向上居中。
                                                                                                                                    • min-height: 100vh: 确保页面内容至少占据视口高度(vh = 视口高度的 1%),即使内容较少也能垂直居中。
                                                                                                                                      (2)容器样式(.container)
                                                                                                                                      .container {
                                                                                                                                          background-color: #fff;
                                                                                                                                          padding: 30px;
                                                                                                                                          border-radius: 10px;
                                                                                                                                          box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
                                                                                                                                          max-width: 600px;
                                                                                                                                          width: 100%;
                                                                                                                                      }
                                                                                                                                      
                                                                                                                                      • 外观设计:
                                                                                                                                        • background-color: #fff: 容器背景为白色,与页面背景形成对比。
                                                                                                                                        • padding: 30px: 容器内部内容与边框的间距,增强留白效果。
                                                                                                                                        • border-radius: 10px: 圆角设计,使界面更柔和。
                                                                                                                                        • box-shadow: 添加柔和的阴影(0 4px 10px 模糊半径 10px,透明度 0.1),提升层次感。
                                                                                                                                        • 响应式布局:
                                                                                                                                          • max-width: 600px: 限制容器最大宽度,避免在大屏幕上内容过于分散。
                                                                                                                                          • width: 100%: 确保容器在小屏幕下占满整个宽度,实现响应式效果。
                                                                                                                                            (3)标题样式(h1、h2)
                                                                                                                                            h1 {
                                                                                                                                                color: #007bff;
                                                                                                                                                text-align: center;
                                                                                                                                                margin-bottom: 20px;
                                                                                                                                                font-size: 2rem;
                                                                                                                                            }
                                                                                                                                            h2 {
                                                                                                                                                color: #007bff;
                                                                                                                                                margin-top: 20px;
                                                                                                                                                font-size: 1.5rem;
                                                                                                                                            }
                                                                                                                                            
                                                                                                                                            • 视觉统一:
                                                                                                                                              • color: #007bff: 标题颜色为蓝色(品牌色或强调色),突出重点。
                                                                                                                                              • text-align: center(仅 h1): 使主标题居中,与容器对齐。
                                                                                                                                              • 层级与间距:
                                                                                                                                                • font-size: h1 为 2rem(32px),h2 为 1.5rem(24px),形成视觉层级。
                                                                                                                                                • margin-bottom: 20px(h1)和 margin-top: 20px(h2): 控制标题与相邻元素的间距。
                                                                                                                                                  (4)输入框与按钮样式
                                                                                                                                                  4.1文本输入框(input [type="text"])

                                                                                                                                                  css

                                                                                                                                                  input[type="text"] {
                                                                                                                                                      width: calc(100% - 120px);
                                                                                                                                                      padding: 10px;
                                                                                                                                                      border: 1px solid #ddd;
                                                                                                                                                      border-radius: 5px;
                                                                                                                                                      font-size: 1rem;
                                                                                                                                                      margin-right: 10px;
                                                                                                                                                  }
                                                                                                                                                  
                                                                                                                                                  • 布局与尺寸:
                                                                                                                                                    • width: calc(100% - 120px): 输入框宽度占容器宽度减去 120px(为按钮留出空间)。
                                                                                                                                                    • padding: 10px: 输入框内部文本与边框的间距。
                                                                                                                                                    • 外观与交互:
                                                                                                                                                      • border: 1px solid #ddd: 浅灰色边框,区分输入区域。
                                                                                                                                                      • border-radius: 5px: 轻微圆角,保持界面统一。
                                                                                                                                                      • margin-right: 10px: 与右侧按钮的间距。
                                                                                                                                                        4.2按钮(input [type="button"])

                                                                                                                                                        css

                                                                                                                                                        input[type="button"] {
                                                                                                                                                            padding: 10px 20px;
                                                                                                                                                            background-color: #007bff;
                                                                                                                                                            color: #fff;
                                                                                                                                                            border: none;
                                                                                                                                                            border-radius: 5px;
                                                                                                                                                            cursor: pointer;
                                                                                                                                                            font-size: 1rem;
                                                                                                                                                            transition: background-color 0.3s ease;
                                                                                                                                                        }
                                                                                                                                                        input[type="button"]:hover {
                                                                                                                                                            background-color: #0056b3;
                                                                                                                                                        }
                                                                                                                                                        
                                                                                                                                                        • 基础样式:
                                                                                                                                                          • padding: 10px 20px: 按钮的内边距,控制大小。
                                                                                                                                                          • background-color: #007bff: 蓝色背景,与标题颜色呼应。
                                                                                                                                                          • color: #fff: 白色文本,高对比度。
                                                                                                                                                          • border: none: 移除默认边框,使按钮更简洁。
                                                                                                                                                          • cursor: pointer: 鼠标悬停时显示手型,提示可点击。
                                                                                                                                                          • 交互效果:
                                                                                                                                                            • transition: background-color 0.3s ease: 背景色渐变过渡,增强交互反馈。
                                                                                                                                                            • :hover 伪类:悬停时颜色加深(#0056b3),提供视觉反馈。
                                                                                                                                                              (5)单选框与标签样式

                                                                                                                                                              css

                                                                                                                                                              input[type="radio"] {
                                                                                                                                                                  margin-right: 10px;
                                                                                                                                                              }
                                                                                                                                                              label {
                                                                                                                                                                  margin-right: 20px;
                                                                                                                                                                  font-size: 1rem;
                                                                                                                                                              }
                                                                                                                                                              
                                                                                                                                                              • 单选框(input [type="radio"]):margin-right: 10px: 单选框与右侧标签的间距。
                                                                                                                                                              • 标签(label):
                                                                                                                                                                • margin-right: 20px: 标签之间的间距,区分不同选项。
                                                                                                                                                                • font-size: 1rem: 标签文本大小,与其他元素保持一致。
                                                                                                                                                                  (6)闪烁动画(blink 类)

                                                                                                                                                                  css

                                                                                                                                                                  .blink {
                                                                                                                                                                      animation: blink 1s infinite;
                                                                                                                                                                  }
                                                                                                                                                                  @keyframes blink {
                                                                                                                                                                      0% { opacity: 1; }
                                                                                                                                                                      50% { opacity: 0.5; }
                                                                                                                                                                      100% { opacity: 1; }
                                                                                                                                                                  }
                                                                                                                                                                  
                                                                                                                                                                  • 动画定义:
                                                                                                                                                                    • @keyframes blink: 定义名为 blink 的动画。
                                                                                                                                                                    • 0% 和 100% 关键帧:透明度为 1(完全不透明)。
                                                                                                                                                                    • 50% 关键帧:透明度为 0.5(半透明)。
                                                                                                                                                                    • 应用动画:
                                                                                                                                                                      • .blink 类:通过 animation 属性应用动画,1s 为周期,infinite 表示无限循环。
                                                                                                                                                                      • 效果:使标题(如

                                                                                                                                                                        )每隔 1 秒闪烁一次,突出显示。

                                                                                                                                                                        整体概述

                                                                                                                                                                        这段 CSS 通过合理的布局、颜色搭配和交互细节,实现了以下目标:

                                                                                                                                                                        1. 视觉统一:使用蓝色作为主色调,搭配白色背景和浅灰色边框,简洁专业。
                                                                                                                                                                        2. 响应式设计:容器最大宽度限制和 Flexbox 布局确保页面适配不同屏幕尺寸。
                                                                                                                                                                        3. 用户体验:按钮悬停效果、闪烁动画和适当的间距增强了交互反馈。
                                                                                                                                                                        4. 结构清晰:通过类名和选择器组织代码,便于维护和扩展。
                                                                                                                                                                        3. JavaScript 部分

                                                                                                                                                                        javascript

                                                                                                                                                                        function getinfof() {
                                                                                                                                                                            var v = document.getElementsByName("usrname");
                                                                                                                                                                            console.log(v[0].value);
                                                                                                                                                                            var xhr = new XMLHttpRequest();
                                                                                                                                                                            var url = "";
                                                                                                                                                                            xhr.open("post", url, true);
                                                                                                                                                                            xhr.send("mange_get");
                                                                                                                                                                            xhr.onreadystatechange = function () {
                                                                                                                                                                                if (xhr.readyState == 4 && xhr.status == 200) {
                                                                                                                                                                                    var t = xhr.responseText;
                                                                                                                                                                                    var data = t.split('#');
                                                                                                                                                                                    v[0].value = data[0];
                                                                                                                                                                                }
                                                                                                                                                                            }
                                                                                                                                                                        }
                                                                                                                                                                        function getinfos() {
                                                                                                                                                                            var v = document.getElementsByName("usrname");
                                                                                                                                                                            console.log(v[1].value);
                                                                                                                                                                            var xhr = new XMLHttpRequest();
                                                                                                                                                                            var url = "";
                                                                                                                                                                            xhr.open("post", url, true);
                                                                                                                                                                            xhr.send("mange_get");
                                                                                                                                                                            xhr.onreadystatechange = function () {
                                                                                                                                                                                if (xhr.readyState == 4 && xhr.status == 200) {
                                                                                                                                                                                    var t = xhr.responseText;
                                                                                                                                                                                    var data = t.split('#');
                                                                                                                                                                                    v[1].value = data[1] + data[2] + data[3];
                                                                                                                                                                                }
                                                                                                                                                                            }
                                                                                                                                                                        }
                                                                                                                                                                        function fun1() {
                                                                                                                                                                            var xhr = new XMLHttpRequest();
                                                                                                                                                                            var url = "";
                                                                                                                                                                            xhr.open("post", url, true);
                                                                                                                                                                            xhr.send("mange_set=1 1"); // 蜂鸣器开
                                                                                                                                                                        }
                                                                                                                                                                        function fun2() {
                                                                                                                                                                            var xhr = new XMLHttpRequest();
                                                                                                                                                                            var url = "";
                                                                                                                                                                            xhr.open("post", url, true);
                                                                                                                                                                            xhr.send("mange_set=1 0"); // 蜂鸣器关
                                                                                                                                                                        }
                                                                                                                                                                        function fun3() {
                                                                                                                                                                            var xhr = new XMLHttpRequest();
                                                                                                                                                                            var url = "";
                                                                                                                                                                            xhr.open("post", url, true);
                                                                                                                                                                            xhr.send("mange_set=0 1"); // LED开
                                                                                                                                                                        }
                                                                                                                                                                        function fun4() {
                                                                                                                                                                            var xhr = new XMLHttpRequest();
                                                                                                                                                                            var url = "";
                                                                                                                                                                            xhr.open("post", url, true);
                                                                                                                                                                            xhr.send("mange_set=0 0"); // LED关
                                                                                                                                                                        }
                                                                                                                                                                        
                                                                                                                                                                        • 数据获取函数:getinfof() 和 getinfos() 函数借助 XMLHttpRequest 对象向服务器发送 POST 请求,以获取光照数据和加速度数据。当请求成功时,将服务器返回的数据解析并显示在输入框中。
                                                                                                                                                                        • 设备控制函数:fun1()、fun2()、fun3() 和 fun4() 函数同样使用 XMLHttpRequest 对象向服务器发送 POST 请求,用于控制蜂鸣器和 LED 灯的开关。

                                                                                                                                                                          整体功能概述

                                                                                                                                                                          这段 JavaScript 代码通过 XMLHttpRequest(XHR)实现了以下功能:

                                                                                                                                                                          1. 数据获取:从服务器获取光照数据和加速度数据,并显示在页面上。
                                                                                                                                                                          2. 设备控制:通过发送指令控制蜂鸣器和 LED 灯的开关。
                                                                                                                                                                          (1)数据获取函数(getinfof() 和 getinfos())

                                                                                                                                                                          javascript

                                                                                                                                                                          function getinfof() {
                                                                                                                                                                              // 获取名为 "usrname" 的第一个输入框
                                                                                                                                                                              var v = document.getElementsByName("usrname");
                                                                                                                                                                              console.log(v[0].value); // 打印当前值(调试用)
                                                                                                                                                                              // 创建 XHR 对象
                                                                                                                                                                              var xhr = new XMLHttpRequest();
                                                                                                                                                                              var url = ""; // 需替换为实际 API 地址
                                                                                                                                                                              xhr.open("post", url, true); // POST 请求,异步模式
                                                                                                                                                                              // 发送请求参数
                                                                                                                                                                              xhr.send("mange_get");
                                                                                                                                                                              // 监听请求状态变化
                                                                                                                                                                              xhr.onreadystatechange = function() {
                                                                                                                                                                                  if (xhr.readyState === 4 && xhr.status === 200) {
                                                                                                                                                                                      // 解析响应数据
                                                                                                                                                                                      var t = xhr.responseText;
                                                                                                                                                                                      var data = t.split('#'); // 假设数据以 # 分隔
                                                                                                                                                                                      v[0].value = data[0]; // 更新第一个输入框的值
                                                                                                                                                                                  }
                                                                                                                                                                              };
                                                                                                                                                                          }
                                                                                                                                                                          // getinfos() 逻辑类似,但操作第二个输入框(v[1])
                                                                                                                                                                          
                                                                                                                                                                          1. DOM 操作:
                                                                                                                                                                            1. getElementsByName("usrname"):获取页面中所有 name="usrname" 的元素(两个输入框)。
                                                                                                                                                                            2. 通过索引 v[0] 和 v[1] 分别操作光照和加速度数据的输入框。
                                                                                                                                                                          2. XHR 请求:
                                                                                                                                                                            1. 请求方法:POST(用于发送数据到服务器)。
                                                                                                                                                                            2. 请求参数:mange_get(服务器根据此参数识别为 “获取数据” 请求)。
                                                                                                                                                                            3. 响应处理:

                                                                                                                                                                          当 readyState === 4(请求完成)且 status === 200(成功)时,解析响应数据。

                                                                                                                                                                          假设响应数据格式为 data0#data1#data2#data3,用 split('#') 分割后更新输入框。

                                                                                                                                                                          (2)设备控制函数(fun1() 到 fun4())

                                                                                                                                                                          javascript

                                                                                                                                                                          function fun1() {
                                                                                                                                                                              var xhr = new XMLHttpRequest();
                                                                                                                                                                              var url = "";
                                                                                                                                                                              xhr.open("post", url, true);
                                                                                                                                                                              xhr.send("mange_set=1 1"); // 蜂鸣器开
                                                                                                                                                                          }
                                                                                                                                                                          // fun2() 到 fun4() 类似,仅参数不同:
                                                                                                                                                                          // fun2(): "mange_set=1 0"(蜂鸣器关)
                                                                                                                                                                          // fun3(): "mange_set=0 1"(LED 开)
                                                                                                                                                                          // fun4(): "mange_set=0 0"(LED 关)
                                                                                                                                                                          
                                                                                                                                                                          1. 指令格式:
                                                                                                                                                                            1. mange_set=设备类型 状态:设备类型:1 代表蜂鸣器,0 代表 LED。
                                                                                                                                                                              1. 状态:1 代表开,0 代表关。
                                                                                                                                                                          2. XHR 请求:
                                                                                                                                                                            1. 通过 POST 发送指令到服务器,服务器根据 mange_set 参数执行相应操作。

                                                                                                                                                                          七.代码原理图与项目模块联系(录屏重点讲解部分)

                                                                                                                                                                          一、WebServer(thttpd 代码核心)与网页的 HTTP 交互实现

                                                                                                                                                                          • 代码功能:在 parse_and_process 函数中,通过检测请求内容(如 strstr(input, "username=") 识别登录请求、strstr(input, "data1=") 识别数据操作请求),调用 handle_login(处理登录逻辑,验证用户名密码并返回跳转脚本)、handle_add(解析数据参数并执行求和计算)等函数,最终通过 send 向网页返回响应(包括登录结果、数据处理结果或默认 JSON 数据)。
                                                                                                                                                                          • 与原理图关联:

                                                                                                                                                                            完全对应原理图中 “网页 → WebServer” 的 HTTP 通信链路。WebServer 作为 HTTP 服务核心,接收网页的登录、数据操作等请求,执行解析与业务处理后,向网页反馈结果,实现网页与服务器的双向交互,体现 WebServer 在原理图中 “HTTP 通信枢纽” 的定位。

                                                                                                                                                                            二、WebServer 与 Modbus 采集控制程序的进程间通信实现

                                                                                                                                                                            • 代码功能:
                                                                                                                                                                              • 共享内存:handle_recv 函数通过 shmget、shmat 操作共享内存,读取 Modbus 采集控制程序存入的设备数据(如传感器数值),并通过 WebServer 返回给网页。
                                                                                                                                                                              • 消息队列:handle_send 函数利用 msgget、msgsnd 创建消息队列,将网页的控制命令(如 mange_set 携带的参数)封装成消息发送给 Modbus 采集控制程序。
                                                                                                                                                                              • 与原理图关联:
                                                                                                                                                                                • 精准对应原理图中 “WebServer ↔ Modbus 采集控制程序” 的进程间通信。WebServer 通过共享内存获取 Modbus 设备采集的数据(如 read_data 线程采集的光线、加速度数据),供网页展示;通过消息队列接收网页命令(如控制 LED、蜂鸣器的指令),传递给 Modbus 采集控制程序,驱动 write_command 线程执行 Modbus 设备控制操作,实现原理图中 “通过进程间通信协调两者功能” 的设计。

                                                                                                                                                                                  三、Modbus 采集控制程序与 Modbus 设备的交互实现

                                                                                                                                                                                  • 代码功能:read_data 线程通过 modbus_read_registers 读取 Modbus 设备寄存器数据(光线、加速度 XYZ),并存入共享内存。
                                                                                                                                                                                    • write_command 线程通过消息队列接收命令,利用 modbus_write_bit 向 Modbus 设备写入控制指令(如开灯、关蜂鸣器)。
                                                                                                                                                                                    • 与原理图关联:
                                                                                                                                                                                    • 直接对应原理图中 “Modbus 采集控制程序 → Modbus 设备” 的 Modbus/TCP 通信链路。采集程序作为中间执行者,一方面采集 Modbus 设备数据,通过共享内存供 WebServer 转发给网页;另一方面接收 WebServer 通过消息队列传递的命令,控制 Modbus 设备,完成原理图中 “设备数据采集” 和 “设备控制” 的核心功能。

                                                                                                                                                                                      四、整体代码对原理图的完整复现

                                                                                                                                                                                      • 交互链路复现:
                                                                                                                                                                                      • 代码通过 “WebServer 处理 HTTP 请求 → 进程间通信(共享内存 / 消息队列)→ Modbus 采集控制程序操作 Modbus 设备” 的流程,完整复现原理图中 “网页 —WebServer—Modbus 采集控制程序 —Modbus 设备” 的交互链路。
                                                                                                                                                                                      • 模块功能映射:

                                                                                                                                                                                        WebServer 代码模块实现原理图中 WebServer 的 HTTP 通信与请求处理功能。

                                                                                                                                                                                          • 共享内存 / 消息队列代码实现原理图中进程间通信功能。
                                                                                                                                                                                          • Modbus 采集控制程序代码(read_data、write_command 线程)实现原理图中 Modbus 设备的数据采集与控制功能。

                                                                                                                                                                                            三者协同,从代码层面完整落地原理图的系统设计,实现网页对 Modbus 设备的间接控制与数据获取。

                                                                                                                                                                                            1. 项目测试流程解析 (主要是应对录屏/答辩)

                                                                                                                                                                                            (1)项目文档层次

                                                                                                                                                                                            (2)进入modbus thttpd 目录

                                                                                                                                                                                            (3)make

                                                                                                                                                                                            (4)windows IP

                                                                                                                                                                                            (5)编译 modbus 线程

                                                                                                                                                                                            (6)编译 thttpd

                                                                                                                                                                                            (7)登录 项目界面(虚拟网址)

                                                                                                                                                                                            (8)打开slave 进行编辑

                                                                                                                                                                                            (9)传递信息成功

                                                                                                                                                                                            (10)接着设置slave

                                                                                                                                                                                            (11)传递灯光信号成功

                                                                                                                                                                                            (12)项目最终成功获取灯罩数据与加速度数据

                                                                                                                                                                                            (13)项目最终成功结果

                                                                                                                                                                                            1. 项目扩展方向思路

                                                                                                                                                                                            (1)数据库模块扩展(SQLite3 实现)

                                                                                                                                                                                            1. 数据库初始化代码(新增 db_util.c)

                                                                                                                                                                                            #include 
                                                                                                                                                                                            #include 
                                                                                                                                                                                            #include 
                                                                                                                                                                                            // 定义数据库文件路径
                                                                                                                                                                                            #define DB_PATH "sensor_data.db"
                                                                                                                                                                                            // 创建传感器数据表
                                                                                                                                                                                            int init_sensor_table() {
                                                                                                                                                                                                sqlite3 *db;
                                                                                                                                                                                                char *err_msg = 0;
                                                                                                                                                                                                int rc = sqlite3_open(DB_PATH, &db);
                                                                                                                                                                                                if (rc != SQLITE_OK) {
                                                                                                                                                                                                    fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
                                                                                                                                                                                                    sqlite3_close(db);
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                const char *sql = "CREATE TABLE IF NOT EXISTS sensor_data ("
                                                                                                                                                                                                                  "id INTEGER PRIMARY KEY AUTOINCREMENT,"
                                                                                                                                                                                                                  "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,"
                                                                                                                                                                                                                  "light INT NOT NULL,"
                                                                                                                                                                                                                  "accel_x INT NOT NULL,"
                                                                                                                                                                                                                  "accel_y INT NOT NULL,"
                                                                                                                                                                                                                  "accel_z INT NOT NULL);";
                                                                                                                                                                                                rc = sqlite3_exec(db, sql, 0, 0, &err_msg);
                                                                                                                                                                                                if (rc != SQLITE_OK) {
                                                                                                                                                                                                    fprintf(stderr, "SQL error: %s\n", err_msg);
                                                                                                                                                                                                    sqlite3_free(err_msg);
                                                                                                                                                                                                    sqlite3_close(db);
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                sqlite3_close(db);
                                                                                                                                                                                                return 0;
                                                                                                                                                                                            }
                                                                                                                                                                                            // 插入传感器数据
                                                                                                                                                                                            int insert_sensor_data(int light, int x, int y, int z) {
                                                                                                                                                                                                sqlite3 *db;
                                                                                                                                                                                                sqlite3_stmt *stmt;
                                                                                                                                                                                                const char *sql = "INSERT INTO sensor_data (light, accel_x, accel_y, accel_z) "
                                                                                                                                                                                                                  "VALUES (?, ?, ?, ?);";
                                                                                                                                                                                                if (sqlite3_open(DB_PATH, &db) != SQLITE_OK) {
                                                                                                                                                                                                    fprintf(stderr, "Database open error\n");
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
                                                                                                                                                                                                    fprintf(stderr, "Prepare statement failed\n");
                                                                                                                                                                                                    sqlite3_close(db);
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                sqlite3_bind_int(stmt, 1, light);
                                                                                                                                                                                                sqlite3_bind_int(stmt, 2, x);
                                                                                                                                                                                                sqlite3_bind_int(stmt, 3, y);
                                                                                                                                                                                                sqlite3_bind_int(stmt, 4, z);
                                                                                                                                                                                                if (sqlite3_step(stmt) != SQLITE_DONE) {
                                                                                                                                                                                                    fprintf(stderr, "Insert failed\n");
                                                                                                                                                                                                    sqlite3_finalize(stmt);
                                                                                                                                                                                                    sqlite3_close(db);
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                sqlite3_finalize(stmt);
                                                                                                                                                                                                sqlite3_close(db);
                                                                                                                                                                                                return 0;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            2. 修改 read_data 线程(modbus.c)

                                                                                                                                                                                            void *read_data(void *arg) {
                                                                                                                                                                                                // ...原有共享内存代码...
                                                                                                                                                                                                while (1) {
                                                                                                                                                                                                    // 原有数据读取逻辑
                                                                                                                                                                                                    int rc = modbus_read_registers(ctx, 0, 4, reg);
                                                                                                                                                                                                    if (rc != 4) { /* 错误处理 */ }
                                                                                                                                                                                                    // 新增数据库插入
                                                                                                                                                                                                    if (insert_sensor_data(reg[0], reg[1], reg[2], reg[3]) != 0) {
                                                                                                                                                                                                        fprintf(stderr, "Failed to save sensor data to DB\n");
                                                                                                                                                                                                    }
                                                                                                                                                                                                    // 原有共享内存写入逻辑
                                                                                                                                                                                                    sprintf(p, "%d#%d#%d#%d\n", reg[0], reg[1], reg[2], reg[3]);
                                                                                                                                                                                                }
                                                                                                                                                                                                return NULL;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            (2)WebServer 新增历史查询接口

                                                                                                                                                                                            1. 新增 handle_history 函数(custom_handle.c)

                                                                                                                                                                                            // 查询历史数据
                                                                                                                                                                                            static int handle_history(int sock, const char *start, const char *end) {
                                                                                                                                                                                                sqlite3 *db;
                                                                                                                                                                                                sqlite3_stmt *stmt;
                                                                                                                                                                                                char sql[512];
                                                                                                                                                                                                char json[4096] = "{ \"data\": [";
                                                                                                                                                                                                snprintf(sql, sizeof(sql), 
                                                                                                                                                                                                         "SELECT * FROM sensor_data WHERE timestamp BETWEEN '%s' AND '%s'",
                                                                                                                                                                                                         start, end);
                                                                                                                                                                                                if (sqlite3_open(DB_PATH, &db) != SQLITE_OK) {
                                                                                                                                                                                                    send_error_response(sock, 500, "Database error");
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
                                                                                                                                                                                                    send_error_response(sock, 500, "Query failed");
                                                                                                                                                                                                    sqlite3_close(db);
                                                                                                                                                                                                    return -1;
                                                                                                                                                                                                }
                                                                                                                                                                                                int first = 1;
                                                                                                                                                                                                while (sqlite3_step(stmt) == SQLITE_ROW) {
                                                                                                                                                                                                    if (!first) strcat(json, ",");
                                                                                                                                                                                                    first = 0;
                                                                                                                                                                                                    snprintf(json + strlen(json), sizeof(json) - strlen(json),
                                                                                                                                                                                                            "{\"time\":\"%s\",\"light\":%d,\"x\":%d,\"y\":%d,\"z\":%d}",
                                                                                                                                                                                                            sqlite3_column_text(stmt, 1),
                                                                                                                                                                                                            sqlite3_column_int(stmt, 2),
                                                                                                                                                                                                            sqlite3_column_int(stmt, 3),
                                                                                                                                                                                                            sqlite3_column_int(stmt, 4),
                                                                                                                                                                                                            sqlite3_column_int(stmt, 5));
                                                                                                                                                                                                }
                                                                                                                                                                                                strcat(json, "]}");
                                                                                                                                                                                                // 发送 JSON 响应
                                                                                                                                                                                                send_json_response(sock, json);
                                                                                                                                                                                                
                                                                                                                                                                                                sqlite3_finalize(stmt);
                                                                                                                                                                                                sqlite3_close(db);
                                                                                                                                                                                                return 0;
                                                                                                                                                                                            }
                                                                                                                                                                                            
                                                                                                                                                                                            2. 修改 parse_and_process 函数

                                                                                                                                                                                            int parse_and_process(int sock, const char *query, const char *input) {
                                                                                                                                                                                                // 原有逻辑...
                                                                                                                                                                                                // 新增历史查询处理
                                                                                                                                                                                                else if (strstr(input, "action=history")) {
                                                                                                                                                                                                    char start[64], end[64];
                                                                                                                                                                                                    sscanf(input, "start=%[^&]&end=%s", start, end);
                                                                                                                                                                                                    return handle_history(sock, start, end);
                                                                                                                                                                                                }
                                                                                                                                                                                                // 其他处理...
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            (3)前端页面扩展(example.html)

                                                                                                                                                                                            1. 新增历史查询界面

                                                                                                                                                                                                

                                                                                                                                                                                            历史数据查询

                                                                                                                                                                                            查询
                                                                                                                                                                                            1. 新增 JavaScript 查询逻辑

                                                                                                                                                                                            function queryHistory() {
                                                                                                                                                                                                const start = document.getElementById('startTime').value;
                                                                                                                                                                                                const end = document.getElementById('endTime').value;
                                                                                                                                                                                                const xhr = new XMLHttpRequest();
                                                                                                                                                                                                xhr.open('POST', '/api', true);
                                                                                                                                                                                                xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                                                                                                                                                                                                xhr.send(`action=history&start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`);
                                                                                                                                                                                                xhr.onreadystatechange = function() {
                                                                                                                                                                                                    if (xhr.readyState === 4 && xhr.status === 200) {
                                                                                                                                                                                                        const data = JSON.parse(xhr.responseText).data;
                                                                                                                                                                                                        let html = '
                                                                                                                                                                                            '; data.forEach(item => { html += ``; }); html += '
                                                                                                                                                                                            时间光照XYZ
                                                                                                                                                                                            ${item.time}${item.light} ${item.x}${item.y}${item.z}
                                                                                                                                                                                            '; document.getElementById('historyResult').innerHTML = html; } }; }
                                                                                                                                                                                            3. 新增 CSS 样式

                                                                                                                                                                                            .history-section {
                                                                                                                                                                                                margin-top: 30px;
                                                                                                                                                                                                padding: 20px;
                                                                                                                                                                                                border-top: 1px solid #ddd;
                                                                                                                                                                                            }
                                                                                                                                                                                            .history-result {
                                                                                                                                                                                                margin-top: 15px;
                                                                                                                                                                                                max-height: 400px;
                                                                                                                                                                                                overflow-y: auto;
                                                                                                                                                                                            }
                                                                                                                                                                                            table {
                                                                                                                                                                                                width: 100%;
                                                                                                                                                                                                border-collapse: collapse;
                                                                                                                                                                                            }
                                                                                                                                                                                            th, td {
                                                                                                                                                                                                padding: 8px;
                                                                                                                                                                                                border: 1px solid #ddd;
                                                                                                                                                                                                text-align: center;
                                                                                                                                                                                            }
                                                                                                                                                                                            th {
                                                                                                                                                                                                background-color: #f8f9fa;
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            (4)编译与部署调整

                                                                                                                                                                                            1.修改 Makefile

                                                                                                                                                                                            添加 SQLite3 依赖:

                                                                                                                                                                                            httpd.out: main.c thttpd.c custom_handle.c db_util.c
                                                                                                                                                                                                gcc -o $@ $^ -lpthread -lsqlite3
                                                                                                                                                                                            
                                                                                                                                                                                            2.初始化数据库

                                                                                                                                                                                            在 main.c 的初始化阶段调用:

                                                                                                                                                                                            int main() {
                                                                                                                                                                                                init_sensor_table(); // 新增
                                                                                                                                                                                                // 原有代码...
                                                                                                                                                                                            }
                                                                                                                                                                                            

                                                                                                                                                                                            (5)扩展方向优化建议

                                                                                                                                                                                            1. 数据分页查询

                                                                                                                                                                                            在 handle_history 中增加 LIMIT 和 OFFSET 参数,支持大数据量分页。

                                                                                                                                                                                            1. 数据可视化
                                                                                                                                                                                            2. 用户权限在前端使用 ECharts 绘制光照和加速度的历史趋势图。
                                                                                                                                                                                            3. 异常处理 在数据库中增加用户表,修改登录逻辑实现多用户权限控制。

                                                                                                                                                                                            在数据库操作中添加事务机制,确保数据一致性。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码