news 2026/9/19 12:10:04

CSAPP网络编程:从socket到HTTP的完整学习笔记

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CSAPP网络编程:从socket到HTTP的完整学习笔记

1. 开端:一句“8.20 搁置,9.5 继续”背后的网络编程学习

我在计划表里给 CSAPP 第11章网络编程留的时间是 8.20,结果当天被项目上的事打断,连书都没翻开。标题里那个“搁置”不是矫情,是真的没时间。到了 9.5 我重新打开这一章,把 socket、connect、accept 这些概念从头又理了一遍,才慢慢把之前零零碎碎的知识点串成一条线。这篇文章就当是我这段“拖延-重启”的记录,把关键知识点重新整理一遍,也把调试时踩过的坑写出来,给后面读这章的人少走点弯路。

CSAPP 这章和以往几章很不一样,它不教你写业务代码,也不追求让你立刻能写出高并发服务器。它把“网络”这件事拆成操作系统层面的概念,然后告诉你 socket 编程为什么是这套固定流程。如果你之前只是用过 Python 的requests库,或者调过 Java 的ServerSocket,但不知道底层发生了什么,这章就是补那块短板的。

这章适合三类人看:第一类是正在补操作系统和体系结构的学生,想把“文件描述符、进程、信号”和网络串起来;第二类是只写过应用层网络调用、没碰过 C socket 的开发者,想搞清楚连接建立和数据传输的本质;第三类是准备做 CSAPP 配套实验的读者,需要把第11章的接口都用熟练。下面我按自己的学习顺序,把这章的核心内容拆开讲。

1.1 客户端-服务器模型:几乎所有网络程序的地基

第11章开头先讲了客户端-服务器模型,很多人会觉得这是废话,但恰恰是理解整章的前提。在一个网络应用里,服务器进程先启动,创建 socket 后绑定地址,然后进入监听状态,等待客户端来连接。客户端进程则是主动发起连接的一方,连接建立后双方都能读写数据。

这个模型并不是唯一形态,但几乎你见过的所有主流网络应用都能套进去。你打开浏览器访问网页,浏览器是客户端,Web 服务器是服务端;你发邮件,邮件客户端是客户端,邮件服务器是服务端;你在终端里敲mysql -h 192.168.1.10,mysql 命令是客户端,MySQL 守护进程是服务端。

为什么 CSAPP 要把这个模型说得那么重?因为后面的所有 API、所有状态转换、所有边界情况,都是围绕“谁先启动”“谁在等待”“连接建立之后怎么通信”来展开的。你如果只是背了 socket、bind、listen、accept、connect 这几个函数名,却说不清哪一步发生在客户端、哪一步发生在服务端,那这一章基本等于白读。

我还踩过一个典型的理解错误:以为accept是“接受连接请求并完成通信”的入口,实则accept只是从已完成连接的队列里取出一个连接,真正的三次握手是内核在调用listen之后就开始处理的。这一点后面我还会细说。

1.2 IP、端口、协议栈:面试官最爱问的三个词

网络编程绕不开 IP、端口、协议栈。我第一次读这章的时候,这些词都能背,但一问“端口到底有什么用”就开始含糊。其实可以这样理解:IP 负责把数据包送到某台主机,端口负责把数据包交给这台主机上的某个进程。

一台服务器上可能同时跑着 Web 服务、SSH 服务、数据库服务,如果只有 IP,系统根本不知道该把网络数据递给谁。端口号是个 16 位整数,范围是 0 到 65535,其中 0 到 1023 通常是系统预留端口,比如 HTTP 用 80,HTTPS 用 443,SSH 用 22。我们自己写实验代码时一般用 8000 到 50000 之间的高位端口,避免和系统服务冲突。

协议栈这个词听起来吓人,其实就是一个分层模型:应用层、传输层、网络层、链路层。应用层数据往下传递时,每一层都会加上自己的头部;接收方再一层层剥掉头部。第11章重点在传输层,尤其是 TCP 和 UDP 的差异。

维度TCPUDP
连接性面向连接,需要建立连接无连接,直接发数据报
可靠性可靠,有确认、重传、排序不可靠,丢包不负责
传输单位字节流数据报,有边界
典型场景HTTP、文件传输、远程登录DNS、音视频实时传输、游戏状态同步

