欢迎光临
我们一直在努力

《深入理解计算机系统》读书笔记12: 网络编程

引言:从进程间通信到网络通信

作者: andylin02
学习章节: 第 11 章 网络编程
关键词: 客户端-服务器模型;套接字接口;socket/bind/listen/accept/connect;HTTP协议;Web服务器;Proxy Lab;并发服务器


“网络应用依赖于前面很多概念:进程、信号、字节顺序、内存映射以及动态内存分配。需要理解基本的客户端-服务器编程模型,以及如何编写使用因特网提供的服务的客户端-服务器程序。”——CSAPP作者

第 10 章我们学习了系统级 I/O,第 8 章学习了进程和信号,第 9 章学习了虚拟内存。第 11 章将这些知识融会贯通,深入探讨网络应用的构建方法。对主机而言,网络只是一种 I/O 设备,从网络上接收的数据从适配器经过 I/O 和内存总线复制到内存,通常是通过 DMA 传送。

本章将带你从零开始构建网络应用:先理解客户端-服务器模型和网络协议栈,再掌握套接字接口的核心函数,最终实现一个功能完整的 Web 服务器(Tiny Web Server),并为下一章第 12 章并发编程和 Proxy Lab 实验打下基础。

本章结构速览:

  • 11.1 客户端-服务器编程模型:网络应用的基本架构与事务模型
  • 11.2 网络:从以太网段到互联网络的层次结构
  • 11.3 全球 IP 因特网:IP 地址、域名系统(DNS)、TCP 连接
  • 11.4 套接字接口:socket、bind、listen、accept、connect 详解
  • 11.5 Web 服务器:HTTP 协议、静态内容与动态内容(CGI)
  • 11.6 Tiny Web 服务器实现:完整源码解析
  • 11.7 配套实验:Proxy Lab——实现一个多线程带缓存的 Web 代理

💡 与第 10 章的关联:第 10 章的系统级 I/O(RIO 包)是本章网络编程的直接依赖——所有套接字读写都依赖 rio_readlineb 和 rio_writen 等函数。建议在阅读本章前先掌握第 10 章的内容。

一、客户端-服务器编程模型

1.1 基本概念

每个网络应用都是基于客户端-服务器模型的。采用这个模型,一个应用是由一个服务器进程和一个或多个客户端进程组成。服务器管理某种资源,并且通过操作这种资源来为它的客户端提供某种服务。

💡 关键认知:客户端和服务器是进程,而不是机器或主机。一台主机可以同时运行许多不同的客户端和服务器,客户端和服务器的事务可以在同一台或不同的主机上。

1.2 客户端-服务器事务

一个客户端-服务器事务由以下四步组成:

┌─────────────────────────────────────────────────────────────────────┐
│ 客户端-服务器事务模型 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 客户端进程 服务器进程 │
│ ┌─────────┐ ┌─────────┐ │
│ │ │ 1. 发送请求 │ │ │
│ │ │ ────────────────────→ │ │ │
│ │ │ │ 解释 │ │
│ │ │ 2. 操作资源 │ 请求 │ │
│ │ │ │ │ │
│ │ │ 3. 发送响应 │ │ │
│ │ │ ←──────────────────── │ │ │
│ │ 4. 处理 │ │ │ │
│ │ 响应 │ │ │ │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

  • 步骤 1:客户端需要服务时,向服务器发送请求,发起一个事务(如 Web 浏览器请求一个文件)
  • 步骤 2:服务器收到请求后解释它,并以适当方式操作其资源(如 Web 服务器读取磁盘文件)
  • 步骤 3:服务器给客户端发送一个响应,并等待下一个请求
  • 步骤 4:客户端收到响应并处理它(如浏览器将数据显示在屏幕上)

二、网络——从以太网到互联网络

2.1 网络层次结构

物理上,网络是一个按照地理远近组成的层次系统。最底层是局域网(LAN) ,最流行的局域网技术是以太网。

┌─────────────────────────────────────────────────────────────────────┐
│ 网络层次结构示意图 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 层次 1: 以太网段(Ethernet Segment) │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │主机A│ │主机B│ │主机C│ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
│ └──────────┼──────────┘ │
│ 集线器 │
│ │
│ 层次 2: 桥接以太网(Bridged Ethernet) │
│ [以太网段1] ──→ 网桥/交换机 ──→ [以太网段2] │
│ │
│ 层次 3: 互联网络(Internet) │
│ [局域网A] ──→ 路由器 ──→ [局域网B] │
│ │
└─────────────────────────────────────────────────────────────────────┘

