树莓派 AI 模块测试 温度时间测试

Pyhton 程序获取UTC时间

复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import os
import socket
import fcntl
import struct
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

PORT = 12345


def get_iface_ip(ifname):
    s = None
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        return socket.inet_ntoa(
            fcntl.ioctl(
                s.fileno(),
                0x8915,
                struct.pack("256s", ifname[:15].encode("utf-8"))
            )[20:24]
        )
    except Exception:
        return None
    finally:
        if s:
            s.close()


def print_urls():
    found = False
    seen = set()

    try:
        for _, ifname in socket.if_nameindex():
            if ifname == "lo":
                continue

            ip = get_iface_ip(ifname)
            if not ip or ip in seen:
                continue

            seen.add(ip)
            print(f"{ifname}: http://{ip}:{PORT}")
            found = True
    except Exception:
        pass

    if not found:
        print(f"localhost: http://127.0.0.1:{PORT}")


def get_time_text():
    try:
        return os.popen("date").read().strip() or "--"
    except Exception:
        return "--"


def build_html():
    now = get_time_text()

    html_tpl = """<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="refresh" content="1">
<title>当前时间</title>
<style>
body {
    margin: 0;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: #0d1117;
    color: #e6edf3;
    font-family: Arial, Consolas, monospace;
}
.label {
    font-size: 32px;
    font-weight: 700;
    margin-bottom: 12px;
}
.value {
    font-size: 48px;
    color: #79c0ff;
    font-weight: 700;
}
</style>
</head>
<body>
    <div class="label">当前时间</div>
    <div class="value">__TIME__</div>
</body>
</html>"""

    return html_tpl.replace("__TIME__", now)


class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path != "/":
            self.send_error(404)
            return

        data = build_html().encode("utf-8")
        self.send_response(200)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(data)))
        self.end_headers()
        self.wfile.write(data)

    def log_message(self, format, *args):
        pass


if __name__ == "__main__":
    print_urls()
    ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever()

h@raspberrypi:~/Music/SSD $ python3 date.py

eth0: http://192.168.9.243:12345


代码逐行表格解析

