linux网络编程:UDP

在嵌入式Linux网络编程中,UDP协议是实现快速、简单通信的关键。与TCP的复杂机制不同,UDP采用无连接方式,直接发送数据报。本文将深入解析UDP编程的每个环节,基于文档内容提供完整实现。

UDP核心概念与特性

UDP是用户数据报协议,位于TCP/IP模型的传输层。与TCP相比,UDP具有以下特点:

  • 资源开销小,机制简单

  • 无连接,无需建立和释放连接

  • 传输不安全、不可靠,可能丢包、乱序

  • 适合实时性要求高的场景

UDP通信完整流程

UDP通信采用简单的发送-接收模式:

cpp 复制代码
发送端:socket() -> sendto() -> close()
接收端:socket() -> bind() -> recvfrom() -> close()

UDP包头结构分析

UDP包头固定8个字节,包含以下字段:

  • 源端口:2字节,发送方端口号

  • 目的端口:2字节,接收方端口号

  • 长度:2字节,UDP头部和数据的总长度

  • 校验和:2字节,用于错误检测

关键函数深度解析

socket函数

cpp 复制代码
int socket(int domain, int type, int protocol);

功能:创建套接字

参数:

  • domain:AF_INET表示IPv4协议族

  • type:SOCK_DGRAM表示数据报套接字

  • protocol:0表示UDP通信

    返回值:成功返回文件描述符,失败返回-1

sendto函数

cpp 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

功能:向另一个套接字发送信息

参数:

  • sockfd:套接字文件描述符

  • buf:发送内容的首地址

  • len:发送数据的长度

  • flags:发送属性,默认0

  • dest_addr:目的地址结构体指针

  • addrlen:目的地址长度

    返回值:成功返回发送字节数,失败返回-1

recvfrom函数

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

功能:从套接字接收信息

参数:

  • sockfd:套接字文件描述符

  • buf:接收缓冲区首地址

  • len:缓冲区最大长度

  • flags:接收属性,默认0

  • src_addr:存放源地址的结构体指针

  • addrlen:源地址长度指针

    返回值:成功返回接收字节数,失败返回-1

字节序转换函数

网络通信使用大端字节序,本地主机可能使用小端字节序,需要进行转换:

cpp 复制代码
uint32_t htonl(uint32_t hostlong);    // 主机到网络长整型
uint16_t htons(uint16_t hostshort);   // 主机到网络短整型
uint32_t ntohl(uint32_t netlong);     // 网络到主机长整型
uint16_t ntohs(uint16_t netshort);    // 网络到主机短整型
  • h:host本地

  • n:net网络

  • l:long长整型,用于IP地址

  • s:short短整型,用于端口号

IP地址转换函数

cpp 复制代码
in_addr_t inet_addr(const char *cp);  // 字符串IP转32位IP
char *inet_ntoa(struct in_addr in);   // 32位IP转字符串IP
int inet_pton(int af, const char *src, void *dst);  // 字符串IP转二进制
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);  // 二进制IP转字符串

bind函数详解

cpp 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:将地址与套接字绑定

参数:

  • sockfd:套接字文件描述符

  • addr:要绑定的地址信息

  • addrlen:地址长度

    返回值:成功返回0,失败返回-1

注意事项:

  1. 只能绑定自己的IP地址

  2. 端口号不能重复绑定

  3. 服务器端必须调用bind,客户端通常不调用

UDP服务器端完整实现

cpp 复制代码
#include "head.h"

int main(void)
{
    int fp = 0;
    int sockfd = 0;
    int ret = 0;
    ssize_t nret = 0;
    struct sockaddr_in recvaddr;
    struct sockaddr_in sendaddr;
    char tmpbuff[256] = {0};
    socklen_t addrlen = sizeof(sendaddr);

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("fail to socket");
        return -1;
    }

    recvaddr.sin_family = AF_INET;
    recvaddr.sin_port = htons(50000);
    recvaddr.sin_addr.s_addr = inet_addr("192.168.0.167");
    ret = bind(sockfd, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
    if(-1 == ret)
    {
        perror("fail to bind");
        return -1;
    }

    nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, (struct sockaddr *)&sendaddr, &addrlen);
        if(-1 == nret)
        {
            perror("fail to recvfrom");
            return -1;
        }
    fp = open(tmpbuff, O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if (-1 == fp)
    {
        perror("fail to open");
        return -1;
    }

    while(1)
    {
        memset(tmpbuff, 0, sizeof(tmpbuff));
        nret = recvfrom(sockfd, tmpbuff, sizeof(tmpbuff), 0, NULL, NULL);
        if(-1 == nret)
        {
            perror("fail to recvfrom");
            return -1;
        }
        if (0 == strcmp(tmpbuff, "____QUIT____"))
        {
            break;
        }
        write(fp, tmpbuff, nret);  

    }
    close(sockfd);
    close(fp);
    printf("接受成功\n");

    return 0;

}

UDP客户端完整实现

cpp 复制代码
#include "head.h"