2.2 以太网段(Ethernet Segment)

每个以太网适配器都有一个全球唯一的 48 位地址(MAC 地址),存储在该适配器的非易失性存储器上。集线器不加分辨地将一个端口收到的每个位复制到其他所有端口,因此每个主机都能看到该帧,但只有目的主机实际读取它。

2.3 互联网络

多个不兼容的局域网可以通过叫做路由器的特殊计算机连接起来,组成一个互联网络(internet) 。互联网络的关键特性是,它能采用完全不同和不兼容技术的各种局域网和广域网组成。通过运行在每个主机和路由器上的协议软件,解决两大问题:

问题解决方案
命名机制 定义一致的主机格式,唯一标识每台主机
传送机制 定义标准传送单元——包(packet),包含包头和有效载荷

三、全球 IP 因特网

3.1 TCP/IP 协议族

全球 IP 因特网是最著名和最成功的互联网络实现。每台因特网主机都运行实现 TCP/IP 协议的软件,客户端和服务器混合使用套接字接口函数和 Unix I/O 函数来进行通信。

┌─────────────────────────────────────────────────────────────────────┐
│ TCP/IP 协议族层次结构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 应用层 HTTP │ FTP │ SMTP │ … │
│ ───────────────────────────────────────────────────────────────── │
│ 传输层 TCP(可靠、全双工) │ UDP(进程间传送) │
│ ───────────────────────────────────────────────────────────────── │
│ 网络层 IP(主机间传送,不可靠) │
│ ───────────────────────────────────────────────────────────────── │
│ 链路层 以太网 / 令牌环 / … │
│ │
└─────────────────────────────────────────────────────────────────────┘

3.2 IP 地址

一个 IP 地址就是一个 32 位的无符号整数。在 IP 地址结构中存放的地址总是以大端法(网络字节顺序)存放。通常用点分十进制来表示。

// IP 地址结构
struct in_addr {
uint32_t s_addr; // 网络字节顺序(大端)
};

// 点分十进制示例:0x8002c2f2 = 128.2.194.242

字节序转换函数:

函数功能记忆方法
htonl 32 位主机字节序 → 网络字节序 主机小,网络大
htons 16 位主机字节序 → 网络字节序
ntohl 32 位网络字节序 → 主机字节序
ntohs 16 位网络字节序 → 主机字节序

3.3 域名系统(DNS)

域名和 IP 地址的映射是通过分布世界范围内的数据库(DNS)来维护的。

# DNS 查询命令
hostname # 确定本地主机的实际域名
nslookup whleshark.ics.cs.cmu.edu # 查找域名对应的 IP 地址

💡 一个域名可能有多个 IP 地址(负载均衡、CDN 等场景)。

3.4 因特网连接

一个套接字是连接的一个端点,套接字地址为(IP 地址:端口号)。一个连接是由它两端的套接字地址唯一确定的。

端口类型:

端口类型范围特点
临时端口 49152–65535 客户端发起连接时,由内核随机自动分配
知名端口 0–1023 服务器使用,如 Web 服务器使用 80,电子邮件使用 25

TCP 连接的特点:

  • 点对点:连接两端是一对套接字
  • 全双工:数据可以双向同时流动
  • 可靠:保证数据按顺序、无丢失、无重复地到达

四、套接字接口

“套接字接口是一组函数,它们和 Unix I/O 函数结合起来,用以创建网络应用。”

4.1 套接字地址结构

// IP 套接字地址结构(具体地址)
struct sockaddr_in {
uint16_t sin_family; // 协议族(总是 AF_INET)
uint16_t sin_port; // 端口号(网络字节顺序)
struct in_addr sin_addr; // IP 地址(网络字节顺序)
unsigned char sin_zero[8]; // 填充,使总大小为 16 字节
};

// 通用套接字地址结构(用于函数参数)
struct sockaddr {
uint16_t sa_family; // 协议族
char sa_data[14]; // 地址数据
};