行号 代码 解释
1 #!/usr/bin/env python3 Linux 脚本头,表示直接运行这个文件时,用 python3 来解释。
2 # -*- coding: utf-8 -*- 声明源码文件使用 UTF-8 编码,方便写中文。
3 空行 只是分隔,没有语法作用。
4 import os 导入 os 模块,这里用来执行 date 命令。
5 import socket 导入 socket 模块,这里用来获取网卡名、IP、启动 HTTP 服务。
6 import fcntl 导入 fcntl 模块,这里用 ioctl 获取指定网卡的 IP 地址。
7 import struct 导入 struct 模块,这里把网卡名打包成二进制数据给 ioctl
8 from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer 从标准库里导入 HTTP 请求处理类和多线程 HTTP 服务器类。
9 空行 分隔。
10 PORT = 1234 定义端口号常量,后面服务监听 1234 端口。
11 空行 分隔。
12 空行 分隔。
13 def get_iface_ip(ifname): 定义函数:根据网卡名获取它的 IPv4 地址。
14 s = None 先定义变量 s,后面在 finally 里方便安全关闭。
15 try: 开始异常保护代码块。
16 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 创建一个 IPv4 的 UDP socket,仅用于查询网卡信息,不是用来通信。
17 return socket.inet_ntoa( 把二进制格式 IP 地址转成字符串格式。
18 fcntl.ioctl( 调用底层 ioctl,向系统查询网卡地址。
19 s.fileno(), 获取 socket 的文件描述符。
20 0x8915, 这是 Linux 的 SIOCGIFADDR 命令号,意思是"获取网卡地址"。
21 struct.pack("256s", ifname[:15].encode("utf-8")) 把网卡名编码成固定长度的二进制结构,传给 ioctl。只取前 15 个字符。
22 )[20:24] 从系统返回的二进制结果里截出 IPv4 地址那 4 个字节。
23 ) 结束 inet_ntoa(...)
24 except Exception: 如果前面任何一步失败,就进这里。
25 return None 获取失败时返回 None
26 finally: 无论成功还是失败,最后都执行这里。
27 if s: 如果 socket 已经创建成功。
28 s.close() 关闭 socket,释放资源。
29 空行 分隔。
30 空行 分隔。
31 def print_urls(): 定义函数:启动时打印访问地址。
32 found = False 标记是否找到有效 IP。
33 seen = set() 建立一个集合,用来给 IP 去重。
34 空行 分隔。
35 try: 开始异常保护。
36 for _, ifname in socket.if_nameindex(): 遍历系统所有网卡。返回值是 (编号, 网卡名),这里只取网卡名。
37 if ifname == "lo": 如果当前网卡是回环网卡 lo
38 continue 跳过本轮循环,继续看下一块网卡。
39 空行 分隔。
40 ip = get_iface_ip(ifname) 调用前面的函数,读取当前网卡的 IP。
41 if not ip or ip in seen: 如果没读到 IP,或者这个 IP 已经打印过了。
42 continue 跳过当前网卡。
43 空行 分隔。
44 seen.add(ip) 把这个 IP 放入去重集合。
45 print(f"{ifname}: http://{ip}:{PORT}") 在终端打印访问地址,比如 eth0: http://192.168.1.20:1234
46 found = True 说明已经找到至少一个可用地址。
47 except Exception: 如果遍历网卡时报错。
48 pass 忽略异常,不让程序退出。
49 空行 分隔。
50 if not found: 如果没有找到任何有效网卡地址。
51 print(f"localhost: http://127.0.0.1:{PORT}") 打印回环地址作为兜底。
52 空行 分隔。
53 空行 分隔。
54 def get_time_text(): 定义函数:读取当前系统时间。
55 try: 开始异常保护。
56 return os.popen("date").read().strip() or "--" 执行 date 命令,读结果,去掉首尾空白;如果结果为空,就返回 "--"
57 except Exception: 如果执行命令失败。
58 return "--" 失败时返回占位符。
59 空行 分隔。
60 空行 分隔。
61 def build_html(): 定义函数:拼接整个 HTML 页面。
62 now = get_time_text() 先读取当前时间。
63 空行 分隔。
64 html_tpl = """<!doctype html> 定义一个普通的多行 HTML 模板字符串,不是 f-string。
65 <html lang="zh-CN"> HTML 根节点,语言设为中文。
66 <head> HTML 头部开始。
67 <meta charset="utf-8"> 指定网页字符编码为 UTF-8。
68 <meta name="viewport" content="width=device-width,initial-scale=1"> 移动端适配,让页面宽度等于设备宽度。
69 <meta http-equiv="refresh" content="1"> 页面每 1 秒自动刷新一次。
70 <title>当前时间</title> 浏览器标签页显示的标题。不是页面正文内容。
71 <style> CSS 样式开始。
72 body { 开始写 body 样式。注意这里是普通字符串,所以可以直接写单个 {}
73 margin: 0; 页面外边距为 0。
74 height: 100vh; 页面高度等于整个浏览器视口高度。
75 display: flex; 使用 flex 布局。
76 flex-direction: column; 纵向排列内容。
77 align-items: center; 横向居中。
78 justify-content: center; 纵向居中。
79 background: #0d1117; 背景色。
80 color: #e6edf3; 默认文字颜色。
81 font-family: Arial, Consolas, monospace; 页面默认字体。
82 } body 样式结束。
83 .label { 开始写标题文字样式。
84 font-size: 32px; 标题字号 32 像素。
85 font-weight: 700; 标题加粗。
86 margin-bottom: 12px; 标题和下面时间值之间留 12 像素间距。
87 } .label 样式结束。
88 .value { 开始写时间数值样式。
89 font-size: 48px; 时间值字号 48 像素。
90 color: #79c0ff; 时间值颜色蓝色。
91 font-weight: 700; 时间值加粗。
92 } .value 样式结束。
93 </style> CSS 样式结束。
94 </head> HTML 头部结束。
95 <body> HTML 正文开始。
96 <div class="label">当前时间</div> 页面正文里显示标题"当前时间"。
97 <div class="value">__TIME__</div> 页面正文里显示占位符 __TIME__,后面会被替换成真实时间。
98 </body> HTML 正文结束。
99 </html>""" HTML 文档结束,同时结束这个多行字符串。
100 空行 分隔。
101 return html_tpl.replace("__TIME__", now) 把模板里的 __TIME__ 替换成真实时间后返回。
102 空行 分隔。
103 空行 分隔。
104 class Handler(BaseHTTPRequestHandler): 定义 HTTP 请求处理类,继承标准库的请求处理基类。
105 def do_GET(self): 浏览器发 GET 请求时,服务器会自动调用这个方法。
106 if self.path != "/": 如果访问路径不是根路径 /
107 self.send_error(404) 返回 404 错误。
108 return 结束这次请求处理。
109 空行 分隔。
110 data = build_html().encode("utf-8") 生成 HTML 字符串,并编码成 UTF-8 字节。HTTP 发送的是字节,不是普通字符串。
111 self.send_response(200) 返回 HTTP 状态码 200,表示成功。
112 self.send_header("Content-Type", "text/html; charset=utf-8") 告诉浏览器返回内容类型是 UTF-8 HTML。
113 self.send_header("Content-Length", str(len(data))) 告诉浏览器正文有多少字节。
114 self.end_headers() 结束 HTTP 头部。
115 self.wfile.write(data) 把 HTML 字节数据写给浏览器。
116 空行 分隔。
117 def log_message(self, format, *args): 重写默认日志函数。
118 pass 什么都不做,这样访问网页时终端不会刷日志。
119 空行 分隔。
120 空行 分隔。
121 if __name__ == "__main__": 只有直接运行这个脚本时,下面两行才会执行。
122 print_urls() 启动服务前先打印访问地址。
123 ThreadingHTTPServer(("0.0.0.0", PORT), Handler).serve_forever() 启动多线程 HTTP 服务器,监听所有网卡地址,永久运行。

