Linux 网络编程:深入浅出UDP协议Socket编程规范

引言

在上一篇文章中,我们详细探讨了基于TCP协议的Socket编程,可以感受到TCP的编程流程还是有一些繁琐的,然而,在网络编程的世界里,并非所有场景都需要如此繁琐的连接建立。今天,我们将把目光投向另一个重要的主角------UDP协议(User Datagram Protocol)

UDP是一种无连接、不可靠但在特定场景下(如视频流、DNS查询、实时游戏)效率极高的传输协议。本文将带你一步步掌握Linux环境下C++ UDP Socket的编程规范,并深入剖析其与TCP开发的异同。

一、UDP 协议的特点(与 TCP 对比)

特性 UDP TCP
连接方式 无连接,无需三次握手 面向连接,三次握手建立连接
数据传输 不可靠、不保证顺序、不重传,但轻量、延迟低 可靠、有序、重传
数据形式 数据报,一次收一次发 字节流,无消息边界
适用场景 实时性要求高:视频、语音、游戏、直播、DNS 可靠性要求高:HTTP、文件传输、数据库通讯
服务端并发模型 单进程即可处理多个客户端,无需 accept 必须 accept 为每个客户端创建连接

二、UDP Socket 编程中常用的 API

UDP 使用的 API 与 TCP 90% 一样,但流程不同,由于UDP不存在握手这一步骤,所以在绑定地址之后,服务端不需要 listen,客户端也不需要 connetc,服务端同样不需要 accept

同时在UDP中,我们主要使用以下两个函数来替代TCP中的 sendrecv

2.1 recvfrom
cpp 复制代码
#include <sys/types.h>
#include <sys/socket.h>
/**
 * @brief 将接收到的消息放入缓冲区 buf 中。
 * 
 * @param sockfd 套接字文件描述符
 * @param buf 缓冲区指针
 * @param len 缓冲区大小
 * @param flags 通信标签,详见recv方法说明
 * @param src_addr 可以填NULL,如果 src_addr 不是 NULL,并且底层协议提供了消息的源地址,则该源地址将被放置在 src_addr 指向的缓冲区中。
 * @param addrlen 如果src_addr不为NULL,它应初始化为与 src_addr 关联的缓冲区的大小。返回时,addrlen 被更新为包含实际源地址的大小。如果提供的缓冲区太小,则返回的地址将被截断;在这种情况下,addrlen 将返回一个大于调用时提供的值。
 * @return ssize_t 实际收到消息的大小。如果接收失败,返回-1
 */
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
2.2 sendto
cpp 复制代码
/**
 * @brief 向指定地址发送缓冲区中的数据(一般用于UDP模式)
 * 
 * @param sockfd 套接字文件描述符
 * @param buf 缓冲区指针
 * @param len 缓冲区大小
 * @param flags 通信标签,详细减send方法说明
 * @param dest_addr 目标地址。如果用于连接模式,该参数会被忽略
 * @param addrlen 目标地址长度
 * @return ssize_t 发送的消息大小。发送失败返回-1
 */
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

三、实战范例

3.1 服务端代码

服务端的核心在于必须绑定端口,否则客户端无法找到它。

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

#define handle_error(cmd,result) \
    if (result < 0)              \
    {                            \
        perror(cmd);             \
        return -1;               \
    }                            \
    

