linux学习笔记(32)网络编程——UDP

UDP是什么?


UDP = 像寄明信片

  • 写了就寄,不管对方收没收到
  • 不保证顺序,可能先寄的后到
  • 简单快速,没有复杂流程

1. UDP vs TCP 直观对比

TCP(像打电话):

// 必须建立连接
connect() → 三次握手 → 可靠传输 → 四次挥手
// 保证:顺序、不丢失、不重复

UDP(像寄明信片):

// 直接发送,无连接
sendto() → 网络 → 可能到达,可能丢失
// 不保证:顺序、不丢失、不重复

2. 最简单的UDP服务器

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
    printf("=== UDP服务器启动 ===\n");
    
    // 1. 创建UDP socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket创建失败");
        exit(1);
    }
    printf("1. UDP socket创建成功\n");
    
    // 2. 设置服务器地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;  // 监听所有网卡
    server_addr.sin_port = htons(PORT);        // 端口号
    
    // 3. 绑定地址(UDP也需要bind!)
    if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("绑定失败");
        close(sockfd);
        exit(1);
    }
    printf("2. 绑定端口 %d 成功\n", PORT);
    printf("3. 等待客户端数据...\n\n");
    
    // 主循环
    while (1) {
        char buffer[BUFFER_SIZE];
        struct sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        
        // 4. 接收数据(阻塞等待)
        int bytes_received = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0,
                                     (struct sockaddr*)&client_addr, &client_len);
        
        if (bytes_received > 0) {
            buffer[bytes_received] = '\0';  // 添加字符串结束符
            
            // 获取客户端信息
            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
            int client_port = ntohs(client_addr.sin_port);
            
            printf("📨 收到来自 %s:%d 的消息:\n", client_ip, client_port);
            printf("   内容: %s\n", buffer);
            printf("   长度: %d 字节\n\n", bytes_received);
            
            // 5. 回复客户端
            char response[BUFFER_SIZE];
            snprintf(response, sizeof(response), 
                    "服务器已收到你的消息: %s", buffer);
            
            sendto(sockfd, response, strlen(response), 0,
                  (struct sockaddr*)&client_addr, client_len);
            printf("📤 已发送回复\n\n");
        }
    }
    
    close(sockfd);
    return 0;
}

3. 最简单的UDP客户端

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    printf("=== UDP客户端启动 ===\n");
    
    // 1. 创建UDP socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket创建失败");
        exit(1);
    }
    printf("1. UDP socket创建成功\n");
    
    // 2. 设置服务器地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
    
    printf("2. 目标服务器: %s:%d\n", SERVER_IP, PORT);
    
    while (1) {
        char buffer[BUFFER_SIZE];
        
        // 3. 获取用户输入
        printf("\n💬 请输入要发送的消息 (输入quit退出): ");
        fgets(buffer, sizeof(buffer), stdin);
        buffer[strcspn(buffer, "\n")] = '\0';  // 去掉换行符
        
        if (strcmp(buffer, "quit") == 0) {
            break;
        }
        
        // 4. 发送数据到服务器
        int bytes_sent = sendto(sockfd, buffer, strlen(buffer), 0,
                               (struct sockaddr*)&server_addr, sizeof(server_addr));
        printf("📤 发送: %s (%d字节)\n", buffer, bytes_sent);
        
        // 5. 接收服务器回复
        char response[BUFFER_SIZE];
        struct sockaddr_in from_addr;
        socklen_t from_len = sizeof(from_addr);
        
        int bytes_received = recvfrom(sockfd, response, sizeof(response) - 1, 0,
                                     (struct sockaddr*)&from_addr, &from_len);
        
        if (bytes_received > 0) {
            response[bytes_received] = '\0';
            printf("📥 收到回复: %s\n", response);
        } else {
            printf("❌ 未收到回复 (可能丢失)\n");
        }
    }
    
    close(sockfd);
    printf("👋 客户端退出\n");
    return 0;
}

4. 编译和测试

编译命令:

编译UDP服务器

gcc -o udp_server udp_server.c

编译UDP客户端

gcc -o udp_client udp_client.c

测试步骤:

  1. 终端1 - 启动UDP服务器:
    ./udp_server
  2. 终端2 - 启动UDP客户端:
    ./udp_client
    输入:Hello UDP!
  3. 终端3 - 再启动一个UDP客户端:
    ./udp_client
    输入:This is client 2

预期输出:

服务器输出:

复制代码
=== UDP服务器启动 ===
1. UDP socket创建成功
2. 绑定端口 8080 成功
3. 等待客户端数据...

📨 收到来自 127.0.0.1:54321 的消息:
   内容: Hello UDP!
   长度: 10 字节