这版和你之前报错版的核心区别

项目 之前报错版 现在这版
HTML 写法 return f"""...""" 普通三引号字符串
CSS 大括号 必须写成 {``{ }} 直接写 { }
插值方式 {变量} .replace("__TIME__", now)
出错点 容易被单个 } 搞炸 基本不会有这个问题

你现在最常需要改的地方

你要改什么 改哪一行
端口号 第 10 行
自动刷新秒数 第 69 行
浏览器标签标题 第 70 行
页面正文标题"当前时间" 第 96 行
时间占位符名字 第 97、101 行,要对应一致
页面背景色 第 79 行
标题字号 第 84 行
时间值字号 第 89 行
终端打印 URL 逻辑 第 31 到 51 行

程序执行顺序

顺序 发生什么
1 程序启动,进入第 121 行
2 调用 print_urls() 打印访问地址
3 启动 HTTP 服务,开始监听 1234 端口
4 浏览器访问 / 时,触发 do_GET()
5 build_html() 被调用
6 build_html() 里调用 get_time_text() 取当前时间
7 时间替换进 HTML 模板
8 HTML 编码成字节并返回给浏览器
9 浏览器每 1 秒刷新一次,再重复步骤 4 到 8

C 语言显示树莓派UTC时间 自带 CPU 温度

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 1234
#define BUF_SIZE 8192

void get_temp(char *out, size_t size) {
    FILE *fp = popen("vcgencmd measure_temp 2>/dev/null", "r");
    if (!fp) {
        snprintf(out, size, "--");
        return;
    }

    char buf[128] = {0};
    if (!fgets(buf, sizeof(buf), fp)) {
        pclose(fp);
        snprintf(out, size, "--");
        return;
    }
    pclose(fp);

    buf[strcspn(buf, "\r\n")] = 0;
    if (strncmp(buf, "temp=", 5) == 0) {
        snprintf(out, size, "%s", buf + 5);
    } else {
        snprintf(out, size, "--");
    }
}

void get_time_text(char *out, size_t size) {
    FILE *fp = popen("date", "r");
    if (!fp) {
        snprintf(out, size, "--");
        return;
    }

    if (!fgets(out, size, fp)) {
        pclose(fp);
        snprintf(out, size, "--");
        return;
    }
    pclose(fp);

    out[strcspn(out, "\r\n")] = 0;
}

