不会有 warn 警告的通用 web 开头

#!/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}: \n http://{ip}:{PORT}")
            found = True
    except Exception:
        pass
 
    if not found:
        print(f"localhost: http://127.0.0.1:{PORT}")

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

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

三、读取时间函数

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

四、打印访问地址函数

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

五、拼网页并发送 HTTP 响应

行号代码说明
82void send_response(int client_fd) {定义发送网页响应的函数。参数 client_fd 是客户端 socket 文件描述符。
83char temp[64];定义字符数组,保存温度文本。
84char now[128];定义字符数组,保存时间文本。
85char body[4096];定义字符数组,保存 HTML 正文。
86char response[BUF_SIZE];定义字符数组,保存完整 HTTP 响应。
87空行排版。
88get_temp(temp, sizeof(temp));调用前面写的函数,读取温度到 temp
89get_time_text(now, sizeof(now));读取当前时间到 now
90空行排版。
91snprintf(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 后面还要继续接参数。
138temp, nowtemp 替换第一个 %s,用 now 替换第二个 %s
139);结束这次 snprintf
140空行排版。
141snprintf(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 正文拼进去。
148strlen(body), bodystrlen(body) 替换 %zu,用 body 替换 %s
149);结束 snprintf
150空行排版。
151send(client_fd, response, strlen(response), 0);通过客户端 socket 把整个 HTTP 响应发送出去。
152}结束 send_response 函数。
153空行分隔函数。

六、主函数 main

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

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

语法例子意思
指针char *outout 不是普通字符,而是“指向字符数组开头”的地址。
数组char buf[128]分配 128 个字符空间。
取地址&addr取变量 addr 的内存地址。
结构体成员addr.sin_port取结构体里的某个字段。
指针访问结构体成员ifa->ifa_nameifa 是指针,所以用 -> 访问字段。
强制类型转换(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;

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