网络编程基础
网络编程基础
网络编程是让不同进程通过网络交换数据的编程技术。两个程序可以在同一台机器、同一个局域网,或互联网的不同位置;它们通常通过操作系统提供的 Socket API 收发数据。
常见场景包括:
- 浏览器与 Web 服务器通信;
- 客户端连接游戏、聊天或数据库服务器;
- 微服务之间的 RPC / HTTP 调用;
- 文件传输、远程登录、消息队列;
- 局域网设备发现和实时音视频。
网络编程不等于“调用一次 send()、对方调用一次 recv()”。实际程序还要处理域名解析、连接失败、超时、部分读写、协议解析、并发、资源限制和安全问题。
网络通信的参与者
最常见的模式是客户端—服务端(Client–Server):
1 | 客户端 服务端 |
| 角色 | 典型职责 |
|---|---|
| 客户端 | 主动发起连接或请求;通常不固定绑定端口,由系统分配临时端口。 |
| 服务端 | 在固定地址和端口上等待请求;通常需要 bind()、listen() 和 accept()。 |
| 对等节点 | 在 P2P 等场景中,两端可同时充当客户端和服务端。 |
客户端和服务端描述的是通信中的角色,不是机器的永久身份:一台机器可以同时运行客户端程序和服务端程序。
网络分层
网络协议按层组织,每层负责不同的问题。应用开发中最常接触以下四层:
| 层次 | 代表协议或概念 | 主要职责 |
|---|---|---|
| 应用层 | HTTP、DNS、SMTP、自定义协议 | 定义业务数据含义,例如“获取页面”“登录”“查询域名” |
| 传输层 | TCP、UDP | 在两个进程之间传输数据;使用端口区分应用 |
| 网络层 | IP | 在不同网络间寻址和路由;使用 IP 地址定位主机 |
| 链路层 | Ethernet、Wi-Fi | 在本地链路上传输数据帧;使用 MAC 地址 |
发送时,数据会逐层封装:
1 | 应用数据 |
接收端再反向解封装。普通 Socket 程序通常只处理应用层数据;TCP、UDP、IP 首部由操作系统网络协议栈处理。
更多协议层次、TCP/UDP 和 HTTP 的知识见 协议基础.md。
IP 地址:找到目标主机
IP 地址用于在网络中定位主机的网络接口。
| 类型 | 位数 | 示例 | 说明 |
|---|---|---|---|
| IPv4 | 32 位 | 192.168.1.10 |
常用点分十进制表示 |
| IPv6 | 128 位 | 2001:db8::10 |
地址空间更大,使用十六进制与冒号压缩表示 |
常见特殊地址:
| 地址 | 含义 |
|---|---|
127.0.0.1 / ::1 |
回环地址,只能访问本机,适合本地开发与测试 |
0.0.0.0 |
服务端绑定时表示监听所有 IPv4 网卡;它不是客户端应连接的目标地址 |
:: |
IPv6 中与 0.0.0.0 类似的未指定地址,常用于监听所有 IPv6 地址 |
常见 IPv4 私有地址范围包括 10.0.0.0/8、172.16.0.0/12 和 192.168.0.0/16。这些地址不能直接在公网路由,通常需要 NAT、端口映射或 VPN 才能被公网主机访问。
“服务程序已经启动”不等于“外部一定能访问”。还要检查:服务监听的具体地址、服务器防火墙/安全组、路由、NAT 规则以及客户端网络是否可达。
端口:找到目标服务
同一台主机可能运行多个网络程序,端口号用于将到达主机的数据交给正确的应用服务。端口是 16 位无符号整数,范围为 0 到 65535。
| 范围 | 含义 |
|---|---|
0–1023 |
知名端口;Unix-like 系统中通常需管理员权限才能绑定 |
1024–49151 |
注册端口,普通服务常使用 |
49152–65535 |
动态/临时端口,客户端通常由系统自动选择 |
常见端口:
| 服务 | 默认端口 | 说明 |
|---|---|---|
| HTTP | 80 |
明文 Web 协议的惯例端口 |
| HTTPS | 443 |
HTTP over TLS 的惯例端口 |
| SSH | 22 |
远程登录 |
| DNS | 53 |
域名解析,通常使用 UDP,也可使用 TCP |
| MySQL | 3306 |
数据库默认端口 |
| Redis | 6379 |
内存数据库默认端口 |
端口只是约定,服务可以监听其他端口。例如 HTTP 服务也可以运行在 8080。使用端口 0 进行 bind() 时,系统会自动分配可用端口,可通过 getsockname() 查询实际值。
地址、端口与连接
一个网络端点通常写作:
1 | IP 地址:端口号 |
对于一条已经建立的 TCP 连接,操作系统通常使用四元组区分它:
1 | 源 IP、源端口、目的 IP、目的端口 |
例如:
1 | 客户端:192.168.1.100:50000 |
多个客户端可以同时连接同一服务器的 8080 端口,因为它们的源端口或源 IP 不同。服务端的监听 Socket 负责 accept() 新连接;每个成功的 accept() 会返回一个独立的客户端通信 Socket。
域名与 DNS
人更容易记住 example.com 这样的域名,网络连接最终仍需使用 IP 地址。DNS(Domain Name System)负责将域名解析为 IPv4 地址(A 记录)或 IPv6 地址(AAAA 记录)。
在 C++ 网络程序中,推荐使用 getaddrinfo() 处理域名和地址:它同时支持 IPv4、IPv6、端口字符串以及服务端绑定。
1 |
|
service 参数是端口的字符串,例如 "443" 或 "https",不是整数 443。getaddrinfo() 的失败返回 EAI 错误码,应使用 gai_strerror(),而不是 perror()。
协议:让数据可被双方理解
协议定义通信双方共同遵守的规则。即使 TCP 连接成功,如果双方没有使用同一应用层协议,也无法正确理解对方的数据。
一个协议至少应明确:
- 消息的边界在哪里;
- 每个字段的含义、类型、长度和字节序;
- 请求与响应的顺序;
- 成功、失败和异常分别如何表示;
- 超时、重复请求、未知字段和版本升级如何处理。
例如只发送文本 hello 仍存在问题:服务端不知道消息是否已经结束、是否还有下一段内容,以及应该返回什么。对于 TCP,不能用“调用一次 send()”来定义一条消息,因为 TCP 只提供有序字节流。
详细的协议边界、TCP/UDP、HTTP、DNS 与自定义协议设计见 协议基础.md。
TCP 与 UDP:如何选择
| 对比项 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 传输语义 | 可靠、有序的字节流 | 尽力而为的数据报 |
| 消息边界 | 不保留,应用层必须定义 | 保留一份数据报边界 |
| 常见场景 | 网页、文件、数据库、远程登录 | DNS、实时媒体、游戏状态、局域网发现 |
简单原则:业务需要完整、可靠、有序的数据时通常选择 TCP;允许少量丢失且更在意实时性时可以考虑 UDP。UDP 不会天然让程序“更快”,可靠性、重传、排序和拥塞处理若由应用承担,复杂度会增加。
Socket:程序使用网络的接口
Socket(套接字)是操作系统提供的网络通信接口。在 Unix-like 系统中,Socket 通常以文件描述符 int fd 表示,因此可以创建、读写、关闭。
| Socket 类型 | 常见协议 | 特点 |
|---|---|---|
SOCK_STREAM |
TCP | 面向连接、可靠、有序、字节流 |
SOCK_DGRAM |
UDP | 无连接、数据报、尽力而为 |
SOCK_RAW |
IP/ICMP 等 | 直接处理较底层报文,通常需要较高权限 |
TCP 服务端典型流程:
1 | socket → setsockopt(可选)→ bind → listen → accept → recv/send → close |
TCP 客户端典型流程:
1 | socket → connect → recv/send → close |
UDP 接收端典型流程:
1 | socket → bind → recvfrom/sendto → close |
Socket API 的所有参数、返回值、错误处理和完整 C++ 示例见 2_Socket基础.md。
网络字节序与文本地址
网络协议中的多字节整数通常使用大端序(网络字节序)。端口号和自定义协议中的长度字段不能直接按本机内存布局发送。
| 函数 | 用途 |
|---|---|
htons() / ntohs() |
16 位主机序与网络序转换;常用于端口 |
htonl() / ntohl() |
32 位主机序与网络序转换;常用于长度字段 |
inet_pton() |
文本 IP 转为二进制地址;不能解析域名 |
inet_ntop() |
二进制 IP 转为文本 |
1 |
|
inet_pton() 返回 1 才表示转换成功;生产代码应检查返回值。需要解析域名时使用 getaddrinfo(),不要把域名直接传给 inet_pton()。
阻塞、超时与并发
默认 Socket 通常是阻塞的:没有数据时 recv() 会等待;没有待处理连接时 accept() 会等待。阻塞并不一定有问题,但一个慢客户端可能长期占用处理线程。
常见演进路径:
1 | 少量连接:阻塞 I/O |
无论是哪种模型,都应设置连接、读取、写入和业务处理超时;否则网络异常、对端静默或慢速攻击可能长期占用资源。
I/O 模型、非阻塞处理与多路复用见 IO模型.md、IO多路复用.md;服务器架构见 服务器并发模型.md。
常见故障排查思路
| 现象 | 优先检查 |
|---|---|
connect() 被拒绝(ECONNREFUSED) |
目标 IP 是否正确、服务是否正在监听该端口、监听地址是否仅为 127.0.0.1 |
连接超时(ETIMEDOUT) |
路由是否可达、防火墙/安全组、NAT/端口映射、目标主机是否在线 |
bind() 返回 EADDRINUSE |
端口是否被其他进程占用、旧连接状态、是否需要在正确位置设置 SO_REUSEADDR |
| 域名解析失败 | DNS 配置、域名拼写、网络连通性、getaddrinfo() 返回的具体错误 |
| 能连接但收不到完整数据 | 是否错误假设一次 recv() 就是一条消息;是否缺少协议边界和循环读取 |
| 服务端被慢客户端拖住 | 是否使用阻塞 I/O 且每连接独占线程;是否缺少读写超时、连接上限和背压 |
网络编程的核心可以概括为:
1 | IP 地址:找到目标主机 |