void print_urls() {
    struct ifaddrs *ifaddr, *ifa;
    char ip[INET_ADDRSTRLEN];
    int found = 0;

    if (getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        return;
    }

    for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
        if (!ifa->ifa_addr) continue;
        if (ifa->ifa_addr->sa_family != AF_INET) continue;
        if (strcmp(ifa->ifa_name, "lo") == 0) continue;

        struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr;
        if (!inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip))) continue;

        printf("%s: http://%s:%d\n", ifa->ifa_name, ip, PORT);
        found = 1;
    }

    if (!found) {
        printf("localhost: http://127.0.0.1:%d\n", PORT);
    }

    freeifaddrs(ifaddr);
}

void send_response(int client_fd) {
    char temp[64];
    char now[128];
    char body[4096];
    char response[BUF_SIZE];

    get_temp(temp, sizeof(temp));
    get_time_text(now, sizeof(now));

    snprintf(body, sizeof(body),
        "<!doctype html>"
        "<html lang=\"zh-CN\">"
        "<head>"
        "<meta charset=\"utf-8\">"
        "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">"
        "<meta http-equiv=\"refresh\" content=\"1\">"
        "<title>温度和时间</title>"
        "<style>"
        "body{"
        "margin:0;"
        "height:100vh;"
        "display:flex;"
        "flex-direction:column;"
        "align-items:center;"
        "justify-content:center;"
        "gap:28px;"
        "background:#0d1117;"
        "color:#e6edf3;"
        "font-family:Arial,Consolas,monospace;"
        "}"
        ".label{"
        "font-size:32px;"
        "font-weight:700;"
        "margin-bottom:8px;"
        "}"
        ".value{"
        "font-size:64px;"
        "color:#79c0ff;"
        "font-weight:700;"
        "}"
        ".block{"
        "text-align:center;"
        "}"
        "</style>"
        "</head>"
        "<body>"
        "<div class=\"block\">"
        "<div class=\"label\">当前 CPU 温度</div>"
        "<div class=\"value\">%s</div>"
        "</div>"
        "<div class=\"block\">"
        "<div class=\"label\">当前时间</div>"
        "<div class=\"value\" style=\"font-size:40px;\">%s</div>"
        "</div>"
        "</body>"
        "</html>",
        temp, now
    );

    snprintf(response, sizeof(response),
        "HTTP/1.1 200 OK\r\n"
        "Content-Type: text/html; charset=utf-8\r\n"
        "Content-Length: %zu\r\n"
        "Connection: close\r\n"
        "\r\n"
        "%s",
        strlen(body), body
    );

    send(client_fd, response, strlen(response), 0);
}

int main() {
    int server_fd, client_fd;
    struct sockaddr_in addr;
    int opt = 1;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket");
        return 1;
    }

    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("bind");
        close(server_fd);
        return 1;
    }

    if (listen(server_fd, 5) < 0) {
        perror("listen");
        close(server_fd);
        return 1;
    }

    print_urls();

    while (1) {
        client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) continue;

        char req[1024];
        recv(client_fd, req, sizeof(req) - 1, 0);
        send_response(client_fd);
        close(client_fd);
    }

    close(server_fd);
    return 0;
}

h@raspberrypi:~/Music/c-web/tem $ gcc temp_web.c -o temp_web

h@raspberrypi:~/Music/c-web/tem $ ./temp_web

eth0: http://192.168.9.243:1234

程序解析


一、头文件和宏定义

行号 代码 说明
1 #include <stdio.h> 引入标准输入输出库。这里会用到 printfsnprintfFILEpopenfgets
2 #include <stdlib.h> 引入标准库。这里主要是为了让一些通用函数声明完整。
3 #include <string.h> 引入字符串处理库。这里会用到 strncmpstrcmpstrlenstrcspnmemset
4 #include <unistd.h> 引入 Unix/Linux 常用函数。这里会用到 close
5 #include <ifaddrs.h> 引入网卡地址相关结构和函数。这里会用到 getifaddrsfreeifaddrs
6 #include <arpa/inet.h> 引入 IP 地址转换相关函数。这里会用到 inet_ntop
7 #include <sys/socket.h> 引入 socket 网络编程函数。这里会用到 socketbindlistenacceptsendrecv
8 #include <netinet/in.h> 引入 sockaddr_in 这种 IPv4 地址结构体。
9 空行 只是为了排版清楚,没有语法作用。
10 #define PORT 1234 宏定义端口号,后面写 PORT 就等于写 1234
11 #define BUF_SIZE 8192 宏定义响应缓冲区大小为 8192 字节。
12 空行 只是分隔代码块。

