一、IP 核心基础
1. 什么是 IP 地址?一句话讲透本质
本质是:给接入互联网的每一台设备(电脑、服务器、手机、路由器)分配的一个「唯一网络身份标识」,也可以理解为网络设备的「门牌号」。
所有网络数据的传输(比如你用 C++ 写的客户端给服务器发消息、浏览器访问网页、两台电脑局域网通信),核心逻辑都是:通过「源 IP」找到「目的 IP」,把数据精准送达。没有 IP 地址,网络中所有设备都是孤立的,无法完成任何通信。
2. IPv4 地址
目前主流使用的是 IPv4 协议(IPv6 是趋势,但实战开发中 IPv4 仍是绝对主流),也是本文的核心讲解对象,小白必须吃透。
- IPv4 地址的底层本质:32 位二进制数 ,比如
11000000.10101000.00000001.00000001 - 为什么我们看到的是
192.168.1.1?为了人类易读,把 32 位二进制拆成4 个 8 位的二进制段 ,每个段转成「0~255」的十进制数,段之间用.分隔,这种格式叫做「点分十进制」。
每个段的取值范围是 0~255,超出这个范围的一定是非法 IP(比如 192.168.1.256),C++ 实战中做 IP 校验的核心规则之一。
3. IPv4 地址的分类
32 位的 IPv4 地址,分为「网络位 」和「主机位 」:网络位标识「哪个网段」,主机位标识「该网段下的哪台设备」。根据网络位的长度,IPv4 分为 A、B、C、D、E 五类,只需要掌握核心的 3 类即可,考点 + 重点如下:
- A 类地址:1~126.x.x.x ,网络位占 8 位,主机位 24 位,用于大型网络(比如国家骨干网)
- B 类地址:128~191.x.x.x,网络位占 16 位,主机位 16 位,用于中型企业 / 机构
- C 类地址:192~223.x.x.x,网络位占 24 位,主机位 8 位,最常用(局域网、家庭路由器、公司内网全是这类)
- D 类(224~239):组播地址,E 类(240~255):保留地址,只需要知道名称,不用深究。
4. 公有 IP & 私有 IP
- 私有 IP:局域网内部使用的 IP 地址,不能在公网上通信 ,是所有开发者本地调试代码的标配,3 个固定网段:
- 10.0.0.0 ~ 10.255.255.255
- 172.16.0.0 ~ 172.31.255.255
- 192.168.0.0 ~ 192.168.255.255 最常用,你的路由器、本地电脑的 IP 基本都是这个网段
- 公有 IP:互联网上的唯一合法 IP,由运营商分配,比如阿里云服务器的 IP、百度官网的 IP,公网设备之间通信必须用公有 IP。
C++ 写本地客户端 / 服务端程序时,绑定的 IP 都是私有 IP(比如 127.0.0.1 本机回环地址、192.168.1.100);部署到云服务器后,绑定的是公有 IP。
5. 网关 & 子网掩码
- 子网掩码:配合 IP 地址,划分「网络位」和「主机位」 ,比如常见的
255.255.255.0对应 C 类地址,意思是前 3 段是网络位,最后 1 段是主机位。 - 网关:局域网的「出口」,比如你的电脑要访问百度,数据先发给网关(路由器 IP,一般是 192.168.1.1),再由网关转发到公网。
二、IP 核心知识点
1. 什么是字节序?
字节序:多字节数据在计算机内存中的存储顺序 ,比如一个 32 位的 IP 整数 0x12345678,占 4 个字节,不同 CPU 的存储方式不同,只有两种:
- 小端序(Little-Endian):低字节存低地址,高字节存高地址 → 所有 x86/x86_64 架构的 CPU(Intel、AMD)都是小端序,你的电脑、服务器基本都是小端序。
- 大端序(Big-Endian):高字节存低地址,低字节存高地址 → 也叫「网络字节序」,IP/TCP/UDP 等所有网络协议,统一规定使用大端序传输数据。
2. 为什么 C++ 开发者必须掌握?
核心原因:本地主机是小端序,网络传输是大端序。如果你的 C++ 程序直接把本地的 IP 地址、端口号「原封不动」发给网络,对方收到的会是乱码数据,通信直接失败!
比如:你在本地用 C++ 定义端口号 8080(对应十六进制 0x1F90),小端序存储是 0x90 0x1F,如果不转换直接发,对方收到的是 0x901F 对应端口号 36927,完全错误。
3. C++ 中处理字节序的 4 个核心系统函数
所有 C++ 网络编程,只要涉及 IP / 端口的网络传输,都必须用这 4 个函数做「主机序 ↔ 网络序」的转换,函数在 <arpa/inet.h> 头文件中,Linux/Windows 均兼容,记死 + 吃透:
cpp
#include <arpa/inet.h>
// 1. 主机32位整数 → 网络32位整数(用于IP地址转换)
uint32_t htonl(uint32_t hostlong);
// 2. 主机16位整数 → 网络16位整数(用于端口号转换,端口是16位)
uint16_t htons(uint16_t hostshort);
// 3. 网络32位整数 → 主机32位整数(接收IP时转换)
uint32_t ntohl(uint32_t netlong);
// 4. 网络16位整数 → 主机16位整数(接收端口时转换)
uint16_t ntohs(uint16_t netshort);
3. 两种形式的区别与使用场景
- 字符串形式:
192.168.1.1→ 人类易读,用户输入、日志打印都用这个形式。 - 32 位整数形式:
0xC0A80101→ 计算机易处理,C++ 网络编程中所有 IP 的底层运算、传输、存储,都是用 32 位无符号整数,这是核心规则!
所以:C++ 开发中,必须掌握「IP 字符串 ↔ 32 位整数」的双向转换,这是 IP 编程的基础.
4. C++ 中 IP 转换的核心函数
所有函数均在 <arpa/inet.h> 头文件中,Linux/Windows 通用,分为「旧版」和「新版」,全部要会用:
方案 1:旧版函数
cpp
#include <arpa/inet.h>
// ① IP字符串 → 32位网络序整数 (核心)
in_addr_t inet_addr(const char *cp);
// ② 32位网络序整数 → IP字符串 (核心)
char *inet_ntoa(struct in_addr in);
inet_addr返回的是「网络序的 32 位整数」,不用再调用 htonl 转换;inet_ntoa传入的是in_addr结构体(内部就是 32 位网络序 IP),返回的是点分十进制字符串。
方案 2:新版函数
旧版函数有缺陷(比如inet_ntoa是线程不安全的),C++11 之后推荐使用新版函数
cpp
#include <arpa/inet.h>
// ① 任意IP格式 → 网络序整数(IPv4填AF_INET,IPv6填AF_INET6)
int inet_pton(int af, const char *src, void *dst);
// ② 网络序整数 → 任意IP格式字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
旧版仅支持 IPv4,有线程安全问题;新版兼容 IPv4/IPv6,线程安全,推荐使用。
5. sockaddr_in 结构体
核心地位
在 C++ 的 socket 网络编程中,所有的「IP 地址」+「端口号」的组合,都必须封装到 sockaddr_in 结构体中,没有例外!不管是绑定本地 IP + 端口、连接远程服务器 IP + 端口、接收客户端 IP + 端口,全靠这个结构体。
结构体定义 + 核心成员解析
cpp
#include <netinet/in.h>
// IPv4专用的地址结构体
struct sockaddr_in {
sa_family_t sin_family; // 地址族,固定填 AF_INET (标识IPv4)
uint16_t sin_port; // 端口号,必须是 网络序 (要调用htons转换)
struct in_addr sin_addr; // IP地址,内部是32位网络序整数(in_addr_t)
char sin_zero[8]; // 填充位,无意义,填0即可
};
// 嵌套的IP地址结构体
struct in_addr {
in_addr_t s_addr; // 核心:32位网络序的IPv4地址整数
};
结构体赋值的标准写法
赋值顺序:填地址族 → 转端口为网络序 → 转 IP 为网络序:
cpp
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr)); // 初始化结构体,所有成员置0
addr.sin_family = AF_INET; // 固定:IPv4
addr.sin_port = htons(8080); // 端口号:主机序 → 网络序
// 方式1:直接赋值IP(字符串转网络序整数)
addr.sin_addr.s_addr = inet_addr("192.168.1.1");
// 方式2:新版函数赋值IP(推荐)
inet_pton(AF_INET, "192.168.1.1", &addr.sin_addr);
三、IP 相关核心代码
1. IP 字符串 ↔ 32 位整数 互转
cpp
#include <iostream>
#include <arpa/inet.h>
#include <cstring>
using namespace std;
// IP字符串 -> 32位主机序整数
uint32_t ip_str_to_uint(const char* ip) {
uint32_t net_ip = inet_addr(ip); // 得到网络序整数
return ntohl(net_ip); // 转为主机序,方便打印/运算
}
// 32位主机序整数 -> IP字符串
string ip_uint_to_str(uint32_t ip_uint) {
uint32_t net_ip = htonl(ip_uint); // 转为网络序
struct in_addr addr;
addr.s_addr = net_ip;
return string(inet_ntoa(addr)); // 转为字符串
}
int main() {
const char* ip = "192.168.1.100";
// 转换测试
uint32_t ip_uint = ip_str_to_uint(ip);
cout << "IP字符串: " << ip << " → 32位整数: " << ip_uint << endl;
cout << "32位整数: " << ip_uint << " → IP字符串: " << ip_uint_to_str(ip_uint) << endl;
return 0;
}
编译运行:
g++ -o ip_convert ip_convert.cpp && ./ip_convert运行结果:
192.168.1.100 → 3232235876,反向转换一致,验证正确。
2. 校验 IP 地址是否合法
开发中用户输入的 IP 可能是非法的(比如 192.168.1.256、256.0.0.1、192.168.1),必须做合法性校验
cpp
#include <iostream>
#include <arpa/inet.h>
#include <cstring>
using namespace std;
// 方案1:调用系统函数(推荐,简洁高效,实战首选)
bool is_ip_valid(const char* ip) {
struct sockaddr_in addr;
return inet_pton(AF_INET, ip, &addr.sin_addr) == 1;
}
// 方案2:手动实现(面试常考,考察基础功底,纯C++逻辑)
bool is_ip_valid_manual(const char* ip) {
int count = 0; // 统计点的个数,必须是3个
int num = 0; // 统计每个段的数值
for (int i = 0; ip[i] != '\0'; i++) {
if (ip[i] == '.') {
count++;
if (count > 3 || num <0 || num >255) return false;
num = 0;
} else if (isdigit(ip[i])) {
num = num *10 + (ip[i]-'0');
if (num >255) return false; // 超过255直接非法
} else {
return false; // 包含非数字字符
}
}
// 最后一个段校验 + 点的个数必须是3
return count ==3 && num >=0 && num <=255;
}
int main() {
const char* ip1 = "192.168.1.1";
const char* ip2 = "192.168.1.256";
cout << ip1 << " 合法吗?" << (is_ip_valid(ip1) ? "是" : "否") << endl;
cout << ip2 << " 合法吗?" << (is_ip_valid(ip2) ? "是" : "否") << endl;
return 0;
}
3. Socket 绑定 IP + 端口
cpp
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <cstring>
#include <unistd.h>
using namespace std;
int main() {
// 1. 创建TCP socket
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) { perror("socket error"); return -1; }
// 2. 初始化sockaddr_in结构体,绑定IP+端口
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET; // IPv4地址族
server_addr.sin_port = htons(8080); // 端口:主机序→网络序
server_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 绑定本机所有IP(0.0.0.0的特殊含义)
// 3. 绑定操作
if (bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) <0) {
perror("bind error");
close(fd);
return -1;
}
cout << "成功绑定IP: 0.0.0.0, 端口: 8080" << endl;
close(fd);
return 0;
}
0.0.0.0的含义 ------ 绑定本机所有的网卡 IP,客户端可以通过本机的任意 IP(127.0.0.1、192.168.1.100)访问该服务,这是开发中的标配写法。