CSAPP 选择 TCP 讲网络编程,是因为它和文件描述符的读写模型最契合。你调用readwrite操作 TCP 连接时,就像操作一个管道,内核帮你处理了拆包和重组,你只需要关心字节流的边界。

1.3 Socket 的真相:它就是一个能读能写的文件

这一章最重要的认知转变,就是把 socket 当成文件描述符来理解。Unix 世界里“一切皆文件”,socket 也不例外。socket()系统调用返回的是一个非负整数,它和open()返回的文件描述符地位相同,都可以传给readwriteclose使用。

我在读第10章的时候,对文件描述符已经很熟了,但看到第11章 socket 时才真正理解这套抽象的力量。内核维护了一张文件描述符表,每个 fd 都指向一个打开的文件表项,socket 在文件表项里有自己的内核读写缓冲区。你往 socket 里写数据,数据进入内核发送缓冲区,由 TCP 协议栈负责分包和发送;你从 socket 里读数据,读的是内核接收缓冲区里已经到达的字节流。

这个模型解释了为什么read在 socket 上可能返回 0,表示对端关闭了连接;也解释了为什么write在 socket 上可能触发SIGPIPE信号,因为对端已经关闭,再写就是往一个没有接收方的管道里灌水,系统会默认终止进程。

理解了这个,你再看后面的代码就会明白,网络编程写服务端,本质上就是管理一堆 fd,监听新的连接、读写已有连接、关闭不再使用的连接。

2. Socket 编程的核心套路:从 socket() 到 accept()

2.1 服务端的固定流程,牢记 listenfd 和 connfd 的区别

CSAPP 第11章把服务端流程总结得很清晰:先socket创建监听 fd,再bind绑定地址,然后listen进入监听状态,之后循环accept拿出已完成的连接。这里有一个关键点,我之前一直没意识到:listenfdconnfd是两种完全不同的 fd。

listenfd是整个服务器生命周期的“门口接应员”,它只负责等待新的连接请求,永远不会用来收发业务数据。accept每次返回一个新的connfd,这个新 fd 才是具体某一次客户端连接的“直达通道”,我们在这个 conndf 上readwrite,最终服务完再把它close掉。

为什么要有这个区分?因为一个服务器往往要服务多个客户端,而listenfd可以一直存在,继续接收新的连接。你如果不开新线程、不 fork 子进程,只用一个进程循环 accept,那么同一时刻只能处理一个连接的完整生命周期,这就是“迭代服务器”,它能跑,但效率低,而且还存在“连接饿死”的问题。

服务端调listen(sockfd, backlog)时,backlog参数表示内核里已完成连接队列的长度,我在测试代码里通常写 5。客户端发起连接后,三次握手由内核完成,完成后的连接先放到这个队列里,等accept来取。队列满了之后,新的连接请求会被内核丢弃或延后处理。所以这个数值不能随便填 0,否则本地用并发测试工具压一下就会出现大量连接失败。

2.2 客户端的 connect:为什么客户端一般不 bind

客户端这边流程要简单很多:创建 socket,调用connect发起连接,连接成功后直接读写,最后关闭。客户端的connect会指定服务端的 IP 和端口,同时内核会为这个 socket 自动分配一个临时端口,作为本次连接的本端端口。这也是为什么客户端代码里通常看不到bind

你可能会问:服务端必须 bind,因为客户端需要通过固定 IP 和端口找到它;客户端不 bind,是因为内核自动选端口就够了,不需要给外部暴露固定地址。如果多个客户端进程同时连接同一个服务器,内核会给每个客户端 socket 分配不同的临时端口,连接四元组(源IP, 源端口, 目的IP, 目的端口)也就各不相同,服务器就能区分不同连接。

在实验环境里,如果connect返回Connection refused,先别急着怀疑代码,检查服务器进程是否真的在监听那个端口。我用netstat -tlnp查端口时,经常发现自己刚才的服务端进程没起来,或者端口号写错了,客户端连到另一个端口上。

2.3 字节序和地址结构:新手必踩的两块砖

先讲字节序。不同 CPU 存放多字节整数的顺序不同,x86 是小端,而网络传输规定统一用大端,也就是“网络字节序”。调用socket编程接口时,端口号、IP 地址都要从主机字节序转换成网络字节序,反方向则要转换回来。