二、读取 CPU 温度函数

行号 代码 说明
13 void get_temp(char *out, size_t size) { 定义函数 get_tempvoid 表示这个函数本身不返回值。char *out 是把结果写到外面传进来的字符数组里。size_t size 是这个数组有多大。
14 FILE *fp = popen("vcgencmd measure_temp 2>/dev/null", "r"); popen 执行 shell 命令,并把命令输出当成文件读。fp 是文件指针。"r" 表示只读。2>/dev/null 表示把错误输出丢弃。
15 if (!fp) { 如果 fp 是空指针,说明命令执行失败。!fp 就是"没有成功打开"。
16 snprintf(out, size, "--"); "--" 写到输出数组里,表示失败时的占位值。snprintfsprintf 安全,因为它知道数组最大长度。
17 return; 立刻结束这个函数。
18 } 结束 if 代码块。
19 空行 只是排版。
20 char buf[128] = {0}; 定义一个 128 字节的字符数组 buf,并全部初始化为 0。用来临时接命令输出。
21 if (!fgets(buf, sizeof(buf), fp)) { fp 里读一行到 buf。如果读不到,进入失败处理。sizeof(buf) 是数组总长度。
22 pclose(fp); 关闭 popen 打开的"命令文件"。和普通 fclose 不一样,popen 配套的是 pclose
23 snprintf(out, size, "--"); 读失败也写 "--"
24 return; 结束函数。
25 } 结束这个 if
26 pclose(fp); 正常读完后也要关闭文件指针。
27 空行 排版。
28 buf[strcspn(buf, "\r\n")] = 0; 去掉行末换行符。strcspn(buf, "\r\n") 会找到 \r\n 第一次出现的位置,然后把那里改成字符串结束符 0
29 if (strncmp(buf, "temp=", 5) == 0) { 比较 buf 的前 5 个字符是不是 "temp="。如果是,就说明 vcgencmd 输出格式正常。
30 snprintf(out, size, "%s", buf + 5); buf 从第 6 个字符开始的内容复制到 outbuf + 5 就是跳过 "temp="。例如 temp=84.0'C 会变成 84.0'C
31 } else { 如果前缀不是 "temp=",走这里。
32 snprintf(out, size, "--"); 不符合预期格式,也返回 "--"
33 } 结束 else
34 } 结束 get_temp 函数。
35 空行 分隔函数。

三、读取时间函数

行号 代码 说明
36 void get_time_text(char *out, size_t size) { 定义读取当前时间的函数,结果也写到外部字符数组里。
37 FILE *fp = popen("date", "r"); 执行 date 命令,读取当前系统时间。
38 if (!fp) { 如果命令执行失败。
39 snprintf(out, size, "--"); 写入失败占位值。
40 return; 结束函数。
41 } 结束 if
42 空行 排版。
43 if (!fgets(out, size, fp)) { 直接把 date 输出读进 out
44 pclose(fp); 读失败时先关闭。
45 snprintf(out, size, "--"); 失败占位值。
46 return; 结束函数。
47 } 结束这个 if
48 pclose(fp); 正常读完后关闭命令句柄。
49 空行 排版。
50 out[strcspn(out, "\r\n")] = 0; 去掉读进来的换行符。否则网页上可能多一行空白。
51 } 结束 get_time_text 函数。
52 空行 分隔函数。

四、打印访问地址函数

