网络编程:UDP Socket

1 核心概念

1.1 UDP (User Datagram Protocol)
  • 定义:用户数据报协议。是一种无连接的、不可靠的、基于数据报的传输层协议。
  • 本质 :只负责把数据包扔出去,不保证对方收到,也不保证顺序。类似于"寄信 "或"发短信"。
  • 特点
    • 无连接 (Connectionless):通信前不需要建立连接(无三次握手),结束时不需要断开(无四次挥手)。
    • 不可靠 (Unreliable):不提供重传、排序、流量控制机制。数据可能丢失、重复或乱序。
    • 面向数据报 (Datagram):保留数据边界。发送方发 100 字节,接收方就得一次收 100 字节(不会像 TCP 那样出现粘包)。
    • 高效:头部开销小(仅 8 字节),实时性高,适合视频会议、直播、广播等场景。
1.2 关键区别 (TCP vs UDP)
特性 TCP (打电话) UDP (发短信)
连接性 面向连接 (三次握手) 无连接
可靠性 可靠 (重传、排序、校验) 不可靠 (尽力而为)
传输模式 字节流 (Stream) 数据报 (Datagram)
Socket 类型 SOCK_STREAM SOCK_DGRAM
核心 API read / write recvfrom / sendto
应用场景 网页(HTTP)、文件传输(FTP) 视频流、DNS、广播

2 关键数据结构

需包含头文件:#include <netinet/in.h>

UDP 使用与 TCP 完全相同的地址结构体,没有区别。

IPv4 专用地址结构体

c 复制代码
struct sockaddr_in {
    short int          sin_family;  // 地址族 (必须设为 AF_INET)
    unsigned short int sin_port;    // 端口号 (网络字节序, 使用 htons)
    struct in_addr     sin_addr;    // IP 地址结构体
    unsigned char      sin_zero[8]; // 填充字节 (置0)
};

struct in_addr {
    unsigned long s_addr; // 32位 IP 地址 (网络字节序)
};

3 核心 API

需包含头文件:#include <sys/socket.h>

3.1 创建套接字
c 复制代码
int socket(int domain, int type, int protocol);
  • type: 使用 SOCK_DGRAM (数据报套接字)。
3.2 绑定地址 (bind)
c 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 接收端 (Server)必须绑定。类似于在门口挂个信箱,否则不知道收谁的信。
  • 发送端 (Client)通常不需要绑定。系统会自动分配一个临时端口发送数据。
3.3 数据接收 (recvfrom)

UDP 没有连接,不知道数据从哪来,所以接收时必须同时拿"信的数据"和"发信人的地址"。

c 复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • 功能:接收数据,并获取发送者的地址信息。
  • 参数
    • src_addr:(传出参数) 用来存储发送方的 IP 和端口信息。
    • addrlen:(传入传出参数) src_addr 的长度。
  • 返回值:成功返回接收到的字节数,失败返回 -1。
  • 注意 :如果不关心是谁发的,后两个参数可填 NULL
3.4 数据发送 (sendto)

UDP 没有连接,发送时必须每次都指定"发给谁"。

c 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • 功能:向指定的目标地址发送数据。
  • 参数
    • dest_addr:(传入参数) 接收方的目标 IP 和端口结构体。
    • addrlendest_addr 的长度。
  • 返回值:成功返回发送的字节数,失败返回 -1。
3.5 UDP 中通常不用
  • listen():UDP 不需要监听,因为没有连接请求。
  • accept():UDP 不需要接受连接。
  • connect():UDP 可以调用,但含义不同(变为"已连接 UDP"),一般很少用。

4 标准 UDP 编程模型

阶段 接收端 (Receiver/Server) 发送端 (Sender/Client)
1. 准备 socket(..., SOCK_DGRAM, ...) socket(..., SOCK_DGRAM, ...)
2. 地址 填充 sockaddr_in (本机 IP+端口) 填充 sockaddr_in (目标 IP+端口)
3. 绑定 bind() 必须执行 (通常省略,系统自动分配)
4. 通信 recvfrom() (阻塞等待数据,获取对方地址) sendto() (直接发送,需指定目标地址)
5. 回复 sendto() (利用 recvfrom 得到的地址回复) recvfrom() (如果需要等待回复)
6. 结束 close() close()

