UDP编程与网络通信

**摘要:**本文详细介绍了UDP编程的基本流程、函数接口,并深入讲解了广播和组播两种网络通信方式。通过对比TCP与UDP的区别,结合完整的代码示例和流程图,帮助读者全面理解UDP协议的特点和应用场景。

1. UDP编程基础

1.1 UDP编程流程

UDP通信流程(类比发短信):

服务器端流程:

  1. 创建数据报套接字socket(AF_INET, SOCK_DGRAM, 0))→ 相当于拥有手机
  2. 绑定网络信息bind())→ 相当于绑定手机号码
  3. 接收信息recvfrom())→ 接收短信
  4. 关闭套接字close())→ 通信结束

客户端流程:

  1. 创建数据报套接字socket(AF_INET, SOCK_DGRAM, 0))→ 相当于拥有手机
  2. 指定服务器的网络信息 → 知道对方的手机号码
  3. 发送信息sendto())→ 发送短信
  4. 关闭套接字close())→ 发送完毕

1.2 UDP核心函数接口

recvfrom() 函数
cpp 复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);

功能:接收UDP数据包
参数:
    sockfd:套接字描述符
    buf:接收缓冲区的首地址
    len:接收缓冲区的大小
    flags:通常设置为0
    src_addr:发送端的网络信息结构体指针(可获取发送方地址)
    addrlen:发送端网络信息结构体大小的指针

返回值:
    成功:接收到的字节数
    失败:-1,并设置errno
sendto() 函数
cpp 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

功能:发送UDP数据包
参数:
    sockfd:套接字描述符
    buf:发送缓冲区的首地址
    len:发送缓冲区的大小
    flags:通常设置为0
    dest_addr:接收端的网络信息结构体指针
    addrlen:接收端网络信息结构体的大小

返回值:
    成功:发送的字节数
    失败:-1,并设置errno

重要说明: 在UDP中,recvfrom()函数无法检测客户端是否异常退出,因为UDP是无连接的。

1.3 UDP服务器端完整示例

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

int main(int argc, char const *argv[])
{
    // 1. 创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket err");
        return -1;
    }

    // 2. 填充服务器地址结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);  // IP地址
    saddr.sin_port = htons(atoi(argv[2]));       // 端口号

    // 3. 绑定网络信息
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
        perror("bind err");
        return -1;
    }
    printf("bind ok\n");
    
    // 4. 接收数据
    char buf[128] = "";
    recvfrom(sockfd, buf, 128, 0, NULL, NULL);
    printf("Received: %s\n", buf);

    // 5. 关闭套接字
    close(sockfd);
    return 0;
}

1.4 UDP客户端完整示例

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

int main(int argc, char const *argv[])
{
    // 1. 创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket err");
        return -1;
    }

    // 2. 填充服务器地址结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);  // 服务器IP
    saddr.sin_port = htons(atoi(argv[2]));       // 服务器端口

    // 3. 发送数据
    char buf[128] = "hello";
    sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr));
    printf("send ok\n");

    // 4. 关闭套接字
    close(sockfd);
    return 0;
}

1.5 双向通信示例

**练习:**实现客户端发送"hello"给服务器端,服务器接收后回复"recv:hello!!!!!"。

UDP服务器端(双向通信)
cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    // 1. 创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket err");
        return -1;
    }

    // 2. 填充服务器地址结构体
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    saddr.sin_port = htons(atoi(argv[2]));

    // 3. 绑定网络信息
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) {
        perror("bind err");
        return -1;
    }
    printf("bind ok\n");

    // 4. 接收客户端数据
    char buf[128] = "";
    int len = sizeof(caddr);
    recvfrom(sockfd, buf, 128, 0, (struct sockaddr *)&caddr, &len);
    printf("Client says: %s\n", buf);

    // 5. 发送回复给客户端
    memset(buf, 0, 128);
    strcpy(buf, "recv:hello!!!!!");
    sendto(sockfd, buf, 128, 0, (struct sockaddr *)&caddr, sizeof(caddr));

    // 6. 关闭套接字
    close(sockfd);
    return 0;
}
UDP客户端(双向通信)
cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    // 1. 创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket err");
        return -1;
    }

    // 2. 填充服务器地址结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    saddr.sin_port = htons(atoi(argv[2]));

    // 3. 发送数据到服务器
    char buf[128] = "hello";
    sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr));
    printf("send ok\n");

    // 4. 接收服务器回复
    recvfrom(sockfd, buf, 128, 0, NULL, NULL);
    printf("Server reply: %s\n", buf);

    // 5. 关闭套接字
    close(sockfd);
    return 0;
}