行号 代码 说明
53 void print_urls() { 定义打印访问地址的函数。
54 struct ifaddrs *ifaddr, *ifa; 定义两个网卡链表指针。ifaddr 指向整条链表开头,ifa 用来遍历。
55 char ip[INET_ADDRSTRLEN]; 定义字符串数组,保存转换后的 IPv4 地址。INET_ADDRSTRLEN 是系统给的 IPv4 字符串最大长度。
56 int found = 0; 标记有没有找到可用地址。0 表示没找到,1 表示找到了。
57 空行 排版。
58 if (getifaddrs(&ifaddr) == -1) { 获取系统所有网卡地址信息。如果返回 -1 就失败。注意这里传的是 &ifaddr,因为函数要修改这个指针。
59 perror("getifaddrs"); 打印错误信息,前缀是 "getifaddrs"
60 return; 获取失败就直接返回。
61 } 结束 if
62 空行 排版。
63 for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { 遍历整个网卡地址链表。ifa = ifa->ifa_next 就是走到下一个节点。
64 if (!ifa->ifa_addr) continue; 如果当前节点没有地址信息,直接跳过这一次循环。continue 表示进入下一轮。
65 if (ifa->ifa_addr->sa_family != AF_INET) continue; 如果不是 IPv4 地址,就跳过。AF_INET 表示 IPv4。
66 if (strcmp(ifa->ifa_name, "lo") == 0) continue; 如果网卡名是 lo,也跳过。lo 是本地回环接口。
67 空行 排版。
68 struct sockaddr_in *sa = (struct sockaddr_in *)ifa->ifa_addr; 把通用地址指针强制转换成 IPv4 专用结构体指针,这样后面才能取 sin_addr
69 if (!inet_ntop(AF_INET, &sa->sin_addr, ip, sizeof(ip))) continue; 把二进制 IP 地址转成字符串,例如 192.168.1.100。如果失败就跳过。
70 空行 排版。
71 printf("%s: http://%s:%d\n", ifa->ifa_name, ip, PORT); 打印一行访问地址,比如 eth0: http://192.168.1.20:1234
72 found = 1; 说明已经找到至少一个可用地址。
73 } 结束 for 循环。
74 空行 排版。
75 if (!found) { 如果一个地址都没找到。
76 printf("localhost: http://127.0.0.1:%d\n", PORT); 那就退回打印本机地址。
77 } 结束 if
78 空行 排版。
79 freeifaddrs(ifaddr); 释放 getifaddrs 分配的链表内存。这个不能漏。
80 } 结束 print_urls 函数。
81 空行 分隔函数。

五、拼网页并发送 HTTP 响应