int main(int argc, char const *argv[])
{
    // 定义socket的文件描述符
    int sockfd,temp_result;
    // 定义客户端与服务端的套结字数据结构,供之后填写
    struct sockaddr_in server_addr,client_addr;
    // 清空
    memset(&server_addr,0,sizeof(server_addr));
    memset(&client_addr,0,sizeof(client_addr));

    // 填写服务段地址,指定协议
    server_addr.sin_family = AF_INET;
    // 填写ip地址 INADDR_ANY就是0.0.0.0
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // 填写端口号
    server_addr.sin_port = htons(6666);

    //udp协议socket编程流程
    // 1. 创建socket
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    handle_error("socket",sockfd);
    // 2. bind函数绑定socket地址
    temp_result = bind(sockfd,(struct sockaddr*)&server_addr,
    sizeof(server_addr));
    handle_error("bind",temp_result);

    char* buf = malloc(sizeof(char) * 1024);
    // 不需要监听、接受,直接进行连接
    do
    {
        // 清0缓冲区
        memset(buf,0,1024);
        // 接收数据到缓冲区
        socklen_t client_len = sizeof(client_addr);
        temp_result = recvfrom(sockfd,buf,1024,0,
            (struct sockaddr*)&client_addr,&client_len);
        handle_error("recfrom",temp_result);

        if (strncmp(buf,"EOF",3) != 0)
        {
            printf("接收到客户端%s %d的信息:%s\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),buf);
            strcpy(buf,"OK\n");
        }else
        {
            printf("收到结束消息 准备关闭\n");
        }
        // 回复数据
        temp_result = sendto(sockfd,buf,strlen(buf),0,
        (struct sockaddr*)&client_addr
        ,sizeof(client_addr));
        handle_error("sendto",temp_result);

    } while (strncmp(buf,"EOF",3) != 0);
    
    free(buf);
    close(sockfd);
    return 0;
}
3.2 客户端代码
cpp 复制代码
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <unistd.h>

#define handle_error(cmd,result) \
    if (result < 0)              \
    {                            \
        perror(cmd);             \
        return -1;               \
    }                            \
    

int main(int argc, char const *argv[])
{
    // 定义socket的文件描述符
    int sockfd,temp_result;
    // 定义客户端与服务端的套结字数据结构,供之后填写
    struct sockaddr_in server_addr,client_addr;
    // 清空
    memset(&server_addr,0,sizeof(server_addr));
    memset(&client_addr,0,sizeof(client_addr));

    // 填写服务段地址,指定协议
    server_addr.sin_family = AF_INET;
    // 填写ip地址 INADDR_ANY就是0.0.0.0
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    // 填写端口号
    server_addr.sin_port = htons(6666);

    //udp协议socket编程流程
    // 1. 创建socket
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    handle_error("socket",sockfd);
    // 2. bind函数绑定socket地址
    temp_result = bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
    handle_error("bind",temp_result);

    char* buf = malloc(sizeof(char) * 1024);
    // 不需要监听、接受,直接进行连接
    do
    {
        // 清空缓冲区 用来接收数据
        memset(buf,0,1024);

        temp_result = recvfrom(sockfd,buf,1024,0,NULL,NULL);
        handle_error("recefrom",temp_result);

        if (strncmp(buf,"EOF",3) != 0)
        {
            printf("收到服务端%s %d返回的数据%s\n",inet_ntoa(server_addr.sin_addr),
            ntohs(server_addr.sin_port),buf);
            break;
        }
        

    } while (strncmp(buf,"EOF",3) != 0);
    
    free(buf);
    close(sockfd);

    return 0;
}

可以看到其实UDP的编程流程比TCP的要简单很多。

测试:

开启服务端

客户端建立UDP通讯

发送消息:

服务端接收到来自客户端的消息

四、UDP vs TCP ------ 编程流程清晰对比总结

1. 编程流程对比

TCP 服务端
cpp 复制代码
socket() bind() listen() accept() ← 创建连接 recv()/send() close()
UDP 服务端
cpp 复制代码
socket() bind() recvfrom()/sendto() close()

2. 客户端对比

TCP 客户端
cpp 复制代码
socket() connect() send()/recv()
UDP 客户端
cpp 复制代码
socket() sendto()/recvfrom()

3. API 差异

API TCP UDP
connect 必须,建立连接 可选,仅绑定目标地址
listen/accept 必须 不需要
send/recv 用于已连接的套接字 多用于 connect 之后
sendto/recvfrom 一般不用 UDP 必须

4. 并发模型差异

TCP 服务端 UDP 服务端
每个客户端一条连接,需要多线程/epoll 所有客户端共享同一个 socket
连接维护成本高 无连接,性能高

总结

UDP编程相比TCP显得更加"自由"和"原始"。它剥离了繁重的连接管理,还原了网络通信最本质的数据投递功能。作为C++开发者,掌握UDP不仅仅是学会几个API,更是理解可靠性与效率之间权衡的艺术。

希望这篇博客能成为你学习路上的"知识锚点"。欢迎点赞、收藏、评论交流!

相关推荐
model20052 小时前
Alibaba linux 3安装LAMP(3)
linux·运维·服务器
JosieBook2 小时前
【IDEA】IntelliJ IDEA 快捷键大全(Windows/Linux 版)
linux·windows·intellij-idea
赖small强2 小时前
【Linux 内存管理】Kernel Buddy 分配器:Page Block Size 实现原理与工作机制深度解析
linux·buddy·page block size
SaaS_Product2 小时前
企业网盘怎么注册?一文解读申请流程
网络·人工智能·云计算·saas·onedrive
robin59112 小时前
容器-汇总所有环境下的日志排查问题
linux·容器·kubernetes
朗晴2 小时前
Linux修改SSH远程端口号22!
linux·运维·ssh
赖small强3 小时前
【Linux 驱动开发】Linux PWM (脉冲宽度调制) 全面技术指南
linux·驱动开发·pwm
代码游侠3 小时前
Linux系统编程 - 文件操作
linux·运维·服务器·学习
涡轮蒸鸭猫喵3 小时前
-------------------UDP协议+TCP协议-------------------------
java·网络·笔记·网络协议·tcp/ip·udp