CSAPP 给的四个函数是htonlhtonsntohlntohs,分别处理 long 和 short 类型的字节序转换。实际代码里,端口一般用htons(PORT),因为端口号是 16 位;IPv4 地址用htonl(INADDR_ANY),表示绑定任意本地地址。我早期没写htons,在笔记本小端环境下端口号被翻转,导致客户端去连一个完全不对的端口,查了很久才找到原因。

再讲地址结构。你在 C 里看到struct sockaddr_instruct sockaddr时,别慌。sockaddr是通用地址结构,所有协议的地址都能往里塞;sockaddr_in是 IPv4 的专用结构,字段更明确。调用bindconnectaccept时都要把sockaddr_in*强转成sockaddr*传参,这是历史遗留的接口设计,现在依然这么用。

struct sockaddr_in里有三个字段最容易写错:sin_family要写AF_INETsin_port要转字节序,sin_addr.s_addr要写 IP 地址的网络字节序值。用inet_pton把点分十进制字符串转成二进制地址,比老式inet_addr更安全,也更容易处理错误返回值。

3. 动手实现一个可跑的 Echo 服务端和客户端

3.1 服务端代码:把上面流程落成 C 代码

看懂了流程,还是要落实到代码。我刚开始在 CSAPP 原书配套代码里看到了很多封装函数,比如open_clientfdopen_listenfd,它们封装了getaddrinfo等细节。我这里为了讲清楚底层,先写一版不依赖封装的原始 echo 服务器,方便对照上面说的每一步。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 #define MAXLINE 4096 int main() { int listenfd, connfd; socklen_t clientlen; struct sockaddr_in serveraddr, clientaddr; char buf[MAXLINE]; listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { perror("socket"); return 1; } memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); serveraddr.sin_port = htons(PORT); if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) { perror("bind"); return 1; } if (listen(listenfd, 5) < 0) { perror("listen"); return 1; } printf("server started at port %d\n", PORT); while (1) { clientlen = sizeof(clientaddr); connfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientlen); if (connfd < 0) { perror("accept"); continue; } printf("client connected: %s\n", inet_ntoa(clientaddr.sin_addr)); ssize_t n; while ((n = read(connfd, buf, MAXLINE)) > 0) { write(STDOUT_FILENO, buf, n); write(connfd, buf, n); } close(connfd); } return 0; }

这段代码的服务端循环是迭代式的,同一时间只能服务一个客户端。read返回值有三种情况:大于 0 表示读到了字节;等于 0 表示对端关闭,循环结束;小于 0 表示出错,其中EINTR是被信号中断,严格代码里应该继续重读,我这里是简化版。

注意我bind地址用了INADDR_ANY,意思是绑定本机所有网卡上的 8888 端口。这样你用127.0.0.1或者本机局域网 IP 都能连上来。如果只绑定127.0.0.1,外部主机就访问不到了。

3.2 客户端代码:建立连接后按行收发

客户端写法相对简单,核心是connect成功之后,用read/write直接通信。下面这段代码接收命令行传入的服务端 IP,然后从标准输入读一行,发送给服务器,再读回服务器的输出。

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 #define MAXLINE 4096 int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "usage: %s <server_ip>\n", argv[0]); return 1; } int clientfd = socket(AF_INET, SOCK_STREAM, 0); if (clientfd < 0) { perror("socket"); return 1; } struct sockaddr_in serveraddr; memset(&serveraddr, 0, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(PORT); if (inet_pton(AF_INET, argv[1], &serveraddr.sin_addr) <= 0) { perror("inet_pton"); return 1; } if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) { perror("connect"); return 1; } char buf[MAXLINE]; printf("input: "); while (fgets(buf, MAXLINE, stdin) != NULL) { write(clientfd, buf, strlen(buf)); ssize_t n = read(clientfd, buf, MAXLINE); if (n > 0) { write(STDOUT_FILENO, buf, n); } printf("input: "); } close(clientfd); return 0; }

这段代码只做了一次read,如果服务器返回的数据超过了一次read能读到的量,就会截断。实际项目中要循环读,直到读到预期长度或连接关闭。这也就是 CSAPP 里强调 RIO(Robust I/O)包的原因,它保证不论收到多少数据,都能按你的需求读满或读到 EOF。实验代码里建议直接用书上的 RIO 封装,别自己裸写read循环,容易漏边界。