💡 所有套接字函数接受的地址参数类型都是 struct sockaddr *,因此在传递 sockaddr_in * 时需要强制类型转换。

4.2 核心套接字函数

┌─────────────────────────────────────────────────────────────────────┐
│ 客户端-服务器套接字调用流程 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 服务器 客户端 │
│ ┌─────────┐ ┌─────────┐ │
│ │ socket │ │ socket │ │
│ │ 创建 │ │ 创建 │ │
│ └────┬────┘ └────┬────┘ │
│ ↓ ↓ │
│ ┌─────────┐ ┌─────────┐ │
│ │ bind │ │connect │ │
│ │ 绑定 │ │ 连接 │ │
│ └────┬────┘ └────┬────┘ │
│ ↓ │ │
│ ┌─────────┐ │ │
│ │ listen │ │ │
│ │ 监听 │ │ │
│ └────┬────┘ │ │
│ ↓ │ │
│ ┌─────────┐ 连接请求 ┌─────────┐ │ │
│ │ accept │←────────────────│connect │ │ │
│ │ 接受 │ │ │ │ │
│ └────┬────┘ └─────────┘ │ │
│ ↓ ↓ │
│ ┌─────────┐ ┌─────────┐ │
│ │ rio_ │ 请求/响应(RIO I/O) │ rio_ │ │
│ │ read/wr │←──────────────────────────→│ read/wr │ │
│ └────┬────┘ └────┬────┘ │
│ ↓ ↓ │
│ ┌─────────┐ ┌─────────┐ │
│ │ close │ │ close │ │
│ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

① socket

int socket(int domain, int type, int protocol);
// 返回:成功返回套接字描述符,失败返回 -1

客户端和服务器使用 socket 函数来创建一个套接字描述符。

  • domain:协议族(通常为 AF_INET 表示 IPv4)
  • type:套接字类型(SOCK_STREAM 表示 TCP)
  • protocol:协议(通常设为 0,表示默认协议)
② bind

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 返回:成功返回 0,失败返回 -1

bind 函数告诉内核将 addr 中的服务器套接字地址和套接字描述符 sockfd 联系起来。服务器用它将监听套接字绑定到知名端口。

③ listen

int listen(int sockfd, int backlog);
// 返回:成功返回 0,失败返回 -1

listen 函数将 sockfd 从一个主动套接字转化为监听套接字,该套接字可以接受来自客户端的连接请求。

  • backlog:内核在开始拒绝连接请求前,队列中要排队的未完成连接请求的数量
④ accept

int accept(int listenfd, struct sockaddr *addr, socklen_t *addrlen);
// 返回:成功返回已连接描述符,失败返回 -1

accept 函数等待来自客户端的连接请求到达监听描述符 listenfd,利用传出参数 addr 来返回客户端的相关信息,并返回对应该客户端连接的通信文件描述符。

💡 两种描述符的区别:

  • 监听描述符 (listenfd):服务器生命周期内持续存在,用于接收新的连接请求
  • 已连接描述符 (connfd):每个客户端连接对应一个,通信完成后关闭
⑤ connect

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
// 返回:成功返回 0,失败返回 -1

客户端通过调用 connect 函数来建立和服务器的连接。connect 函数会阻塞,一直到连接成功建立或者发生错误。

⑥ getaddrinfo 与 getnameinfo

现代网络编程应优先使用这些函数,它们与协议无关,可同时处理 IPv4 和 IPv6。

int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **result);
// 返回:成功返回 0,失败返回非零错误码

getaddrinfo 将主机名、主机地址、服务名、端口号的字符串表示转换成套接字地址结构 addrinfo 链表。

int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *service, size_t servlen, int flags);
// 返回:成功返回 0,失败返回非零错误码

getnameinfo 将套接字地址结构反向转换成主机名和服务名的字符串表示。

4.3 CSAPP 提供的封装函数

// 简化版封装(在 csapp.h 中定义)
int open_clientfd(char *hostname, char *port);
int open_listenfd(char *port);

open_clientfd 建立与运行在 hostname 主机上、监听 port 端口的服务器的连接。open_listenfd 打开并返回一个监听描述符,准备接受连接。

五、Web 服务器

5.1 HTTP 协议基础

