《CSAPP》11
·
客户端-服务器模型
角色分离的计算架构,通信双方分为服务端和客户端。
- 服务端:持续运行、监听特定端口,等待客户端请求。
- 客户端:按需发起连接、发送请求、接收响应后终止。
模型通信的基本操作是“事务”,指客户端与服务器之间一次完整、原子的请求-响应交互过程。
原子性:事务要么成功(请求送达+响应返回)要么失败(超时/连接中断)。中间状态对应用层不可见。

网络
[应用层] "Hello"
↓ + HTTP头(若Web请求)
[传输层] TCP段 = [源端口:54321 | 目的端口:80 | 序号 | 确认号 | ... | "Hello"]
↓ + TCP头部(20字节)
[网络层] IP数据报 = [源IP:192.168.1.10 | 目的IP:203.0.113.5 | TTL | ... | TCP段]
↓ + IP头部(20字节)
[链路层] 以太网帧 = [源MAC | 目的MAC | 类型 | IP数据报 | CRC]
↓ + 帧头部(14字节)+ 尾部(4字节)
[物理层] 01010101...(电信号/光信号)
->经过路由器转发->服务器网卡接收->逆向解封装->应用层read()得到"Hello"。
代码运行在主机上,主机拥有IP和端口用来定位服务,发出去的信息经过路由器,链路,交换机等设备到达目的主机。进行通信服务。
IP因特网
IP:标识主机,与端口协同定位。
struct in_addr {
uint32_t s_addr; // 网络字节序(大端)存储
};
套接字接口
套接字socket是I/O思想的延申。
| 特性 | 普通文件 | 套接字 | CSAPP深意 |
|---|---|---|---|
| 抽象 | int fd = open(...) |
int sockfd = socket(...) |
统一I/O模型:所有I/O通过fd操作 |
| 读写 | read(fd, buf, n) |
read(sockfd, buf, n) |
程序员无需区分数据来源 |
| 关闭 | close(fd) |
close(sockfd) |
资源管理模型完全一致 |
| 内核视角 | 文件表项 → inode | 套接字表项 → 协议控制块 | "Everything is a file" |
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domin:可选AF_INET(ipv4)\AF_INET6(ipv6)\AF_UNIX(本地)
type:可选SOCK_STREAM(TCP)\SOCK_DGRAM(UDP)\SOCK_SEQPACKET
protocal:首选为0,可选IPPROTO_TCP\IPPROTO_UDP

套接字地址结构
#include <netinet/in.h>
struct sockaddr_in {
sa_family_t sin_family; // 2字节:必须为AF_INET
in_port_t sin_port; // 2字节:**网络字节序**端口号
struct in_addr sin_addr; // 4字节:**网络字节序**IPv4地址
char sin_zero[8]; // 8字节:填充至sockaddr大小(必须清零!)
};
socket(协议栈"出生证明")
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
| 参数 | 可选值 | CSAPP推荐 | 内核行为 |
|---|---|---|---|
domain |
AF_INET (IPv4)AF_INET6 (IPv6)AF_UNIX (本地) |
AF_INET |
创建对应协议族的套接字控制块(PCB) |
type |
SOCK_STREAM (TCP)SOCK_DGRAM (UDP) |
SOCK_STREAM |
初始化传输层状态机(TCP: CLOSED → LISTEN/SYN_SENT) |
protocol |
0 (自动)IPPROTO_TCPIPPROTO_UDP |
0 |
由domain+type推导协议号(TCP=6, UDP=17) |
bind 绑定本地地址
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd |
socket返回的fd |
必须是未连接的套接字(TCP处于CLOSED状态) |
addr |
指向协议特定地址结构 | 必须经getaddrinfo填充或手动初始化+字节序转换 |
addrlen |
地址结构实际大小 | sizeof(struct sockaddr_in)(非sizeof(sockaddr)) |
listen 转换为被动套接字
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd |
已bind的套接字 |
状态从CLOSED → LISTEN(TCP三次握手起点) |
backlog |
连接请求队列长度 | 非精确值:实际 = min(backlog, /proc/sys/net/core/somaxconn) |
accept 提取连接
#include <sys/socket.h>
int accept(int listenfd,
struct sockaddr *addr, // 输出:客户端地址
socklen_t *addrlen); // 输入/输出:地址长度
connect 客户端连接
#include <sys/socket.h>
int connect(int sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
| 参数 | 说明 | CSAPP关键点 |
|---|---|---|
sockfd |
客户端套接字(socket创建) |
状态:CLOSED → SYN_SENT |
addr |
服务器地址(经getaddrinfo解析) |
必须包含有效IP+端口 |
addrlen |
地址结构大小 | res->ai_addrlen(来自getaddrinfo) |
主机和服务的转换
| 人类视角 | 机器视角 | 转换必要性 |
|---|---|---|
"www.csapp.org" |
128.2.194.242 |
域名需解析为IP(网络层寻址) |
"http" 或 "80" |
0x0050(网络字节序) |
服务名/端口号需转为二进制端口(传输层多路分解) |
"localhost" |
127.0.0.1 |
本地测试需映射回环地址 |
int getaddrinfo(
const char *node, // 主机名或IP字符串(NULL=本机)
const char *service, // 服务名("http")或端口号("80")
const struct addrinfo *hints, // 指导解析(关键!)
struct addrinfo **res // 输出:地址链表头指针
);
struct addrinfo {
int ai_flags; // AI_PASSIVE, AI_CANONNAME...
int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
int ai_protocol; // 0=自动
socklen_t ai_addrlen; // (输出)地址结构大小
struct sockaddr *ai_addr; // (输出)地址结构指针
char *ai_canonname; // (输出)规范主机名
struct addrinfo *ai_next; // 链表指针
};
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)