3.3 用 telnet 验证服务端:教科书式的测试方法

代码写完后,最直接的验证方式是用系统自带的telnet。把服务端启动起来,在另一个终端执行:

telnet 127.0.0.1 8888

连上之后,你会看到服务端打印了客户端 IP,然后你在 telnet 里敲任意字符串,回车,服务端立刻把同样内容回显过来。这说明 TCP 连接已经建立,数据链路是通的。

测试完 telnet,再跑一遍自己写的客户端:

gcc -std=c11 echoclient.c -o echoclient ./echoclient 127.0.0.1

输入hello csapp,如果看到回显,说明两端程序都工作正常。此时不要急着收工,再多做两个测试:一个是在客户端直接Ctrl+D结束输入,观察服务端是否正常处理read返回 0;另一个是连续启动多个 telnet 客户端,观察迭代服务器是否只能排队服务。第二个测试能直观感受“迭代模型”的缺陷,也为后面理解并发模型做铺垫。

3.4 我实测中遇到的三个运行问题

第一个问题是端口被占用。服务端还没退出又重新启动时,bind经常报Address already in use。原因是上一个服务端进程还占着端口,或者 TCP 的 TIME_WAIT 状态还没结束。解决办法是先用psnetstat -tlnp查端口,把旧进程杀掉,或者在代码里设置SO_REUSEADDR选项。

第二个问题是客户端connect返回Connection refused。这个坑我一开始还怪代码,后来才发现服务端根本没跑起来,或者端口不一致。排查时先netstat -tlnp | grep 8888,确认 listen 状态的进程存在,再检查客户端代码里的端口号。

第三个问题是程序正常关闭时,服务端偶尔会收到SIGPIPE信号导致进程退出。原因是对端已经关闭,但服务端还继续write。CSAPP 里也提到过这个,处理方式要么在write前做好状态判断,要么忽略SIGPIPE信号,或者用send函数的MSG_NOSIGNAL标志。我自己实验时经常在客户端异常退出后看到这种问题,属于必踩项。

注意:写网络代码时,一定要对系统调用的返回值做检查。不要图省事直接忽略acceptreadwrite的返回值,否则排错时的成本会翻倍。

4. 从 Echo 到 HTTP:理解第11章真正的考点

4.1 为什么经历了 Echo 还要学 HTTP 解析

Echo 服务器只能证明 socket 流程通了,但真正有意义的网络程序是解析协议。第11章后半部分花了不少篇幅讲 HTTP,因为 HTTP 是最直观的文本协议,你用 telnet 都能手敲请求。

一个最简单 HTTP 请求长这样:

GET /index.html HTTP/1.1 Host: localhost:8888 User-Agent: curl/8.5.0 Connection: close

服务器收到后,要按行解析请求行,再解析头部,然后根据请求的 URI 决定是返回静态文件还是交给动态逻辑。这个过程和 echo 的核心区别是:echo 不需要理解数据内容,直接回显;HTTP 服务器必须解析语义,做路径映射、内容类型判断、响应头构造。

CSAPP 里给了一个极简 Web 服务器示例,叫 Tiny,代码很短,但完整展示了静态和动态内容返回的流程。我读这一段时收获最大的是:不要把 HTTP 想得太玄,它就是一套约定好格式的文本对话。服务器不需要理解浏览器的“意图”,它只需要按照规范把请求解析出来,按照规范把响应构造回去。

4.2 一个极简 HTTP 请求解析过程

解析 HTTP 请求行的常规做法是先用fgets读一行,然后用sscanf拆出三个字段:方法、URI、版本号。比如:

char method[MAXLINE], uri[MAXLINE], version[MAXLINE]; if (fgets(buf, MAXLINE, fp) == NULL) { return; } sscanf(buf, "%s %s %s", method, uri, version);

这只是最粗糙版本,但要完成 CSAPP 的在线作业或实验,通常还需要处理几个细节:判断方法是不是GETURI是否需要做 URL 解码,比如%20要还原成空格;还要判断 URI 是否含查询字符串,如果有,需要拆成路径和参数两部分。

响应构造也要注意格式。一个最简单的成功响应是:

HTTP/1.1 200 OK Content-Type: text/html Content-Length: 137 Connection: close <html>...</html>