Web 客户端和服务器之间的交互使用的是一个基于文本的应用级协议,叫做 HTTP(超文本传输协议) 。

HTTP 请求的格式:

<method> <uri> <version>
<headers>

<body>

示例:

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Connection: close

HTTP 响应的格式:

<version> <status-code> <status-message>
<headers>

<body>

常见状态码:

状态码含义
200 OK——请求成功
301 Moved Permanently——资源已永久移动
302 Found——资源临时移动
400 Bad Request——请求格式错误
403 Forbidden——禁止访问
404 Not Found——资源不存在
500 Internal Server Error——服务器内部错误

5.2 静态内容 vs 动态内容

Web 内容分静态内容和动态内容。

类型描述示例
静态内容 客户端访问服务器,服务器返回指定的静态资源 HTML 文件、图像、CSS
动态内容 客户端请求页面,服务端根据请求参数动态生成页面 CGI 程序、PHP、JSP

静态内容:服务器读取磁盘文件,通过 HTTP 响应返回。

动态内容:服务器通过 fork + execve 执行 CGI(Common Gateway Interface)程序,将输出作为响应返回给客户端。

5.3 CGI(通用网关接口)

服务器使用 fork 和 execve 在子进程的环境中运行 CGI 程序。服务器通过环境变量向 CGI 程序传递请求参数,CGI 程序的输出(标准输出)被服务器捕获并返回给客户端。

关键环境变量:

变量描述
QUERY_STRING GET 请求的参数
REQUEST_METHOD 请求方法(GET 或 POST)
CONTENT_LENGTH POST 请求的内容长度
REMOTE_ADDR 客户端的 IP 地址

六、Tiny Web 服务器完整实现

Tiny 是一个功能齐全的 Web 服务器,能够为真实的 Web 浏览器提供静态的和动态的文本和图形内容。

6.1 主函数

#include "csapp.h"

void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

int main(int argc, char **argv) {
int listenfd, connfd;
char hostname[MAXLINE], port[MAXLINE];
socklen_t clientlen;
struct sockaddr_storage clientaddr;

if (argc != 2) {
fprintf(stderr, "usage: %s <port>\\n", argv[0]);
exit(1);
}
listenfd = open_listenfd(argv[1]); // 创建监听套接字
while (1) {
clientlen = sizeof(clientaddr);
connfd = accept(listenfd, (SA *)&clientaddr, &clientlen);
getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE,
port, MAXLINE, 0);
printf("Accepted connection from (%s, %s)\\n", hostname, port);
doit(connfd); // 处理请求
close(connfd); // 关闭连接
}
}

6.2 doit 函数:处理 HTTP 请求

void doit(int fd) {
int is_static;
struct stat sbuf;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE], cgiargs[MAXLINE];
rio_t rio;

rio_readinitb(&rio, fd);
rio_readlineb(&rio, buf, MAXLINE);
sscanf(buf, "%s %s %s", method, uri, version);
if (strcasecmp(method, "GET")) {
clienterror(fd, method, "501", "Not Implemented",
"Tiny does not implement this method");
return;
}
read_requesthdrs(&rio);

is_static = parse_uri(uri, filename, cgiargs);
if (stat(filename, &sbuf) < 0) {
clienterror(fd, filename, "404", "Not found",
"Tiny couldn't find this file");
return;
}

if (is_static) {
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size);
} else {
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
clienterror(fd, filename, "403", "Forbidden",
"Tiny couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs);
}
}

6.3 parse_uri:解析 URI

int parse_uri(char *uri, char *filename, char *cgiargs) {
char *ptr;

if (!strstr(uri, "cgi-bin")) { // 静态内容
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
if (uri[strlen(uri) 1] == '/')
strcat(filename, "home.html");
return 1;
} else { // 动态内容
ptr = index(uri, '?');
if (ptr) {
strcpy(cgiargs, ptr + 1);
*ptr = '\\0';
} else
strcpy(cgiargs, "");
sprintf(filename, ".%s", uri);
return 0;
}
}

6.4 serve_static:提供静态内容

