

目录
[3.1 大小端(字节序)](#3.1 大小端(字节序))
[3.2 网络字节序](#3.2 网络字节序)
[4.1 创建套接字](#4.1 创建套接字)
[4.2 地址绑定](#4.2 地址绑定)
[4.3 关闭套接字](#4.3 关闭套接字)

一、认识IP地址
IP 地址可以简单理解成设备在网络上的 "门牌号"。
不管是手机、电脑、路由器,只要想连上网和其他设备通信,就必须有一个独一无二的 IP 地址。就像你寄快递需要写清楚收件人的详细地址,网络里的数据要准确传到目标设备,也得靠这个 "门牌号" 来定位。
举个例子:你用手机连 Wi-Fi 刷视频,视频数据就是从视频平台的服务器,顺着网络根据 IP 地址,精准传到你的手机上的;如果没有这个地址,数据就会像没头苍蝇一样,不知道该往哪儿去。
同时,IP地址可以分为两种类型:
- IPv4:32 位二进制数,转换成十进制就是我们看到的四段式格式,总地址数约 43 亿个,早已不够用,所以才需要 NAT 技术来缓解。
- IPv6 :128 位二进制数,格式是八组十六进制数,地址数量近乎无限,能满足所有智能设备联网需求。技术上 IPv6 支持无状态地址自动配置,设备可以自己生成 IP。
| 维度 | IPv4 | IPv6 | 联系 |
|---|---|---|---|
| 地址长度 | 32 位,点分十进制(如192.168.1.1) |
128 位,冒分十六进制(如2001:0db8::1) |
均是网络设备的逻辑标识,用于定位和通信 |
| 地址数量 | 约 43 亿(2^32),已耗尽 |
约3.4×10^38,足够全球设备使用 |
都是 IP 协议的版本,服务于网络层地址标识 |
| 地址类型 | 公网 IP、私网 IP(如192.168.x.x) |
全球单播、链路本地(如fe80::)、唯一本地等 |
都区分 "公网 / 私网" 类地址,适配不同网络场景 |
| 子网掩码 | 需配合子网掩码(如255.255.255.0)划分网段 |
直接通过前缀长度(如2001:0db8::/64)划分 |
均用于区分地址的 "网络段" 和 "主机段" |
| 配置方式 | 手动配置 / DHCP 分配 | 自动配置(无状态 / 有状态)、手动配置 | 都支持动态 / 静态配置,适配不同网络管理需求 |
| 安全性 | 无原生安全机制,需依赖 IPsec 扩展 | 原生支持 IPsec,内置加密 / 认证功能 | 均支持 IPsec 实现安全通信,IPv6 更原生 |
| 头部结构 | 头部字段多(如校验和、选项),可变长 | 头部字段简化(固定 40 字节),无校验和 | 都包含 "源地址、目标地址、版本号" 等核心字段 |
| 兼容性 | 目前互联网主流协议 | 逐步替代 IPv4,需通过过渡技术(如隧道、双栈)互通 | 需通过过渡技术(如 NAT64、双栈)实现两者互通 |
二、认识端口号
在一个主机中可能存在各种各样的网络进程,这些进程时时刻刻都在通过网络与对应的服务器进行信息交互。前面我们了解了IP地址器可以确定唯一一台网络主机,在网络通信的角度,将消息(网络报文)从一台主机发送到另一台主机并不是目的,真正目的是要将对应的网络消息交给指定的网络进程(比如QQ的聊天消息应该传递给QQ进程才能正确解析,要是传递错误其他进程就会不能正确解析消息的表达意思),这些进程会对对应的数据报文进行正确的处理解析。

但是如何确定唯一的一个网络进程呢?我们引入了端口号的概念:
端口号的概念:同一台设备上,区分不同网络应用程序的数字标识。
它和 IP 地址是 "搭档" 关系 ------
- IP 地址的作用是定位到网络中的某一台设备;
- 端口号的作用是定位到这台设备里的某一个网络程序。
本质是逻辑标识端口号不是设备上的物理接口,而是操作系统为每个正在进行网络通信的程序分配的逻辑编号,范围是0~65535(16 位整数)。操作系统会通过这个编号,把收到的网络数据准确 "投递" 给对应的程序。
在一个网络进程在开始网络通信之前会绑定一个专属的端口号(一个端口号只能绑定一个网络进程),当该进程进行网络通信的时候会将该端口号和本主机的IP地址一起封装进报文并发送给对端服务器或另一个网络进程,对端收到报文后会明白两个信息:
- 数据的具体内容
- 数据的来源(IP+端口号)
当这个服务器或网络进程进行回复的时候也会将自己的IP+端口号封装进报文并根据地址信息准确发送到对端进程。

端口号有以下几种类型:
- 知名端口(0~1023):固定分配给通用网络服务,由国际组织管理。比如 HTTP 服务用 80、HTTPS 用 443、SSH 远程连接用 22,程序一般不能随意占用这类端口。
- 注册端口(1024~49151):可以被用户程序或第三方服务注册使用,避免和知名端口冲突,比如 MySQL 数据库默认用 3306。
- 动态 / 私有端口(49152~65535):普通应用临时使用的端口,程序启动时由操作系统随机分配,通信结束后就会释放,比如浏览器访问网站时的临时端口。
三、网络字节序
3.1 大小端(字节序)
大小端是计算机存储多字节数据时的字节排列顺序,核心是高位字节和低位字节的存放位置差异。我们以 16 进制数 0x1234abcd(4 字节整数)为例,高位字节是 0x12,低位字节是 0x78。

小端序(Little-Endian)存储规则:
- 低位字节在前,高位字节在后。
- 常见场景:x86/x64 架构的 CPU(比如电脑上的 Intel、AMD 处理器)默认用小端序,是目前最主流的字节序。
大端序(Big-Endian)存储规则:
- 高位字节在前,低位字节在后。
补充:还有一种 混合序(Middle-Endian),很少见,一般不用关注。
3.2 网络字节序
了解了大小端的概念之后我们想象一下这种这种场景:如果网络通信的两台电脑一台默认是小端存储一种默认是大端存储那么如果直接传输数据就会导致数据解析不一致的问题。为了避免这种问题,我们规定要进行网络发送和处理的数据在传输过程中一律使用大端序,也就是统一字节序。
这样规定之后,当自己的机器是大端序时就不会再对传输过来的数据进行额外处理,而如果是小端序的机器时就必须将数据转换为小端序;同样的小端序的机器要进行网络传输时首先也需将数据转化为大端序。
四、socket网络通信接口初识
套接字(Socket)是计算机网络中用于实现进程间通信(IPC)的一种抽象机制,它是应用层与传输层协议之间的接口,也是网络编程的核心概念。简单来说,套接字可以理解为两个网络应用程序之间进行通信的 "端点" 或 "句柄",通过它可以在不同主机(或同一主机)的进程之间建立连接、发送和接收数据。
在 Linux/Unix 系统中,套接字是一种特殊的文件描述符 (File Descriptor),遵循 "一切皆文件" 的设计理念。操作系统将网络通信抽象为文件的读写操作,因此对套接字的操作(如数据发送 / 接收)可以复用文件 I/O 的系统调用(如read/write),简化了编程接口。
在功能上,套接字可以完成同一主机中的进程之间的通行,也可以完成网络上不同主机之间进程的通信:
- 网络套接字(跨主机通信):基于 TCP/UDP 协议,通过IP 地址 + 端口号标识远程主机的进程。例如,客户端套接字通过服务器的 IP 地址和端口号发起连接,服务器套接字绑定固定的 IP 和端口监听连接请求。
- 本地套接字(同一主机通信):基于
AF_UNIX协议族,通过文件系统路径标识进程(如/tmp/socket.sock),效率高于网络套接字,适用于同一主机内的进程间通信(如 Linux 系统中 MySQL 的本地连接)
所以在编程创建一个套接字时,我们必须指定该套接字要实现的功能(跨主机通信还是同一主机通信)和方法(跨主机通信的话基于 TCP还是UDP 协议)。
下面我们来了解一下套接字创建的常用接口:
4.1 创建套接字
cpp
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:地址族(协议族),常用AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNIX(本地域套接字)。type:套接字类型,SOCK_STREAM(TCP,面向连接、可靠、字节流)、SOCK_DGRAM(UDP,无连接、不可靠、数据报)、SOCK_RAW(原始套接字,用于自定义协议)。protocol:协议类型,通常为 0(自动根据type选择对应协议,如 TCP 对应IPPROTO_TCP,UDP 对应IPPROTO_UDP)。
返回值 :成功返回套接字文件描述符(非负整数),失败返回-1并设置errno。
4.2 地址绑定
将套接字绑定到指定的 IP 地址和端口号。
cpp
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket()创建的套接字文件描述符。addr:指向struct sockaddr或struct sockaddr_in(IPv4)/struct sockaddr_in6(IPv6)的指针,包含要绑定的地址和端口信息。addrlen:地址结构体的长度。
这里我们需要了解一下struct sockaddr这个结构体:
前面我们说过套接字可以完成同一主机中的进程之间的通行,也可以完成网络上不同主机之间进程的通信,当我们利用套接字完成网络通信时必须绑定IP与端口号,而完成本地进程间通信时需要绑定指定的路径(如/tmp/my_sock),客户端则通过这个路径连接到服务端。
利用套接字完成网络通信时需要使用struct sockaddr_in或者struct sockaddrin6结构体绑定IP与端口号,以struct sockaddr_in为例,其中有以下几个成员:
cpp
#include <netinet/in.h>
#include <arpa/inet.h> // 包含地址转换函数相关声明
struct sockaddr_in {
sa_family_t sin_family; // 地址族(必须为AF_INET,标识IPv4)
in_port_t sin_port; // 端口号(网络字节序,需用htons()转换)
struct in_addr sin_addr; // IPv4地址(网络字节序,用in_addr结构体封装)
char sin_zero[8]; // 填充字段,置0以兼容struct sockaddr的长度
};
// 嵌套的in_addr结构体(存储IPv4地址的32位二进制值)
struct in_addr {
uint32_t s_addr; // IPv4地址(网络字节序,可用inet_addr()/inet_pton()转换)
};
其中 sin_port与sin_addr中的s_addr成员就是我们需要绑定的端口号与IP地址(注意这里的IP地址为网络字节序,绑定时需要在外部转化好再绑定),sin_zero[8]为填充字段用来维持结构体的固定大小。
利用套接字完成本地进程间通信时需要利用sockaddr_un结构体来绑定指定的路径,sockaddr_un的具体结构如下:
cpp
struct sockaddr_un {
sa_family_t sun_family; // 地址族,固定为AF_UNIX(本地域)
char sun_path[108];// 套接字对应的文件系统路径(如"/tmp/mysocket")
};
但是为了统一接口,在绑定地址信息时用户首先需要根据套接字的功能自己利用指定的结构体绑定地址信息(网络通信使用struct sockaddr_in或者struct sockaddrin6结构体绑定IP与端口号,本地通信利用sockaddr_un结构体来绑定指定的路径)后将其强制类型转化为struct sockaddr *后再传入bind函数完成地址绑定。

举个例子,网络通信套接字完成IP端口号的绑定流程:
cpp
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(8080);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 强制转换为struct sockaddr*
bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
4.3 关闭套接字
cpp
#include <unistd.h>
int close(int fd);
功能:关闭套接字文件描述符,释放资源(默认会等待发送缓冲区数据发送完成)。
返回值 :成功返回0,失败返回-1并设置errno。