头部字段和正文之间必须有一个空行。Content-Length必须和正文实际字节数一致,否则浏览器会一直等待或直接报错。很多新手第一次写 HTTP 服务器都栽在空行上,要么多敲了一个\n,要么少敲了一个\r\n

我在实验里用的都是\r\n,因为 HTTP 规范里行结束符是 CRLF。虽然很多服务器对\n也能容忍,但规范就是规范,照着写才不会出现跨平台、跨客户端解析异常。

4.3 并发模型:一个 socket 服务端如何服务多个客户端

CSAPP 第11章后半部分很大一部分在讲并发,因为迭代服务器太弱了。如果你用浏览器发一个请求,浏览器会创建多个连接来下载网页里的静态资源,迭代服务器只能一个接一个处理,前面一个阻塞,后面的全部排队。

书中提到了三种并发方式:基于进程、基于线程、基于 I/O 多路复用。

基于进程的模型最简单,accept返回一个connfd后,fork一个子进程去服务这个连接。子进程要close(listenfd),父进程要close(connfd),不然 fd 引用计数会乱。父进程还要处理SIGCHLD信号回收僵尸进程,否则系统里会堆积一堆僵尸子进程。

基于线程的模型思路类似,用pthread_create为每个连接创建一个现场服务线程。要注意设置线程分离,或者在主线程里回收,否则线程也会变成“僵尸”;多个线程共享全局变量时还要加锁,否则会有竞态。

基于 I/O 多路复用的模型不用为每个连接创建新进程或线程,而是用一个selectpoll同时监听多个 fd,哪个 fd 可读就处理哪个。CSAPP 重点展示了select版本,它更接近现代高并发服务器的实现思路,不过代码细节也更繁琐。

并发模型优点缺点适合场景
多进程简单,隔离性好创建开销大,进程数有限并发量不高的服务
多线程开销较低,共享数据方便需要同步,线程安全问题多中等并发,业务较重
I/O 多路复用单线程可管大量 fd,节省资源编程复杂度高,不适合阻塞任务长连接多、IO 密集型

我个人的学习建议是:先把多进程版本跑通,再去看多线程版本,最后才啃select。一上来就啃事件驱动,很容易被 fd 集合的操作搞晕。

5. 记录一次拖延了半个月的“回归学习”

5.1 搁置期过后如何快速找回状态

从 8.20 到 9.5,中间隔了将近三周。重新打开书的时候,我之前记得的 socket 流程已经糊了。我用的恢复方法是“三步走”:第一步,只看第11章的几张关键图,把 client-server 状态转换重新画一遍;第二步,不看资料默写socketbindlistenacceptconnect的调用顺序;第三步,直接打开旧的实验代码,跑一遍,再改一个地方,比如把迭代服务器改成能处理多次连接的版本。

这样做比从头读一遍更省时间,因为核心记忆还在,缺的是“重新激活”。如果你也碰到计划中断的情况,不要逼自己从第一页重新开始,那样很容易产生厌烦情绪。挑一个小的可运行 demo,先跑起来,再往里加功能,状态会恢复得很快。

搁置期最大的问题不是知识忘了,而是心理上的“畏难”。一旦你开始动手改代码,那种“不知道从哪开始”的焦虑感就会消失。我拖到 9.5 才打开,其实就是因为总觉得要腾出一个完整下午来学,结果一直等不到那样的时间。后来我改变策略,只要求自己先完成第一步“画流程图”,花二十分钟就进入状态了。

5.2 资料与工具清单:哪些真正有用

第11章官方配套代码和讲义肯定要看,但要搭配手册使用。我在写 socket 代码时,每次都打开man 2 socketman 2 bindman 2 connect这几个手册页,把参数和返回值认真过一遍,比自己硬记牢固得多。

调试工具方面,telnet虽然老,但用来测试 TCP 文本协议特别方便。curl是 HTTP 调试神器,可以手动指定请求头、查看响应头。端口排查用netstat -tlnpss -tlnp,如果某个端口被占,用lsof -i:8888能查到具体进程。

抓包工具我用的是 tcpdump 和 Wireshark。第一次看我写的服务器和别人写的服务器交互时,抓包能让你看到三次握手、数据包确认、四次挥手,网络编程一下子从抽象变具体。观察 TCP 状态迁移时,netstat的输出也能帮你看到LISTENESTABLISHEDTIME_WAIT这些状态,这是只看代码永远学不到的东西。