行号 代码 说明
82 void send_response(int client_fd) { 定义发送网页响应的函数。参数 client_fd 是客户端 socket 文件描述符。
83 char temp[64]; 定义字符数组,保存温度文本。
84 char now[128]; 定义字符数组,保存时间文本。
85 char body[4096]; 定义字符数组,保存 HTML 正文。
86 char response[BUF_SIZE]; 定义字符数组,保存完整 HTTP 响应。
87 空行 排版。
88 get_temp(temp, sizeof(temp)); 调用前面写的函数,读取温度到 temp
89 get_time_text(now, sizeof(now)); 读取当前时间到 now
90 空行 排版。
91 snprintf(body, sizeof(body), 开始把 HTML 页面内容格式化写进 body 数组。
92 "<!doctype html>" HTML 文档类型声明。
93 "<html lang=\"zh-CN\">" HTML 根标签,语言设为中文。C 字符串里双引号要写成 \"
94 "<head>" HTML 头部开始。
95 "<meta charset=\"utf-8\">" 指定字符编码 UTF-8。
96 "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">" 手机浏览器自适应宽度。
97 "<meta http-equiv=\"refresh\" content=\"1\">" 网页每 1 秒自动刷新一次。
98 "<title>温度和时间</title>" 浏览器标签页标题。不是页面正文。
99 "<style>" CSS 样式开始。
100 "body{" 开始写 body 的 CSS。
101 "margin:0;" 页面外边距设为 0。
102 "height:100vh;" 页面高度设为整个视口高度。
103 "display:flex;" 使用 flex 布局。
104 "flex-direction:column;" flex 布局方向为纵向,从上到下。
105 "align-items:center;" 横向居中。
106 "justify-content:center;" 纵向居中。
107 "gap:28px;" 上下两个块之间留 28 像素间距。
108 "background:#0d1117;" 背景颜色。
109 "color:#e6edf3;" 默认文字颜色。
110 "font-family:Arial,Consolas,monospace;" 字体设置。
111 "}" body 样式结束。
112 ".label{" .label 类样式开始。
113 "font-size:32px;" 标题字号 32 像素。
114 "font-weight:700;" 字体加粗。
115 "margin-bottom:8px;" 标题下方留 8 像素空隙。
116 "}" .label 样式结束。
117 ".value{" .value 类样式开始。
118 "font-size:64px;" 数值字号 64 像素。
119 "color:#79c0ff;" 数值颜色蓝色。
120 "font-weight:700;" 数值加粗。
121 "}" .value 样式结束。
122 ".block{" .block 类样式开始。
123 "text-align:center;" 块内文字居中。
124 "}" .block 样式结束。
125 "</style>" CSS 样式结束。
126 "</head>" 头部结束。
127 "<body>" 正文开始。
128 "<div class=\"block\">" 第一个信息块开始。
129 "<div class=\"label\">当前 CPU 温度</div>" 第一个块的标题。
130 "<div class=\"value\">%s</div>" 第一个块的值占位符 %s,后面会用 temp 替换。
131 "</div>" 第一个块结束。
132 "<div class=\"block\">" 第二个信息块开始。
133 "<div class=\"label\">当前时间</div>" 第二个块的标题。
134 "<div class=\"value\" style=\"font-size:40px;\">%s</div>" 第二个块的值占位符 %s,后面用 now 替换。这里还额外写了行内样式,把时间字号改成 40 像素。
135 "</div>" 第二个块结束。
136 "</body>" 正文结束。
137 "</html>", HTML 结束。这里逗号表示 snprintf 后面还要继续接参数。
138 temp, now temp 替换第一个 %s,用 now 替换第二个 %s
139 ); 结束这次 snprintf
140 空行 排版。
141 snprintf(response, sizeof(response), 开始拼完整 HTTP 响应。
142 "HTTP/1.1 200 OK\r\n" HTTP 状态行,表示请求成功。
143 "Content-Type: text/html; charset=utf-8\r\n" 告诉浏览器返回的是 UTF-8 编码 HTML。
144 "Content-Length: %zu\r\n" 告诉浏览器正文长度是多少字节。%zu 用来打印 size_t 类型。
145 "Connection: close\r\n" 告诉浏览器这次响应完就关闭连接。
146 "\r\n" HTTP 头和正文之间必须有一个空行。
147 "%s", 把 HTML 正文拼进去。
148 strlen(body), body strlen(body) 替换 %zu,用 body 替换 %s
149 ); 结束 snprintf
150 空行 排版。
151 send(client_fd, response, strlen(response), 0); 通过客户端 socket 把整个 HTTP 响应发送出去。
152 } 结束 send_response 函数。
153 空行 分隔函数。

六、主函数 main

