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)
复制代码
相关推荐
吹什么轩3 小时前
linux网络:TCP套接字基础
linux·网络·tcp/ip
志栋智能4 小时前
安全超自动化:构建全方位安全可观测性的基础
网络·安全·自动化
mqiqe5 小时前
线程调度与 Schedulers:Project Reactor 并发模型的核心引擎
java·开发语言·网络
略略略咯咯5 小时前
网络协议-study
网络·网络协议
DLYSB_6 小时前
从监控告警到现场处置:用 HTTP API、Modbus TCP 构建声光语音告警系统
网络协议·tcp/ip·http·报警灯
爱学习的程序媛6 小时前
通信协议完整梳理
网络·网络协议·信息与通信·通信协议
故乡dee云7 小时前
华为云国际服务器连不上怎么办?安全组、端口、IP 和网络排查方法
服务器·网络·华为云
佳児素花痴╮7 小时前
服务器模型、网络调试工具、网络协议头分析
服务器·网络·网络协议
阿pin7 小时前
TCP VS UDP
网络协议·tcp/ip·udp
2401_895366507 小时前
ospf综合实验
网络