void serve_static(int fd, char *filename, int filesize) {
int srcfd;
char *srcp, filetype[MAXLINE], buf[MAXBUF];

get_filetype(filename, filetype);
sprintf(buf, "HTTP/1.0 200 OK\\r\\n");
sprintf(buf, "%sServer: Tiny Web Server\\r\\n", buf);
sprintf(buf, "%sContent-length: %d\\r\\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\\r\\n\\r\\n", buf, filetype);
rio_writen(fd, buf, strlen(buf));

srcfd = open(filename, O_RDONLY, 0);
srcp = mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
close(srcfd);
rio_writen(fd, srcp, filesize);
munmap(srcp, filesize);
}

6.5 serve_dynamic:提供动态内容(CGI)

void serve_dynamic(int fd, char *filename, char *cgiargs) {
char buf[MAXLINE], *emptylist[] = { NULL };

sprintf(buf, "HTTP/1.0 200 OK\\r\\n");
rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Server\\r\\n");
rio_writen(fd, buf, strlen(buf));

if (fork() == 0) { // 子进程执行 CGI 程序
setenv("QUERY_STRING", cgiargs, 1);
dup2(fd, STDOUT_FILENO); // 将标准输出重定向到套接字
execve(filename, emptylist, environ);
}
wait(NULL); // 父进程等待子进程结束
}

七、并发服务器

虽然第 12 章会详细展开,但本章的 Web 服务器实现为并发编程埋下了伏笔。在第 11 章中,我们实现的是迭代服务器(一次只处理一个客户端),而并发服务器可以同时处理多个客户端。

7.1 三种并发模型概览

模型特点优点缺点
基于进程 accept 后 fork,每个子进程处理一个客户端 地址空间隔离,安全可靠 进程创建/切换开销大
基于 I/O 多路复用 单个进程使用 select/poll/epoll 监控多个描述符 开销小,共享数据容易 编程复杂度高
基于线程 创建多个线程共享进程地址空间 开销适中,共享数据容易 需要同步机制

基于进程的并发服务器核心模式:

while (1) {
connfd = accept(listenfd, ...);
if (fork() == 0) { // 子进程
close(listenfd); // 子进程关闭监听描述符
doit(connfd); // 处理客户端请求
close(connfd);
exit(0);
}
close(connfd); // 父进程关闭已连接描述符
}

💡 关键:子进程需要关闭从父进程继承的监听描述符,父进程需要关闭已连接描述符,以避免文件描述符泄漏。

八、配套实验:Proxy Lab

“Web Proxy 是 Web 浏览器和服务器之间的一个中间程序。本实验就是要实现一个支持多线程带缓存的 Web 代理。”

8.1 实验概览

Proxy Lab 是 CSAPP 的最后一个实验,分为三个部分:

部分任务难度
Part I 实现一个最基本的顺序代理(转发请求和响应) ★★☆
Part II 支持多线程并发处理请求(生产者-消费者模型) ★★★
Part III 使用 LRU 策略缓存 Web 对象(读者-写者模型) ★★★

8.2 Part I:顺序代理

