一、网络通信的底层逻辑:协议栈与封装
数据如何传输?通过分层协作实现。就像快递送货,每层只负责特定环节,不越界。
1. Linux 网络协议栈分层
- :给数据定格式,如 HTTP、SSH、FTP。
Linux 网络通信基于分层架构,涵盖应用层至物理层。文章详解了 IP 地址与端口作为通信端点的定位作用,介绍了 ip、ping、tcpdump 等 Linux 网络管理工具。重点阐述了 TCP 协议的六大核心机制:三次握手建立连接、四次挥手断开连接、序号确认与重传保障可靠性、滑动窗口进行流量控制以及拥塞控制适应网络状态。对比了 TCP 与 UDP 在连接性、可靠性及速度上的差异,并说明了应用程序通过 socket 调用内核协议栈的数据流转路径。

数据如何传输?通过分层协作实现。就像快递送货,每层只负责特定环节,不越界。
在 Socket 网络编程(IPv4 场景)中,sockaddr_in结构体的 sin_addr字段以 32 位二进制形式存储 IP 地址,但日常习惯用点分十进制字符串表示,因此需要借助地址转换函数实现互转。
需引入头文件 <arpa/inet.h>。
1. 字符串转 in_addr
inet_aton(const char *strptr, struct in_addr *addrptr):将点分十进制字符串转为 in_addr 结构体,成功返回 1,失败返回 0。inet_addr(const char *strptr):直接返回转换后的 32 位整数形式 IP,无法区分转换失败与 0.0.0.0。inet_pton(int family, const char *strptr, void *addrptr):更通用的函数(支持 IPv4/IPv6),family 传 AF_INET 表示 IPv4。2. in_addr 转字符串
inet_ntoa(struct in_addr inaddr):将 in_addr 结构体转为点分十进制字符串,但返回的是静态缓冲区,多线程场景下可能被覆盖。inet_ntop(int family, const void *addrptr, char *strptr, size_t len):更安全的通用函数,需手动传入缓冲区存储结果,避免线程安全问题。
发送方流程:应用层生成原始数据 -> 传输层加头(源/目的端口)变成报文段 -> 网络层加头(源/目的 IP)变成数据报 -> 数据链路层加头和尾(MAC 地址)变成以太网帧 -> 物理层变成电信号发送。 接收方反向操作:拆帧 -> 拆数据报 -> 拆报文段 -> 拿到原始数据。
以下展示 C++ 网络编程中封装 IP 与端口处理的 InetAddr 类代码示例:
#include <arpa/inet.h>
#include <string>
#include <cstdint>
#include <cstring>
class InetAddr {
public:
// 构造函数 1:从 sockaddr_in(网络字节序)初始化
InetAddr(const struct sockaddr_in& addr) : _addr(addr) {
// 网络字节序端口 -> 主机字节序
_port = ntohs(addr.sin_port);
// 网络字节序 IP -> 点分十进制字符串
_ip = inet_ntoa(addr.sin_addr);
}
// 构造函数 2:从 IP 字符串(主机序)和端口(主机序)初始化
InetAddr(const std::string& ip, uint16_t port) : _ip(ip), _port(port) {
memset(&_addr, 0, sizeof(_addr));
_addr.sin_family = AF_INET;
// 主机字节序端口 -> 网络字节序
_addr.sin_port = htons(port);
// 点分十进制 IP 字符串 -> 网络字节序
inet_pton(AF_INET, ip.c_str(), &_addr.sin_addr);
}
// 获取主机字节序的端口
uint16_t Port() const { return _port; }
// 获取点分十进制的 IP 字符串
std::string Ip() const { return _ip; }
// 获取 sockaddr_in 结构体(网络字节序)
const struct sockaddr_in& GetSockAddr() const { return _addr; }
private:
struct sockaddr_in _addr; // 网络字节序的地址结构体
std::string _ip; // 主机序:点分十进制 IP 字符串
uint16_t _port; // 主机序:端口号
};
IP 地址类似小区地址,端口号类似门牌号。只有同时知道这两个,数据才能准确送到目标进程。
"IP+ 端口"= 唯一通信端点(如 192.168.1.1:80)。在网络编程中,inet_ntoa 存在静态缓冲区覆盖问题,建议使用 inet_ntop 替代。

| 工具 | 核心功能 | 常用命令示例 | 说明 |
|---|---|---|---|
| ip addr | 查看/配置 IP 地址 | ip addr add 192.168.1.100/24 dev eth0 | 给网卡加 IP,/24 是子网掩码 |
| ip route | 查看/配置路由表 | ip route add default via 192.168.1.1 | 设置默认网关 |
| ping | 测试主机连通性 | ping -c 4 www.baidu.com | 发测试包看延迟和通断 |
| ss/netstat | 查看网络连接状态 | ss -tulnp | 查哪个进程占用了端口 |
| tcpdump/Wireshark | 网络抓包分析 | tcpdump -i eth0 port 80 | 捕获指定网卡端口的数据 |
Windows 平台需引入 WinSock2.h 头文件,并通过 #pragma comment(lib, "ws2_32.lib") 链接库,且需调用 WSAStartup 初始化套接字库,这是 Windows 平台独有的步骤,Linux 无需此操作。
跨平台 UDP 通信示例显示,客户端填写服务器 IP 和端口创建套接字发送数据,服务器日志可显示客户端消息,说明通信已建立。

TCP 保证数据不丢、不错、不乱,核心机制如下。
TCP 报文由固定头部(20 字节)+ 选项 + 数据组成。核心字段包括序号/确认号(保证可靠有序)、窗口大小(流量控制)、标志位(SYN、ACK、FIN、RST)。
为什么是三次?两次无法确认双方都能收发,四次多余。流程如下:
初始化 socket 地址时,需清空结构体,指定协议族 AF_INET,绑定所有本地 IP(INADDR_ANY),并将端口转换为主机字节序。

TCP 是全双工通信,关闭连接需分两步:先关发送通道,再关接收通道。
接收方告诉发送方还能装多少数据(窗口大小),发送方只发窗口内的数据,避免接收方缓冲区溢出。
考虑网络链路能力,核心是拥塞窗口(cwnd)。
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠 | 不可靠 |
| 有序性 | 保证有序 | 不保证有序 |
| 速度 | 慢 | 快 |
| 适用场景 | HTTP、MySQL | 直播、DNS |

写网络程序本质是调用内核接口(socket),让内核处理 TCP/IP 细节。
应用程序 → socket 层(应用与内核接口)→ inet 层(处理 TCP/UDP)→ IP 层(处理 IP 地址)→ 数据链路层(处理 MAC 地址)→ 网卡(发送数据)。 理解这一路径有助于排查网络问题,例如通过抓包工具分析各层数据是否正常,从而定位是应用层逻辑错误还是内核网络栈异常。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online