Linux 学习笔记(三十九):网络基石——TCP/IP 模型与 UDP 实战

📌 核心结论(TL;DR):

  1. 网络通信本质 :是跨主机的进程间通信。靠 IP + Port(即 Socket 套接字)唯一标识网络中的进程。

  2. 网络字节序 :网络规定使用大端字节序 。发送前必须用 htons / htonl 转换。

  3. MAC vs IP:IP 是终极目标(跨网络不变),MAC 是下一跳目标(局域网内一直变)。

  4. UDP 特性:无连接、不可靠、面向数据报。

  5. UDP 开发核心 :服务器必须 bind(且 IP 选 INADDR_ANY),客户端不用显式 bind。

一、 网络基础概念

计算机网络的核心目的是数据共享。为了实现不同设备、不同操作系统之间的无障碍通信,采用了TCP/IP 四层(或五层)模型 (应用层、传输层、网络层、链路层)。数据在发送时自上而下层层封装 (加报头),接收时自下而上层层解包(去报头)。

二、 核心身份标识:IP、Port 与 MAC

  • IP 地址:IPv4 是 32 位整数。标识互联网中唯一的主机,路径选择的重要依据。

  • 端口号(Port):16 位整数。标识主机上的唯一进程。

  • MAC 地址 :48 位。标识局域网内唯一的网卡。注意:跨网络传输时,IP 地址基本不变,但 MAC 地址每经过一个路由器就会改变。

三、 UDP 协议与核心 API

UDP(User Datagram Protocol)不保证数据一定到达,但速度快。

  • socket():创建套接字。

  • bind():绑定端口(仅服务器端)。

  • sendto():发送数据。

  • recvfrom():接收数据。

四、 代码实战:UDP 回显服务器

cpp 复制代码
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(8888);
// 关键:INADDR_ANY 表示绑定所有本机网卡IP
server_addr.sin_addr.s_addr = INADDR_ANY; 
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

五、 踩坑记录:云服务器端口不通

现象:本地测试正常,但用别的电脑连不上云服务器的 UDP 端口。

原因:云厂商(如阿里云、腾讯云)有安全组防火墙机制。

解决:登录云控制台,在安全组入站规则中,放行对应的 UDP 端口。

自我实战操作

bash 复制代码
cd ~/linux_study
mkdir network_test
cd network_test
实战 1:UDP 服务端(UdpServer)
bash 复制代码
vim udp_server.c

(输入以下 C 语言代码,最纯粹的底层逻辑)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8888
#define BUF_SIZE 1024

int main() {
    // 1. 创建 UDP 套接字
    int server_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (server_fd < 0) { perror("socket"); return 1; }

    // 2. 绑定 IP 和 端口
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT); // 端口转网络字节序
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听任意IP(超级重要)

    if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind"); return 1;
    }
    printf("UDP 服务器已启动,监听端口 %d...\n", PORT);

    // 3. 循环接收和发送
    char buffer[BUF_SIZE];
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);

    while (1) {
        // 接收客户端发来的数据
        int n = recvfrom(server_fd, buffer, BUF_SIZE - 1, 0, (struct sockaddr*)&client_addr, &client_len);
        if (n > 0) {
            buffer[n] = '\0';
            printf("收到来自 %s:%d 的消息: %s\n", 
                   inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);
            
            // Echo (原样发回)
            sendto(server_fd, buffer, n, 0, (struct sockaddr*)&client_addr, client_len);
        }
    }
    close(server_fd);
    return 0;
}
实战 2:UDP 客户端(UdpClient)
bash 复制代码
vim udp_client.c

(输入以下代码)

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1" // 本地测试
#define PORT 8888
#define BUF_SIZE 1024

int main() {
    // 1. 创建 UDP 套接字 (不需要显式 bind,系统会自动分配随机端口)
    int client_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (client_fd < 0) { perror("socket"); return 1; }

    // 2. 配置服务器地址信息
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); // 字符串IP转二进制

    char buffer[BUF_SIZE];
    while (1) {
        printf("请输入要发送的消息 (输入 quit 退出): ");
        fgets(buffer, BUF_SIZE, stdin);
        buffer[strcspn(buffer, "\n")] = 0; // 去掉换行符
        
        if (strcmp(buffer, "quit") == 0) break;

        // 发送数据
        sendto(client_fd, buffer, strlen(buffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

        // 接收回显数据
        struct sockaddr_in temp_addr;
        socklen_t temp_len = sizeof(temp_addr);
        int n = recvfrom(client_fd, buffer, BUF_SIZE - 1, 0, (struct sockaddr*)&temp_addr, &temp_len);
        if (n > 0) {
            buffer[n] = '\0';
            printf("收到服务器回显: %s\n", buffer);
        }
    }
    close(client_fd);
    return 0;
}
实战 3:编译测试与网络排错
bash 复制代码
# 编译
gcc udp_server.c -o udp_server
gcc udp_client.c -o udp_client

测试流程:

  1. 开终端 A (服务端):./udp_server

  2. 开终端 B (客户端):./udp_client

  3. 在终端 B 输入 hello,看看终端 A 和 B 分别打印了什么。

  4. 排错加餐 :如果你把服务端跑在云服务器上,客户端在自己的电脑上(Windows),你必须在云服务器后台的安全组 里,开放 8888 的 UDP 端口,否则数据会被防火墙拦截!

相关推荐
闲云野鹤在人间1 小时前
头秃修炼手册:StorageClass+StatefulSet+ETCD 全套渡劫笔记
linux·数据库·笔记·云原生·kubernetes·etcd
wuminyu1 小时前
JVM规范学习总结:class执行性能差异
java·linux·c语言·jvm·c++
你看我名字1 小时前
Kafka 架构、消息可靠性与生产运维:从分区模型到故障治理
运维·架构·kafka
派大猩猩_1 小时前
选题散在 20 篇笔记里?Nutstore Sync在 Obsidian 里创建选题小工具,创作动线短一半
笔记·obsidian·obsidian插件·obsidian ai
EVEN-09131 小时前
Linux-线程控制
linux·jvm
研发小能1 小时前
DevOps 工具链怎么选?已有工具的企业,先评估整合能力
运维·devops
路由侠内网穿透.1 小时前
本地部署开源 WSGI HTTP 服务器 Gunicorn 并实现外部访问
运维·服务器·网络·网络协议·http·开源
前进的李工1 小时前
Agent 学习笔记 04:KV Cache 与上下文缓存
笔记·学习·缓存·agent·注意力机制·kv cache·prompt cache
刚入门的大一新生1 小时前
Linux-线程控制
linux·运维·服务器