Linux网络部分——UDP内部链接关系,服务端、客户端在网络中的联系,公网、内网、本地环回
@bit::Shadow
✧(≖ ◡ ≖✿

目录

认识UDP、TCP协议

一、UDP 是什么?(一句话定义)

二、UDP 的核心特点(对比 TCP)

网络字节序

☆☆判断网络转主机还是主机转网络的关键是什么?

struct sockaddr_in local

成员变量详解

struct sockaddr的继承体系

公网、内网、本地环回

四大接口简解(基于Udp的实现)

套接字socket()创建的含义

进行bind()的含义

recvfrom的含义

sendto()的含义

在服务器客户端分别实现时IP与端口号的指定情况是什么?

1. 服务器端(被动等待连接/数据)

2. 客户端(主动发起请求)

3. 特殊情况:同一台机器上测试

4. 一张图总结对比

在同一台电脑上运行服务器和客户端(常见于开发调试),服务器绑定 0.0.0.0 都行,客户端的目标IP直接写 内网IP,本地回环,公网IP都可以吗?原因是什么

服务器绑定 0.0.0.0 意味着什么?

两台电脑一台客户端、一台服务器端使用公网链接可以吗?

1. 为什么“可以”?(技术层面)

2. 为什么“几乎不可能”?(现实网络层面)——这是关键

服务器端(被连接方)的致命问题:


设计原则:类设计Init()中socket()是必要,构造函数内_port是服务器需求使然。

认识UDP、TCP协议

一、UDP 是什么?(一句话定义)

UDP(User Datagram Protocol,用户数据报协议)是传输层的一种无连接、不可靠、面向数据报的协议。它只在 IP 之上增加了端口号(用于区分进程)和简单的校验和,不保证数据一定到达、不保证顺序、不保证不重复。


二、UDP 的核心特点(对比 TCP)

特性UDPTCP
连接状态无连接(发送前不需要握手)面向连接(三次握手)
可靠性不可靠(不保证送达)可靠(超时重传、确认机制)
顺序性不保证顺序保证按序到达
拥塞控制无(发送速率不受网络限制)有(滑动窗口、慢启动)
流量控制
数据边界面向数据报(一次发送一个完整报文)面向字节流(无边界)
校验和有(可选,但通常开启)有(必须)
开销头部 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_familysa_family_t(通常是unsigned short地址族,标识这是 IPv4 地址AF_INETlocal.sin_family = AF_INET;
sin_portin_port_t(通常是uint16_t端口号(16位),必须用htons()转为网络字节序你想要的端口号(如 8888)local.sin_port = htons(8888);
sin_addrstruct in_addrIP 地址(32位),若指定必须用htonl()inet_pton()转为网络字节序你的 IP 或INADDR_ANYlocal.sin_addr.s_addr = INADDR_ANY;
sin_zerounsigned char[8]填充字段,只为保证结构体大小一致(16字节),通常用memset清零0

memset(local.sin_zero, 0, sizeof(local.sin_zero));

bzero(&local,sizeof(local));

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.0INADDR_ANY)。这是服务器最常用的写法,代码通用性最强。

  • 端口号:必须明确指定(如80808888)。这是操作系统区分不同服务的唯一标识。

代码体现(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.10.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.0INADDR_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)。

  • 路由器把公网IP120.xxx.xxx.xxx映射给了整个家庭网络。

  • 当客户端向120.xxx.xxx.xxx:8888发数据时,数据到达路由器,路由器懵了:“这个8888端口是给哪台电脑的?我没设置过啊!” 然后直接丢弃数据包

结论:默认情况下,外网主动发起的连接,无法穿透你的家庭路由器到达你的笔记本。这就是所谓的“无公网IP”或“端口不通”。

感谢支持,长期连载
欢迎关注