工具用途使用时机
telnet手工连接 TCP 端口,模拟客户端测试服务端是否在监听
curl发送 HTTP 请求测试 Web 服务器响应
netstat / ss查看端口和连接状态排错、查占用
lsof查看进程打开的 fd查端口归属
tcpdump / Wireshark抓包分析协议细节深入理解三次握手、关闭流程

5.3 给刚开始读这章的人三个建议

第一个建议:不要背 API,把流程画成图。第11章所有接口都围绕着“连接建立、数据传输、连接关闭”这条主线,你把这个主线理解透了,API 只是一个查手册的问题。

第二个建议:认真做一次实验。CSAPP 配套的网络实验会要求你实现一个并发的 HTTP 服务,做完之后你对accept、动态请求、URL 解析、响应头构造这些知识点的记忆深度,远超过读十遍书。如果时间紧,至少也要把书上的 Tiny 服务器代码手动敲一遍,敲的过程中会不得不思考很多“昨天看的时候以为懂、今天一写就卡住”的细节。

第三个建议:先跑通,再优化。我第一次写服务端时,总想一口气把所有边界情况都处理好,结果代码越写越乱。后来换了个思路,先实现“能跑、能收发数据”,再逐步处理EINTR、多进程回收、大文件读取这些进阶问题。每完成一个小版本,就测试一轮,这样进步最明显。

我在实际把第11章啃下来的过程中,最真实的体会是:网络编程没有那么多玄机,它的难度主要来自“接口多、状态多、边界条件多”。但只要你能静下心,把一次完整的 echo 通信从socket()close()全部走通,再往 HTTP 解析和并发模型上延伸,这章就算真正入门了。至于剩下的细节,都是靠一次次 debug 和抓包积累出来的经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/19 12:09:53

IDM试用期重置原理与PowerShell激活脚本实战

1. IDM 试用期的本质&#xff1a;不是“到期”&#xff0c;而是“计时器重置失败”很多人以为 IDM 的 30 天试用期是某种硬编码的倒计时&#xff0c;一旦弹出“Trial expired”窗口&#xff0c;就等于被系统彻底封死&#xff0c;只能重装或付费。这种理解错得离谱——它直接导致…

作者头像 李华
网站建设 2026/9/19 12:08:43

Kimi-k2驱动Claude Code:高效AI编程助手配置方案

1. 项目背景与技术解析最近在开发者圈子里流传着一个相当有意思的技术方案——通过Kimi-k2驱动Claude Code实现更高效的代码生成与辅助编程。作为一名长期关注AI编程工具的开发者&#xff0c;我第一时间对这个方案进行了完整测试和验证&#xff0c;现在把可落地的配置方案和实测…

作者头像 李华
网站建设 2026/9/19 12:07:22

Gollum快速上手教程:3个命令搭建你的Git驱动Wiki

Gollum快速上手教程&#xff1a;3个命令搭建你的Git驱动Wiki 【免费下载链接】gollum A simple, Git-powered wiki with a local frontend and support for many kinds of markup and content. 项目地址: https://gitcode.com/gh_mirrors/go/gollum Gollum 是一个用 Rub…

作者头像 李华
网站建设 2026/9/19 12:01:49

G1垃圾回收器原理与调优实战:从Region到暂停时间

1. 先从垃圾回收器的选择聊起1.1 为什么G1会成为主流默认选择接触Java的人&#xff0c;基本都跟GC打过照面。从我自己的经历来说&#xff0c;早年做后端服务的时候&#xff0c;用的还是Parallel Scavenge配Parallel Old&#xff0c;后来CMS一度是低延迟场景的标配&#xff0c;再…

作者头像 李华
网站建设 2026/9/19 12:00:13

教育平台前端自动化原理与Tampermonkey实战

1. 项目概述&#xff1a;这不是“挂机”&#xff0c;而是对在线学习系统交互逻辑的逆向工程实践“学习通”和“智慧职教”是当前国内高校与职业院校广泛采用的两大主流教学平台&#xff0c;它们承载着课程视频播放、章节测验提交、课堂签到、作业上传、讨论区互动等核心教学闭环…

作者头像 李华