int main(void)
{
    int sockfd = 0;
    int fd = 0;
    char filename[256] = {0};
    char tmpbuff[1300] = {0};
    ssize_t nret = 0;
    struct sockaddr_in recvaddr;

    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("fail to socket");
        return -1;
    }

    printf("请输入要发送文件:\n");
    gets(filename);

    recvaddr.sin_family = AF_INET;
    recvaddr.sin_port = htons(50000);
    recvaddr.sin_addr.s_addr = inet_addr("192.168.0.169");
    nret = sendto(sockfd, filename, strlen(filename), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
    if (-1 == nret)
    {
        perror("fail to sendto");
        return -1;
    }

    fd = open(filename, O_RDONLY);
    if (-1 == fd)
    {
        perror("fail to open");
        return -1;
    }

    while (1)
    {
        nret = read(fd, tmpbuff, sizeof(tmpbuff));
        if (nret <= 0)
        {
            break;
        }

        nret = sendto(sockfd, tmpbuff, nret, 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
        if (-1 == nret)
        {
            perror("fail to sendto");
            return -1;
        }
        usleep(10000);
    }
    
    close(fd);

    sprintf(tmpbuff, "____QUIT____");
    nret = sendto(sockfd, tmpbuff, strlen(tmpbuff), 0, (struct sockaddr *)&recvaddr, sizeof(recvaddr));
    if (-1 == nret)
    {
        perror("fail to sendto");
        return -1;
    }

    close(sockfd);

    return 0;
}

结构体sockaddr_in详解

cpp 复制代码
struct sockaddr_in {
    sa_family_t    sin_family;   // 地址族,AF_INET
    in_port_t      sin_port;     // 端口号,网络字节序
    struct in_addr sin_addr;     // IP地址
    unsigned char  sin_zero[8];  // 填充
};

struct in_addr {
    in_addr_t s_addr;  // 32位IP地址
};

UDP文件传输实现

基于文档中的练习要求,实现UDP文件传输:

cpp 复制代码
// 发送端发送文件关键代码
FILE *fp = fopen("test.jpg", "rb");
char buffer[1024];
size_t bytes_read;

while ((bytes_read = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
    sendto(sockfd, buffer, bytes_read, 0,
          (struct sockaddr*)&server_addr, sizeof(server_addr));
    usleep(1000);  // 避免发送过快
}

fclose(fp);

// 接收端接收文件关键代码
FILE *fp = fopen("received.jpg", "wb");

while (1) {
    ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
    if (n <= 0) break;
    fwrite(buffer, 1, n, fp);
}

fclose(fp);

网络配置与调试

虚拟机网络模式

  1. 桥接模式

    • Ubuntu与Windows网络独立

    • 可作为局域网服务器

    • 服务器端必须使用桥接模式

  2. NAT模式

    • Ubuntu网络依赖Windows

    • 无法作为局域网服务器

    • 客户端优先选择NAT模式

网络配置命令

cpp 复制代码
ifconfig                  # 查看网卡信息
ping 8.8.8.8             # 测试网络连通性
ping www.baidu.com       # 测试域名解析

wireshark抓包工具

cpp 复制代码
sudo apt-get install wireshark  # 安装
sudo wireshark                  # 启动

常用过滤规则:

  • udp:只显示UDP数据包

  • udp.port == 50000:显示指定端口

  • ip.addr == 192.168.0.165:显示指定IP

UDP与TCP的关键区别

  1. 连接性

    • UDP:无连接,直接发送

    • TCP:面向连接,需三次握手

  2. 可靠性

    • UDP:不可靠,可能丢包乱序

    • TCP:可靠,有确认重传机制

  3. 复杂度

    • UDP:简单,包头8字节

    • TCP:复杂,包头至少20字节

  4. 适用场景

    • UDP:音视频流、DNS查询、实时游戏

    • TCP:文件传输、网页浏览、邮件

常见问题与解决方案

  1. 数据包丢失

    • 实现应用层确认机制

    • 添加序列号和重传逻辑

  2. 数据包乱序

    • 添加序列号重新排序

    • 设置合理超时时间

  3. 发送速度过快

    • 添加流量控制

    • 使用usleep控制发送间隔

  4. 缓冲区溢出

    • 调整接收缓冲区大小

    • 及时处理接收数据

总结

UDP协议以其简单高效的特点,在嵌入式Linux网络编程中占有重要地位。掌握socket、bind、sendto、recvfrom等核心函数,理解字节序转换和地址处理,是构建UDP应用的基础。

在实际开发中,应根据应用需求选择协议:对实时性要求高、可容忍少量丢包的选择UDP;对可靠性要求高的选择TCP。通过深入理解UDP的工作原理,可以更好地优化网络性能,构建高效稳定的嵌入式网络应用。

相关推荐
失途老马2 小时前
EdgeRouter PPPoE IPv6 完整配置指南(从 0 到通)
网络·飞牛os
2401_858936882 小时前
深入浅出 TCP 通信:从基础到并发服务器实现
服务器·网络·tcp/ip
野犬寒鸦2 小时前
SAP后端实习开发面试:操作系统与网络核心考点及Linux与Redis
java·服务器·网络·后端·面试
偷影子的机2 小时前
LVS实验
网络
战神/calmness2 小时前
应急响应-勒索病毒 13
网络·web安全·php·勒索病毒
乾元3 小时前
RAG 架构: 利用向量数据库构建企业的安全知识库
运维·网络·数据库·人工智能·安全·网络安全·架构
袁小皮皮不皮3 小时前
【HCIA】第一章网络基础
运维·服务器·网络·网络协议·智能路由器
AI周红伟3 小时前
周红伟:OpenClaw+ 微信+ QQ+云上OpenClaw(Clawdbot)快速接入企业微信指南
运维·服务器·网络
bug攻城狮3 小时前
Docker高级篇04:Docker网络
网络·docker·php