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)
复制代码
相关推荐
Shanks.7 小时前
2026 远程控制软件实测:ToDesk / 向日葵 / RustDesk / TeamViewer 在跨城与弱网下的真实差距
网络·远程工作·软件需求
Jeremy_WW8 小时前
802.3协议解读 06:117章节 RS 和 200GMII / 400GMII I
网络·网络协议·信息与通信·光模块·802.3协议
坤坤子吖9 小时前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
顧棟9 小时前
INFINI Gateway 服务器端口重用(SO_REUSEPORT)实现与分析
网络·gateway
沫璃染墨10 小时前
从零入门计算机网络系列(十八):TCP连接管理详解——从三次握手与四次挥手到TIME_WAIT机制
运维·服务器·网络·c++·网络协议·tcp/ip·tcp
LabVIEW开发11 小时前
Omron PLC:接 LabVIEW 常踩的 5 个坑
服务器·网络·labview·labview知识·labview功能·labview程序
南京码讯光电技术有限公司11 小时前
工业5G/4G路由器怎么选?核心参数解析与工业级网关选购指南
网络·智能路由器
爱和冰阔落11 小时前
【Linux网络】JsonCpp 怎么实现序列化?从 Request/Response 到网络计算器消息设计
linux·运维·网络·php
feng_you_ying_li1 天前
linux之数据链路层
linux·网络·智能路由器
国科安芯1 天前
人在回路中的总线:商业空间站环境控制与生命保障系统CANFD网络的可靠性与功能安全
网络·嵌入式硬件·安全·架构·php·抗辐射