📤 已发送回复

📨 收到来自 127.0.0.1:54322 的消息:
   内容: This is client 2
   长度: 16 字节

📤 已发送回复

客户端输出:

复制代码
=== UDP客户端启动 ===
1. UDP socket创建成功
2. 目标服务器: 127.0.0.1:8080

💬 请输入要发送的消息 (输入quit退出): Hello UDP!
📤 发送: Hello UDP! (10字节)
📥 收到回复: 服务器已收到你的消息: Hello UDP!

💬 请输入要发送的消息 (输入quit退出): quit
👋 客户端退出

5. UDP的核心特点

无连接:

复制代码
// 不需要connect(),直接发送
sendto(sockfd, data, len, 0, &server_addr, addr_len);

// 每个数据包都是独立的
// 可以同时给多个服务器发送数据

不可靠:

复制代码
// 数据可能:
// - 丢失(网络拥堵)
// - 重复(网络重传)
// - 乱序(走不同路径)
// - 损坏(比特错误)

简单高效:

复制代码
// 头部只有8字节
struct udp_header {
    uint16_t source_port;
    uint16_t dest_port;
    uint16_t length;
    uint16_t checksum;
};

// 相比TCP的20字节头部,开销小很多

6. UDP的关键函数

sendto()****- 发送数据

int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

recvfrom()****- 接收数据

int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
// src_addr会填充发送者的地址信息

7. UDP的使用场景

适合UDP的场景:

复制代码
// 1. 实时音视频通话
//    - 丢失几帧没关系,但要低延迟

// 2. DNS查询
//    - 简单请求响应,丢了重试就行

// 3. 游戏数据包
//    - 位置更新,丢了用新的覆盖

// 4. 广播/组播
//    - 一对多通信

不适合UDP的场景:

复制代码
// 1. 文件传输
//    - 不能丢失任何数据

// 2. 网页浏览
//    - 需要保证所有数据正确到达

// 3. 电子邮件
//    - 必须可靠传输

8. UDP的优缺点总结

优点:

  • 速度快:没有连接建立、确认、重传
  • 开销小:头部只有8字节
  • 无连接:可以同时与多个对端通信
  • 适合广播:天然支持一对多

缺点:

  • 不可靠:可能丢失、重复、乱序
  • 无拥塞控制:可能加剧网络拥堵
  • 需要应用层处理:自己实现可靠性

🎯 终极对比:

|-----|-----------|------------|
| 特性 | TCP (打电话) | UDP (寄明信片) |
| 连接 | 需要建立连接 | 无连接 |
| 可靠性 | 保证不丢失 | 可能丢失 |
| 顺序 | 保证顺序 | 不保证顺序 |
| 速度 | 慢 | 快 |
| 头部 | 20字节 | 8字节 |
| 控制 | 有流量和拥塞控制 | 无控制 |

同一个端口,可以被tcp和udp同时使用
一个进程可以创建很多个套接字,也可以使用两个以上的端口

相关推荐
炸膛坦客21 小时前
Cortex-M3 内核 MCU-STM32F1 开发之路:(一)单片机 MCU 的构成,包括 FLASH 和 SRAM 的区别,以及引脚类型
stm32·单片机·嵌入式硬件
A9better21 小时前
嵌入式开发学习日志39——stm32之I2C总线物理层与常用术语
stm32·单片机·嵌入式硬件·学习
三佛科技-1873661339721 小时前
FT62FC3X 8位MCU单片机选型表,详细解析FT62FC31A/32A/33A/35A/3FA
单片机·嵌入式硬件
充哥单片机设计21 小时前
【STM32项目开源】基于STM32的智能衣柜系统
stm32·单片机·嵌入式硬件
Try1harder1 天前
极海APM32F107V6 + 合宙Air780E
单片机·嵌入式硬件·物联网·合宙air780
文火冰糖的硅基工坊1 天前
[嵌入式系统-134]:智能体以及其嵌入式硬件架构
科技·嵌入式硬件·架构·嵌入式·gpu
电鱼智能的电小鱼1 天前
服装制造企业痛点解决方案:EFISH-SBC-RK3588 柔性化吊挂调度方案
网络·人工智能·嵌入式硬件·算法·制造
清风6666661 天前
基于单片机的便携式温湿度检测烘干机设计
单片机·嵌入式硬件·毕业设计·课程设计
刻BITTER1 天前
用CMake 实现U8g2 的 SDL2 模拟环境
c++·stm32·单片机·嵌入式硬件·arduino
GilgameshJSS1 天前
STM32H743-ARM例程23-USB_HID
arm开发·stm32·嵌入式硬件