主函数完全复制了 Tiny 服务器的框架,核心任务是在 doit 函数中实现代理逻辑:

  • 从客户端读取 HTTP 请求
  • 解析 URL,提取 host、端口和路径
  • 与目标服务器建立连接
  • 转发请求到目标服务器
  • 读取服务器的响应并转发回客户端
  • 8.3 Part II:多线程代理

    使用生产者-消费者模型实现并发处理,每个客户端请求由一个独立的线程处理。使用互斥锁保护共享数据结构(如活跃客户端计数)。

    8.4 Part III:LRU 缓存

    使用 LRU(最近最少使用)策略缓存 Web 对象,减少重复请求的网络开销。需要处理读者-写者问题——多个线程可以同时读取缓存,但写入时必须独占。

    8.5 关键技术点

    // URL 解析(提取 host、端口、路径)
    typedef struct URL {
    char host[MAXLINE];
    char port[MAXLINE];
    char path[MAXLINE];
    } URL;

    void parseUrl(char *s, URL *url) {
    char *ptr = strstr(s, "//");
    if (ptr != NULL) s = ptr + 2;
    ptr = strchr(s, '/');
    if (ptr != NULL) {
    strcpy(url->path, ptr);
    *ptr = '\\0';
    }
    ptr = strchr(s, ':');
    if (ptr != NULL) {
    strcpy(url->port, ptr + 1);
    *ptr = '\\0';
    } else
    strcpy(url->port, "80");
    strcpy(url->host, s);
    }

    九、本章知识点思维导图

    第 11 章 网络编程

    ├── 1. 客户端-服务器模型
    │ ├── 服务器进程(管理资源)
    │ ├── 客户端进程(请求服务)
    │ └── 事务模型(请求→操作→响应→处理)

    ├── 2. 网络基础
    │ ├── 以太网段(集线器、MAC 地址)
    │ ├── 桥接以太网(网桥/交换机)
    │ └── 互联网络(路由器、包)

    ├── 3. 全球 IP 因特网
    │ ├── IP 地址(32 位整数,网络字节顺序)
    │ ├── 域名系统(DNS 映射)
    │ └── TCP 连接(点对点、全双工、可靠)

    ├── 4. 套接字接口
    │ ├── socket(创建套接字)
    │ ├── bind(绑定地址)
    │ ├── listen(监听连接)
    │ ├── accept(接受连接)
    │ ├── connect(建立连接)
    │ └── getaddrinfo / getnameinfo(地址转换)

    ├── 5. Web 服务器
    │ ├── HTTP 协议(请求/响应格式)
    │ ├── 静态内容 vs 动态内容(CGI)
    │ └── Tiny Web 服务器实现

    ├── 6. 并发服务器
    │ ├── 基于进程(fork)
    │ ├── 基于 I/O 多路复用(select/poll/epoll)
    │ └── 基于线程

    └── 7. Proxy Lab
    ├── Part I:顺序代理
    ├── Part II:多线程代理
    └── Part III:LRU 缓存

    十、本章小结

    第 11 章将网络通信的底层机制与应用层实现完美结合:

  • ✅ 客户端-服务器模型:理解了网络应用的基本架构——服务器管理资源,客户端请求服务
  • ✅ 网络层次结构:从以太网段到互联网络,理解了 MAC 地址、IP 地址、包的概念
  • ✅ TCP/IP 协议族:掌握了 IP(不可靠主机间传送)、UDP(进程间传送)、TCP(可靠全双工)的区别
  • ✅ 套接字接口:深入理解了 socket、bind、listen、accept、connect 的调用流程与语义
  • ✅ Web 服务器:从零实现 Tiny Web 服务器,掌握了 HTTP 协议、静态/动态内容处理、CGI 机制
  • ✅ 并发服务器:了解了三种并发模型,为第 12 章做铺垫
  • ✅ Proxy Lab 实验:通过实现多线程带缓存的代理服务器,将网络编程、并发编程和缓存策略融会贯通
  • 💡 本章最核心的三个洞察:

  • 对主机而言,网络只是一种 I/O 设备——这一观点将网络编程与系统级 I/O 完美统一,套接字描述符本质上也是一种文件描述符
  • 套接字接口是网络编程的“万能钥匙”——所有网络应用(Web 服务器、代理、聊天程序等)都基于相同的套接字接口构建
  • fork + execve 是动态内容的核心——Web 服务器通过创建子进程执行 CGI 程序,将程序输出作为 HTTP 响应返回
  • 🔜 下一篇预告

    下一章我们将进入 第 12 章:并发编程。

    这一章将深入探讨构建高性能网络服务器的核心技术:

    • 📌 并发编程基础:并发与并行的区别、并发编程的应用场景
    • 📌 三种并发模型深度对比:
      • 基于进程的并发编程(fork + 信号)
      • 基于 I/O 多路复用的并发编程(select/poll/epoll)
      • 基于线程的并发编程(Pthreads)
    • 📌 线程同步:互斥锁、信号量、条件变量——避免竞争条件
    • 📌 并发错误:死锁、数据竞争、饥饿的分析与防范
    • 📌 配套实验:
      • Malloc Lab(已在第 9 章介绍)
      • Shell Lab(已在第 8 章介绍)
      • Proxy Lab(已在第 11 章预告)
      • Thread Safety 相关练习

    第 12 章将帮助你掌握编写安全、高效并发程序的核心技术,为构建高性能服务器打下坚实基础。

    敬请期待!


    本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
    👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

    赞(0)
    未经允许不得转载:171主机测评 » 《深入理解计算机系统》读书笔记12: 网络编程
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址