✧(≖ ◡ ≖✿
目录
在同一台电脑上运行服务器和客户端(常见于开发调试),服务器绑定 0.0.0.0 都行,客户端的目标IP直接写 内网IP,本地回环,公网IP都可以吗?原因是什么
设计原则:类设计Init()中socket()是必要,构造函数内_port是服务器需求使然。
认识UDP、TCP协议
一、UDP 是什么?(一句话定义)
UDP(User Datagram Protocol,用户数据报协议)是传输层的一种无连接、不可靠、面向数据报的协议。它只在 IP 之上增加了端口号(用于区分进程)和简单的校验和,不保证数据一定到达、不保证顺序、不保证不重复。
二、UDP 的核心特点(对比 TCP)
| 特性 | UDP | TCP |
|---|---|---|
| 连接状态 | 无连接(发送前不需要握手) | 面向连接(三次握手) |
| 可靠性 | 不可靠(不保证送达) | 可靠(超时重传、确认机制) |
| 顺序性 | 不保证顺序 | 保证按序到达 |
| 拥塞控制 | 无(发送速率不受网络限制) | 有(滑动窗口、慢启动) |
| 流量控制 | 无 | 有 |
| 数据边界 | 面向数据报(一次发送一个完整报文) | 面向字节流(无边界) |
| 校验和 | 有(可选,但通常开启) | 有(必须) |
| 开销 | 头部 8 字节 | 头部 20-60 字节 |
| 传输速度 | 快 | 慢(相对) |
网络字节序
即信息在网络中必须以大端形式传输。
网络、主机转换四大接口(常用于网络中端口的转接)
#include<arpa/inet.h>
//主机转网络
uint16_t htons(uint16_t hostShort);
uint32_t htonl(uint32_t hostLong);
//网络转主机
uint16_t ntohs(uint16_t netShort);
uint32_t ntohl(uint32_t netLong);
☆☆判断网络转主机还是主机转网络的关键是什么?
凡是你要“塞给”内核(或网络)的,用 hton(主机转网络);还是内核(或网络)“塞给你”的,用 ntoh(网络转主机)。
struct sockaddr_in local
使用struct sockaddr_in必须包含额外的头文件:
#include<netinet/in.h>
struct sockaddr_in local; 这行代码,是网络编程中最核心的地址结构体定义。它定义了一个名为 local 的变量,专门用来存储 IPv4 套接字地址(即 IP 地址 + 端口号)。
// 在 <netinet/in.h> 中定义
struct sockaddr_in {
sa_family_t sin_family; // 地址族,必须设置为 AF_INET
in_port_t sin_port; // 端口号(网络字节序)
struct in_addr sin_addr; // IPv4 地址(网络字节序)
unsigned char sin_zero[8]; // 填充字段,确保与 struct sockaddr 大小一致
};
// 其中 struct in_addr 的定义:
struct in_addr {
uint32_t s_addr; // IPv4 地址(32位,网络字节序)
};
成员变量详解
| 字段 | 类型 | 含义 | 必须设置的值 | 示例 |
|---|---|---|---|---|
sin_family | sa_family_t(通常是 unsigned short) | 地址族,标识这是 IPv4 地址 | AF_INET | local.sin_family = AF_INET; |
sin_port | in_port_t(通常是 uint16_t) | 端口号(16位),必须用 htons() 转为网络字节序 | 你想要的端口号(如 8888) | local.sin_port = htons(8888); |
sin_addr | struct in_addr | IP 地址(32位),若指定必须用 htonl() 或 inet_pton() 转为网络字节序 | 你的 IP 或 INADDR_ANY | local.sin_addr.s_addr = INADDR_ANY; |
sin_zero | unsigned char[8] | 填充字段,只为保证结构体大小一致(16字节),通常用 memset 清零 | 0 |
|
struct sockaddr的继承体系

公网、内网、本地环回
公网IP
![]()
内网、本地环回

四大接口简解(基于Udp的实现)
套接字socket()创建的含义
向内核申请一个“网络通信端点”,返回一个文件描述符(fd),后续所有网络操作都通过它来标识这个通信通道。
进行bind()的含义
把套接字与一个本地 IP 地址和端口号关联起来,告诉内核“这个套接字负责接收发往这个 IP:端口 的数据”。
recvfrom的含义
从套接字的接收缓冲区中取出一个 UDP 数据报,同时把发送方的 IP 地址和端口号返回给调用者。
sendto()的含义
把数据封装成 UDP 数据报,发送到指定的目标 IP 地址和端口。
在服务器客户端分别实现时IP与端口号的指定情况是什么?
这是一个非常核心的网络编程概念问题。我直接给你最清晰、最实操的答案。
核心结论:
-
服务器(Server):必须明确指定自己的IP和端口(尤其是端口)。
-
客户端(Client):必须指定服务器的IP和端口;但通常不需要指定自己的IP和端口(由操作系统自动分配)。
同一台机器
服务端IP:IADDR_ANY
客户端IP:指定自己的本地IP。(ifconfig)
不同台机器
服务端IP:INADDR_ANY
客户端IP:指定服务端机器本身的IP+规定端口。
1. 服务器端(被动等待连接/数据)
服务器是“被找到”的一方,所以必须有一个固定的“门牌号”。
-
IP地址:必须明确。
-
如果你想只允许本机访问(测试用),绑定
127.0.0.1。 -
如果你想让同一局域网(WiFi/有线)内的其他机器访问,绑定你查到的内网IP(如
192.168.1.100)。 -
如果你想让本机所有网卡(包括127.0.0.1和192.168.1.100)都能访问,绑定
0.0.0.0(INADDR_ANY)。这是服务器最常用的写法,代码通用性最强。
-
-
端口号:必须明确指定(如
8080、8888)。这是操作系统区分不同服务的唯一标识。
代码体现(UDP Server绑定):
当不指定时自然可以接受任何IP的链接
// 服务器的固定据点
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 或者 inet_addr("192.168.1.100")
server_addr.sin_port = htons(8888); // 固定端口,此处明确
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
2. 客户端(主动发起请求)
客户端是“主动寻找”的一方。它只需要知道“我要找谁”,而不需要关心“我是谁”。
-
必须指定(目标):服务器的IP 和 服务器的端口号。这是你连接的目标。
-
无需指定(自身):客户端自己的IP和端口号完全可以不写。
-
当你在客户端调用
sendto()或connect()之前,如果没有手动bind(),操作系统内核会自动帮你随机挑选一个空闲端口(比如52341)作为源端口,并自动选择合适的网卡IP作为源IP。 -
只有一种情况需要客户端手动绑定:如果你的程序运行在防火墙严格限制的机器上,必须使用固定端口通信。
-
代码体现(UDP Client发送数据):
// 明确指定要“找谁”(服务器)
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = inet_addr("192.168.1.100"); // 服务器的IP
server_addr.sin_port = htons(8888); // 服务器的端口
// 自己的IP和端口?不需要!直接发,系统会自动分配
sendto(sock_fd, buffer, len, 0, (struct sockaddr*)&server_addr, sizeof(server_addr));
3. 特殊情况:同一台机器上测试
如果你是在同一台电脑上运行服务器和客户端(常见于开发调试),服务器绑定 127.0.0.1 或 0.0.0.0 都行,客户端的目标IP直接写 127.0.0.1。
4. 一张图总结对比
| 角色 | 本机IP(自己) | 本机端口(自己) | 目标IP(对方) | 目标端口(对方) |
|---|---|---|---|---|
| 服务器 (Server) | 必须指定(固定) 常用 INADDR_ANY | 必须指定(固定) 如 8888 | 无需关心 | 无需关心 |
| 客户端 (Client) | 无需指定 (系统自动分配) | 无需指定 (系统自动分配随机端口) | 必须指定 (服务器的IP) | 必须指定 (服务器的端口) |
在同一台电脑上运行服务器和客户端(常见于开发调试),服务器绑定 0.0.0.0 都行,客户端的目标IP直接写 内网IP,本地回环,公网IP都可以吗?原因是什么
直接给你结论:是的,都可以!
在同一台电脑上,只要服务器绑定了 0.0.0.0,客户端的目标IP无论是写内网IP(如 192.168.1.100)、本地回环(127.0.0.1)还是公网IP(如你的运营商分配的外网IP),都能成功通信。
但这背后有一个非常硬核的操作系统网络栈原理。我把这3种情况拆开给你讲透,你就彻底明白了。
服务器绑定 0.0.0.0 意味着什么?
0.0.0.0(INADDR_ANY)在操作系统中是一个“通配符”。
-
它不是一个真正的网卡IP。
-
它的意思是:“只要是发往这台电脑上任意一个IP地址(且端口号匹配)的数据包,我都收!”
这意味着,你的服务器进程同时“注册”在了这台电脑的所有IP上:
-
127.0.0.1(回环网卡) -
192.168.1.100(有线/无线网卡) -
xxx.xxx.xxx.xxx(公网IP,如果有的话)
所以,无论客户端从哪个IP发过来,服务器都能收到。
两台电脑一台客户端、一台服务器端使用公网链接可以吗?
1. 为什么“可以”?(技术层面)
只要两台电脑都能访问互联网,并且互相知道对方的公网IP,代码逻辑上完全没问题。你的UDP Socket代码不需要改一行:
-
服务器绑定
0.0.0.0(接收所有IP的数据)。 -
客户端
sendto的目标IP填服务器的公网IP(比如120.xxx.xxx.xxx)。
从编程角度看,IP地址只是一个32位的数字,填什么它就发往哪里,代码绝对支持。
2. 为什么“几乎不可能”?(现实网络层面)——这是关键
现在的家庭宽带、公司网络、手机热点,几乎全都使用了 NAT(网络地址转换)。
简单来说,你的电脑根本没有独立的公网IP。路由器把唯一的公网IP分给了成百上千台设备(手机、电脑、电视)。
服务器端(被连接方)的致命问题:
-
你的服务器运行在笔记本上,笔记本拿到的IP是
192.168.1.100(内网IP)。 -
路由器把公网IP
120.xxx.xxx.xxx映射给了整个家庭网络。 -
当客户端向
120.xxx.xxx.xxx:8888发数据时,数据到达路由器,路由器懵了:“这个8888端口是给哪台电脑的?我没设置过啊!” 然后直接丢弃数据包。
结论:默认情况下,外网主动发起的连接,无法穿透你的家庭路由器到达你的笔记本。这就是所谓的“无公网IP”或“端口不通”。
感谢支持,长期连载
欢迎关注

转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/oooooooooooohd/article/details/163583007