1.6 UDP与TCP的重要区别

  1. 连接方式:TCP必须先运行服务器,客户端才能连接;UDP服务器和客户端运行顺序没有先后,因为UDP是无连接的。
  2. 并发处理 :一个UDP服务器可以同时处理多个客户端请求,可以通过recvfrom()src_addr参数获取客户端IP和端口号。
  3. connect()函数的作用 :在UDP中,客户端使用send()前可以调用connect(),但这并不是建立连接,而是指定默认的发送目标,之后就可以使用send()代替sendto()
  4. 函数通用性 :在TCP中也可以使用recvfrom()sendto(),使用时将最后两个参数设置为NULL即可。

2. 广播通信

2.1 广播理论基础

前面介绍的数据包发送方式只有一个接收方,称为单播

  1. 广播定义 :如果同时发给局域网中的所有主机,称为广播
  2. 协议限制:只有用户数据报(使用UDP协议)套接字才能广播。
  3. 应用场景:一般被设计成局域网搜索协议,如DHCP、ARP等。
  4. 广播地址
    • 以192.168.1.0(子网掩码255.255.255.0)网段为例
    • 最大的主机地址192.168.1.255代表该网段的广播地址
    • 发到该地址的数据包被网段内所有主机接收

2.2 setsockopt()函数详解

Linux中socket属性设置函数:

cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t optlen);

功能:设置套接字属性
参数:
    sockfd:套接字描述符(指定要设置/获取哪个套接字的属性)
    level:协议层(指定要控制的协议层次)
        SOL_SOCKET(应用层) - 通用套接字选项
        IPPROTO_TCP(传输层)
        IPPROTO_IP(网络层)
    optname:选项名(指定要控制的内容和方式)
    
    --- SOL_SOCKET常用选项(man 7 socket)---
        SO_REUSEADDR:允许端口快速重用(int*)
        SO_BROADCAST:允许发送广播数据(int)
        SO_RCVBUF:接收缓冲区大小(int)
        SO_SNDBUF:发送缓冲区大小(int)
        SO_RCVTIMEO:接收超时(struct timeval)
        SO_SNDTIMEO:发送超时(struct timeval)
    
    optval:根据optname不同,该指针类型不同
    optlen:optval指针指向的内存空间的大小

返回值:
    成功:返回0
    失败:返回-1,并更新errno

man 7 socket文档截图:

2.3 广播发送和接收流程

发送广播流程
cpp 复制代码
int opt = 1;  // 类似于广播开关,0为关闭,1为打开
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
  1. 创建数据报套接字
  2. 填充结构体信息(使用广播IP地址)
  3. 允许发送广播数据
  4. 发送数据
  5. 关闭套接字
接收广播流程
  1. 创建数据报套接字
  2. 填充结构体信息(使用广播IP地址)
  3. 绑定到广播地址
  4. 接收数据
  5. 关闭套接字

2.4 广播通信完整示例

广播发送端(send.c)
cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    // 1. 创建数据报套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket err");
        return -1;
    }

    // 2. 填充广播地址结构体
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = inet_addr(argv[1]);  // 广播IP地址
    saddr.sin_port = htons(atoi(argv[2]));       // 端口号

    // 3. 允许发送广播数据
    int opt = 1;  // 类似于广播开关,0为关闭,1为打开
    setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));

    // 4. 发送广播数据
    char buf[128] = "hello";
    sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr));
    printf("Broadcast send ok\n");

    // 5. 关闭套接字
    close(sockfd);
    return 0;
}
广播接收端(recv.c)
复制代码
相关推荐
FPC工厂——皇榜科技1 小时前
机器人灵巧手线路板:从“握得住”到“摸得准”:一只机械手的柔性神经密码
网络·人工智能·科技·机器人·pcb工艺
Orange_sparkle1 小时前
Pi Agent vs LangGraph:从人机交互、企业 RAG 到持久化工作流的完整选型指南
java·网络·人机交互
xiaoye-duck2 小时前
《Linux 网络编程》深入传输层 UDP 协议原理:端口、报文、缓冲区与内核源码解析
linux·网络·udp
好评1243 小时前
【Linux】数据链路层
linux·运维·网络
天衍四九-3 小时前
第一章:从 LLM 到 Agent —— DeepSeek Harness 入门
网络·数据库·人工智能·python
Jae den3 小时前
网络丢包是什么?
网络
XR1234567884 小时前
医院无线整网高密覆盖选型深度解析
运维·网络
发量惊人的中年网工5 小时前
企业一体化安全网关选型:适用规模、需求架构与实施建议
网络
取啥都被占用5 小时前
两个网络供应商靠树莓派跨个域
网络·树莓派
是个西兰花7 小时前
网络基础1
linux·网络·c++·智能路由器