5. UDP Echo 实验代码

5.1 接收端代码 (Server)

负责接收数据,打印发送者 IP,并原样回传。

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

#define PORT        8888
#define BUFFER_SIZE 1024

int main()
{
    int sockfd;
    struct sockaddr_in serv_addr, client_addr;
    char buffer[BUFFER_SIZE];
    socklen_t addr_len = sizeof(client_addr);

    // 1. 创建 UDP Socket
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(1);
    }

    // 2. 绑定地址 (Server 必须 Bind)
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family      = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有网卡
    serv_addr.sin_port        = htons(PORT);

    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        perror("bind");
        exit(1);
    }

    printf("[UDP Server] Listening on port %d...\n", PORT);

    while (1)
    {
        memset(buffer, 0, BUFFER_SIZE);

        // 3. 接收数据 (同时获取对方地址)
        int len = recvfrom(sockfd, buffer, BUFFER_SIZE, 0,
                           (struct sockaddr *)&client_addr, &addr_len);

        if (len > 0)
        {
            buffer[len] = '\0'; // 添加字符串结束符
            printf("Received from %s: %s\n",
                   inet_ntoa(client_addr.sin_addr), buffer);

            // 4. 回复数据 (发回给 sender)
            sendto(sockfd, buffer, len, 0,
                   (struct sockaddr *)&client_addr, addr_len);
        }
    }
    close(sockfd);
    return 0;
}
6.2 发送端代码 (Client)

负责发送数据到指定 IP,并等待回复。

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

#define SERVER_PORT 8888
#define SERVER_IP   "192.168.7.2" // 板子IP

int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in serv_addr;
    char buffer[1024];

    // 1. 创建 UDP Socket
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(1);
    }

    // 2. 配置目标地址
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(SERVER_PORT);
    inet_pton(AF_INET, (argc > 1 ? argv[1] : SERVER_IP), &serv_addr.sin_addr);

    while (1)
    {
        printf("Input > ");
        fgets(buffer, sizeof(buffer), stdin);

        // 3. 发送数据 (指定发给 serv_addr)
        sendto(sockfd, buffer, strlen(buffer), 0,
               (struct sockaddr *)&serv_addr, sizeof(serv_addr));

        // 4. 接收回复
        memset(buffer, 0, sizeof(buffer));
        int len = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                           NULL, NULL); // 不关心是谁回的,填NULL
        if (len > 0)
        {
            buffer[len] = '\0';
            printf("Echo: %s\n", buffer);
        }
    }
    close(sockfd);
    return 0;
}
相关推荐
ChristXlx7 小时前
Linux安装MongoDB(虚拟机适用)
linux·mongodb·postgresql
AttaGain7 小时前
禁用Ubuntu24.04休眠模式
linux
kaka_19947 小时前
如何解决驱动程序无法通过使用安全套接字层(SSL)加密与 SQL Server 建立安全连接。
linux·安全·ssl
云和数据.ChenGuang7 小时前
F5 Big-IP by SNMP.硬件负载均衡
网络协议·tcp/ip·负载均衡·数据库运维工程师·运维教程
我是谁??7 小时前
windows11的ubuntu子系统如何识别到U盘
linux·运维·ubuntu
qq_455760858 小时前
docker - 虚拟化和容器化
linux·运维·服务器
小年糕是糕手8 小时前
【C++】string类(一)
linux·开发语言·数据结构·c++·算法·leetcode·改行学it
大聪明-PLUS8 小时前
常见的 Docker 问题及解决方法
linux·嵌入式·arm·smarc
顾安r8 小时前
12.17 脚本网页 创意导航
java·linux·前端·游戏·html