客户端-服务器模型

角色分离的计算架构,通信双方分为服务端和客户端。

  • 服务端:持续运行、监听特定端口,等待客户端请求。
  • 客户端:按需发起连接、发送请求、接收响应后终止。

模型通信的基本操作是“事务”,指客户端与服务器之间一次完整、原子的请求-响应交互过程。

原子性:事务要么成功(请求送达+响应返回)要么失败(超时/连接中断)。中间状态对应用层不可见。

网络

[应用层]  "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_TCP
IPPROTO_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;       // 链表指针
};

Logo

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

更多推荐