行号 代码 说明
154 int main() { 主函数入口。int 表示最后返回一个整数给系统。
155 int server_fd, client_fd; 定义两个文件描述符变量。server_fd 是服务器 socket,client_fd 是接受到的客户端 socket。
156 struct sockaddr_in addr; 定义 IPv4 地址结构体,用来保存绑定地址和端口。
157 int opt = 1; 定义一个选项值 1,后面设置 socket 参数时会用。
158 空行 排版。
159 server_fd = socket(AF_INET, SOCK_STREAM, 0); 创建一个 TCP socket。AF_INET 表示 IPv4,SOCK_STREAM 表示 TCP。
160 if (server_fd < 0) { 如果返回值小于 0,说明创建失败。
161 perror("socket"); 打印错误信息。
162 return 1; 程序异常退出,返回 1。
163 } 结束 if
164 空行 排版。
165 setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 设置 socket 选项,允许地址快速复用,避免程序重启时提示端口被占用。
166 空行 排版。
167 memset(&addr, 0, sizeof(addr)); 先把 addr 结构体全部清零,避免里面有脏数据。
168 addr.sin_family = AF_INET; 指定地址族为 IPv4。
169 addr.sin_addr.s_addr = INADDR_ANY; 绑定到所有本机网卡地址,相当于 0.0.0.0
170 addr.sin_port = htons(PORT); 设置端口号。htons 把主机字节序转成网络字节序。
171 空行 排版。
172 if (bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { 把 socket 绑定到指定地址和端口。这里把 sockaddr_in* 强制转成 sockaddr*,因为 bind 需要通用类型。
173 perror("bind"); 打印绑定失败原因。
174 close(server_fd); 关闭服务器 socket,防止资源泄露。
175 return 1; 异常退出。
176 } 结束 if
177 空行 排版。
178 if (listen(server_fd, 5) < 0) { 开始监听端口。5 是等待队列长度。
179 perror("listen"); 打印监听失败原因。
180 close(server_fd); 关闭 socket。
181 return 1; 异常退出。
182 } 结束 if
183 空行 排版。
184 print_urls(); 打印所有可访问地址。程序启动后你在终端看到的地址就是这里来的。
185 空行 排版。
186 while (1) { 无限循环。1 永远为真,所以程序一直运行。
187 client_fd = accept(server_fd, NULL, NULL); 等待浏览器连接。一旦有人访问,就返回一个客户端 socket。
188 if (client_fd < 0) continue; 如果接受连接失败,直接开始下一轮循环。
189 空行 排版。
190 char req[1024]; 定义一个 1024 字节数组,简单接收浏览器发来的请求数据。
191 recv(client_fd, req, sizeof(req) - 1, 0); 从客户端读请求内容。这里其实没认真解析 HTTP,只是读一下,避免某些客户端直接断掉。
192 send_response(client_fd); 调用前面的函数,把网页响应发送给浏览器。
193 close(client_fd); 响应完成后关闭这个客户端连接。
194 } 结束 while 循环体。由于是无限循环,正常情况下不会跳出来。
195 空行 排版。
196 close(server_fd); 关闭服务器 socket。实际上这里通常执行不到,因为上面是死循环。
197 return 0; 正常结束程序返回 0。
198 } 结束 main 函数。

七、你最该先看懂的 10 个语法点

语法 例子 意思
指针 char *out out 不是普通字符,而是"指向字符数组开头"的地址。
数组 char buf[128] 分配 128 个字符空间。
取地址 &addr 取变量 addr 的内存地址。
结构体成员 addr.sin_port 取结构体里的某个字段。
指针访问结构体成员 ifa->ifa_name ifa 是指针,所以用 -> 访问字段。
强制类型转换 (struct sockaddr_in *)ifa->ifa_addr 告诉编译器"把这个地址按 sockaddr_in* 来解释"。
条件判断 if (...) { ... } 如果条件成立就执行大括号里的代码。
提前结束函数 return; 立刻退出当前函数。
跳过本次循环 continue; 不再执行本轮后面的代码,直接进入下一轮循环。
无限循环 while (1) 一直循环,除非程序被你手动停止。

八、如果你现在只想自己改字,不想碰逻辑

你平时最常改的其实就这几块:

你要改什么 改哪一行
端口号 第 10 行 #define PORT 1234
网页标题 第 98 行 <title>温度和时间</title>
页面显示"当前 CPU 温度"这几个字 第 129 行
页面显示"当前时间"这几个字 第 133 行
自动刷新秒数 第 97 行 content="1"
温度颜色 第 119 行 color:#79c0ff;
背景色 第 108 行 background:#0d1117;
时间字体大小 第 134 行 font-size:40px;

相关推荐
数据知道4 小时前
claw-code 源码分析:从 TypeScript 心智到 Python/Rust——跨栈移植时类型、边界与错误模型怎么对齐?
python·ai·rust·typescript·claude code·claw code
Thomas.Sir5 小时前
AI 医疗之罕见病/疑难病辅助诊断系统从算法到实现【表型驱动与知识图谱推理】
人工智能·算法·ai·知识图谱
javaGHui5 小时前
QClaw_简单方便_一键部署-多角色共同工作
ai
后端开发基础免费分享6 小时前
Claude Code 最全使用指南:CLAUDE.md、rules、skills、memory 一次讲清
人工智能·ai·claude·claudecode
Thomas.Sir6 小时前
重构诊疗效率与精准度之【AI 赋能临床诊断与辅助决策从理论到实战】
人工智能·python·ai·医疗·诊断
m晴朗7 小时前
测试覆盖率从35%到80%:我用AI批量生成C++单元测试的完整方案
c++·gpt·ai
2501_948114248 小时前
技术解码:Gemini交互式模拟API与高负载网关的选型逻辑
人工智能·python·ai
call me by ur name8 小时前
ERNIE 5.0 Technical Report论文解读
android·开发语言·人工智能·机器学习·ai·kotlin
俊哥V9 小时前
每日 AI 研究简报 · 2026-04-11
人工智能·ai