网络编程 | UDP套接字通信及编程实现经验教程

news2025/1/19 15:49:59

1、UDP基础

        传输层主要应用的协议模型有两种,一种是TCP协议,另外一种则是UDP协议。在上一篇博客文章中,已经对TCP协议及如何编程实现进行了详细的梳理讲解,在本文中,主要讲解与TCP一样广泛使用了另一种协议:UDP(User Datagram Protocol)用户数据报协议。

        网络编程 | TCP套接字通信及编程实现经验教程_tcp套接字通讯-CSDN博客

        TCP协议在网络通信中占主导地位,绝大多数的网络通信借助TCP协议完成数据传输。但UDP也是网络通信中不可或缺的重要通信手段。相较于TCP而言,UDP通信的形式更像是发短信。不需要在数据传输之前建立、维护连接。只专心获取数据就好。省去了三次握手的过程,通信速度可以大大提高,但与之伴随的通信的稳定性和正确率便得不到保证。因此,我们称UDP为“无连接的不可靠的报文传输协议”。

        每个 UDP 报文分为 UDP 报头和 UDP 数据区两部分。其中,报头由 4 个 16 位长(2 字节)字段组成,分别说明该报文的源端口、目的端口、报文长度和校验值。

①、源端口:16位长(2字节),标识发送该报文的进程端口号。如果不需要回复,则可以设置为0。

②、目的端口:同样是16位长(2字节),标识接收该报文的目标进程端口号。

③、报文长度:16位长(2字节),表示整个UDP报文(包括报头和数据区)的长度,单位是字节。最小值为8字节,即只有UDP报头而没有数据的情况。

④、校验值:16位长(2字节),用于错误检查。它覆盖了UDP报头和数据以及一个伪头部的信息。计算时还包括IP头部的一些字段来确保数据在传输过程中没有被修改。如果校验和未计算(通常是为了提高效率),则此字段设置为0。

UDP的特点:

①、无连接:与TCP不同,UDP在传输数据前不需要建立连接,减少了通信的准备时间。

②、不可靠传输:没有像TCP那样的确认机制、重传机制以及流量控制,因此无法保证数据能够准确无误地到达接收端。

③、面向数据报:每个UDP报文独立处理,大小限制通常为64KB以内,包括头部信息。

④、全双工操作:允许同时进行双向的数据传输。

⑤、高效性:由于省去了连接建立的过程,UDP具有较低的延迟和较高的传输效率。

UDP的应用场景:

①、实时性要求高的应用:例如视频会议、电话会议等需要快速传递信息的服务,即使偶尔丢失一些数据包也不会严重影响整体体验。

②、对少量数据丢失容忍度高的应用:比如在线游戏、VoIP等,这类应用更注重数据传输的速度而非完整性。

③、广播/组播通信:UDP支持一对多的通信模式,适合用于广播或组播式的信息发布。

④、即时通讯软件:许多即时通讯工具使用UDP来实现文本消息及音视频通话功能,因为它可以提供更快的响应速度。

解决UDP丢包问题的方法:

①、服务器端设计流量控制:通过调整发送速率避免因过快发送数据而导致的丢包现象。

②、调整接收缓冲区大小:利用setsockopt函数动态修改接收缓冲区容量,以适应不同的网络状况。

        为了便于读者更加直观的理解UDP,并了解UDP和TCP之间的最明显的区别,绘制了如下所示的简易图。

2、UDP通信流程

        相比于TCP,由于UDP是无连接的协议,任何一方都可以在任意时刻发送数据报给另一方,服务器和客户端的界限并没有那么清晰,且客户端与服务器的通信代码流程几乎相同,也就没有非常严格的客户端与服务器之分。但为了保持程序代码框架的逻辑清晰以及便于理解和维护,因此还是需要指定一端作为服务器、另一端作为客户端。

        由于UDP不需要维护连接,程序逻辑简单了很多,但是UDP协议是不可靠的,保证通讯可靠性的机制需要在上层应用层中根据需要,通过优化程序的逻辑架构来完善。

3、UDP通信代码

3.1、UDP常用API接口

(1)、socket
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

函数功能:
    用于创建一个新的套接字,它支持多种协议和地址族,可以用来实现不同类型的网络通信。
参数说明:
    domain: 指定通信域,常用的有:
        AF_INET:IPv4互联网协议族。
        AF_INET6:IPv6互联网协议族。
    type: 指定套接字类型,对于UDP应使用:
        SOCK_DGRAM:支持UDP协议的数据报套接字。
    protocol: 指定使用的协议。对于UDP来说,虽然可以指定为IPPROTO_UDP,但更常见的是设置为0,表示使用由type参数决定的默认协议(在这种情况下就是UDP)。
返回值:
    成功时,返回一个非负整数,代表新创建的套接字描述符。
    失败时,返回-1,并且会设置全局变量errno来指示具体的错误原因。
(2)、bind
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函数功能:
    为套接字sockfd分配一个本地地址addr。如果要接收发送到特定端口的数据报时,必须先调用此函数。
参数说明:
    sockfd:由socket()函数创建的套接字描述符。
    addr:指向sockaddr结构的指针,该结构包含欲绑定到套接字的地址信息。对于IPv4,通常使用sockaddr_in结构,并需要将其转换为sockaddr*类型来传递给bind()。
    addrlen:addr参数所指向的数据结构的大小,以字节为单位。对于IPv4的sockaddr_in结构体,通常是sizeof(struct sockaddr_in)。
返回值:
    成功时,返回0。
    失败时,返回-1,并设置errno变量指示错误原因。
(3)、sendto
#include <sys/types.h>
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

函数功能
    用于从套接字 sockfd 发送数据。当使用 UDP 协议时,这个函数可以将数据报发送到由 dest_addr 指定的目的地。
参数说明
    sockfd: 已经创建好的UDP套接字描述符。
    buf: 指向要发送的数据缓冲区的指针。
    len: 缓冲区 buf 中数据的长度(以字节为单位)。
    flags: 调用操作选项,通常设置为0。
    dest_addr: 目标地址,指向一个包含目标IP地址和端口号的 sockaddr 结构体。对于IPv4,这通常是 sockaddr_in 类型的结构体。
    addrlen: dest_addr参数所指向的数据结构的大小,以字节为单位。
返回值
    成功时,返回实际发送的字节数。
    失败时,返回 -1,并设置 errno 变量指示错误原因。
(4)、recvfrom
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

函数功能
    用于从套接字 sockfd 接收数据。此函数可以接收来自任意发送方的UDP数据报,并可选性的保存发送方的地址信息。
参数说明
    sockfd: 已经创建并绑定到本地地址的UDP套接字描述符。
    buf: 指向缓冲区的指针,用于存储接收到的数据。
    len: 缓冲区 buf 的大小(以字节为单位)。
    flags: 调用操作选项,通常设置为0。
    src_addr: 指向一个 sockaddr 结构体的指针,该结构体将保存发送方的地址信息。如果不需要获取发送方地址,此参数可以为NULL。
    addrlen: 指向 socklen_t 类型变量的指针,表示 src_addr 结构体的大小。调用前应初始化为该结构体的大小;返回时包含实际存储在 src_addr 中的地址信息长度。
返回值
    成功时,返回实际接收到的字节数。
    失败时,返回 -1,并设置 errno 变量指示错误原因。
(5)、close
#include <unistd.h>
int close(int fd);

函数功能
    关闭由文件描述符 fd 指定的文件或套接字,并释放所有与之相关的资源。
函数参数
    fd: 要关闭的文件描述符或套接字描述符。
返回值
    成功时返回 0。
    失败时返回 -1,并设置 errno 变量来指示错误类型。

3.2、UDP参考程序

(1)UDP 客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define PORT 8080
#define SERVER_IP "127.0.0.1"

int main(int argc, char **argv)
{
    int sock_client;
    struct sockaddr_in servaddr, cliaddr;
    char message[1024];
    char buffer[1024];
    socklen_t addrLen = sizeof(cliaddr);
    
    // 创建UDP套接字
    if ((sock_client = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("UDP client socket create success!\n");

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(PORT);
    servaddr.sin_addr.s_addr = inet_addr(SERVER_IP);
    
    while(1) 
    {
        printf("Input data to send to server: ");
        fgets(message, 1024, stdin);
        
        // 发送数据到服务器
        sendto(sock_client, (const char *)message, strlen(message), MSG_CONFIRM, (const struct sockaddr *) &servaddr, sizeof(servaddr));
        
        // 接收来自服务器的数据
        ssize_t n = recvfrom(sock_client, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *) &cliaddr, &addrLen);
        buffer[n] = '\0';
        
        // 打印接收到的消息、服务器IP地址和端口号
        printf("Received from udp server: %s", buffer);
        printf("Server IP: %s\tPort: %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
    }
    
    close(sock_client);
    
    return 0;
}
(2)UDP服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080

int main(int argc, char **argv) 
{
    int sock_server;
    char buffer[1024];
    struct sockaddr_in servaddr, cliaddr;
    socklen_t len;

    // 创建UDP套接字
    if ((sock_server = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
    {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }
    printf("UDP server socket create success!\n");

    memset(&servaddr, 0, sizeof(servaddr));
    memset(&cliaddr, 0, sizeof(cliaddr));
    
    servaddr.sin_family = AF_INET; // IPv4
    servaddr.sin_addr.s_addr = INADDR_ANY;
    servaddr.sin_port = htons(PORT);

    // 绑定套接字到指定端口
    if (bind(sock_server, (const struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) 
    {
        perror("bind failed");
        close(sock_server);
        exit(EXIT_FAILURE);
    }

    printf("Server is listening on port %d...\n", PORT);

    while(1) 
    {
        len = sizeof(cliaddr);
        // 接收来自客户端的数据
        ssize_t n = recvfrom(sock_server, (char *)buffer, 1024, MSG_WAITALL, (struct sockaddr *) &cliaddr, &len);
        buffer[n] = '\0';
        
        // 打印接收到的消息、客户端IP地址和端口号
        printf("Received from UDP client: %s", buffer);
        printf("Client IP: %s\tPort: %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));

        // 发送数据回客户端
        sendto(sock_server, (const char *)buffer, strlen(buffer), MSG_CONFIRM, (const struct sockaddr *) &cliaddr, len);
    }

    close(sock_server);

    return 0;
}

        上述代码的验证结果如下如下所示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2278973.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Linux】线程全解:概念、操作、互斥与同步机制、线程池实现

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da;一、线程概念 &#x1f4d6; 回顾进程 &#x1f4d6; 引入线程 &#x1f4d6; 总结 &a…

掌握未来:从零开始学习生成式AI大模型!

随着人工智能技术的飞速发展&#xff0c;生成式AI大模型已成为当今科技领域的热点。从文本生成、图像创作到音乐创作&#xff0c;生成式AI大模型在各个领域展现出惊人的潜力。本文将带领大家从零开始&#xff0c;逐步学习生成式AI大模型&#xff0c;掌握未来的关键技术。 一、生…

多肽合成 -- 液相合成(liquid-phase peptide synthesis (LPPS))

液相合成的定义 液相合成&#xff08;Solution Synthesis&#xff09;是指在液体介质中进行的化学合成反应&#xff0c;是化学合成中一种基本且重要的方法。它涉及到将反应物溶解在溶剂中&#xff0c;在一定的温度、压力和其他反应条件下进行化学反应&#xff0c;从而生成所需的…

第23篇 基于ARM A9处理器用汇编语言实现中断<五>

Q&#xff1a;怎样修改HPS Timer 0定时器产生的中断周期&#xff1f; A&#xff1a;在上一期实验的基础上&#xff0c;可以修改按键中断服务程序&#xff0c;实现红色LED上的计数值递增的速率&#xff0c;主程序和其余代码文件不用修改。 实现以下功能&#xff1a;按下KEY0…

ChatGPT大模型极简应用开发-CH1-初识 GPT-4 和 ChatGPT

文章目录 1.1 LLM 概述1.1.1 语言模型和NLP基础1.1.2 Transformer及在LLM中的作用1.1.3 解密 GPT 模型的标记化和预测步骤 1.2 GPT 模型简史&#xff1a;从 GPT-1 到 GPT-41.2.1 GPT11.2.2 GPT21.2.3 GPT-31.2.4 从 GPT-3 到 InstructGPT1.2.5 GPT-3.5、Codex 和 ChatGPT1.2.6 …

2025春秋杯冬季赛 day1 crypto

文章目录 通往哈希的旅程小哈斯RSA1ez_rsa 通往哈希的旅程 根据提示推断是哈希函数&#xff0c;ai一下&#xff0c;推测大概率是一个sha1&#xff0c;让ai写一个爆破脚本即可 import hashlib# 给定目标 SHA-1 哈希值 target_hash "ca12fd8250972ec363a16593356abb1f3cf…

广播网络实验

1 实验内容 1、构建星性拓扑下的广播网络,实现hub各端口的数据广播,验证网络的连通性并测试网络效率 2、构建环形拓扑网络,验证该拓扑下结点广播会产生数据包环路 2 实验流程与结果分析 2.1 实验环境 ubuntu、mininet、xterm、wireshark、iperf 2.2 实验方案与结果分析…

RustDesk ID更新脚本

RustDesk ID更新脚本 此PowerShell脚本自动更新RustDesk ID和密码&#xff0c;并将信息安全地存储在Bitwarden中。 特点 使用以下选项更新RustDesk ID&#xff1a; 使用系统主机名生成一个随机的9位数输入自定义值 为RustDesk生成新的随机密码将RustDesk ID和密码安全地存储…

JavaEE之常见的锁策略

前面我们学习过线程不安全问题&#xff0c;我们通过给代码加锁来解决线程不安全问题&#xff0c;在生活中我们也知道有很多种类型的锁&#xff0c;同时在代码的世界当中&#xff0c;也对应着很多类型的锁&#xff0c;今天我们对锁一探究竟&#xff01; 1. 常见的锁策略 注意: …

数字图像处理:实验二

任务一&#xff1a; 将不同像素&#xff08;32、64和256&#xff09;的原图像放大为像素大 小为1024*1024的图像&#xff08;图像自选&#xff09; 要求&#xff1a;1&#xff09;输出一幅图&#xff0c;该图包含六幅子图&#xff0c;第一排是原图&#xff0c;第 二排是对应放大…

WEB渗透技术研究与安全防御

目录 作品简介I IntroductionII 1 网络面临的主要威胁1 1.1 技术安全1 2 分析Web渗透技术2 2.1 Web渗透技术的概念2 2.2 Web漏洞产生的原因2 2.3 注入测试3 2.3.1 注入测试的攻击流程3 2.3.2 进行一次完整的Sql注入测试4 2.3.3 Cookie注入攻击11 3 安全防御方案设计…

使用 Thermal Desktop 进行航天器热分析

介绍 将航天器保持在运行温度下的轨道上是一个具有挑战性的问题。航天器需要处理太空非常寒冷的背景温度&#xff0c;同时还要管理来自内部组件、地球反照率和太阳辐射的高热负荷。航天器在轨道上可以进行的各种轨道机动使解决这个问题变得更加复杂。 Thermal Desktop 是一款…

乘联会:1月汽车零售预计175万辆 环比暴跌33.6%

快科技1月18日消息&#xff0c;据乘联会的初步推算&#xff0c;2025年1月狭义乘用车零售总市场规模预计将达到约175万辆左右。与去年同期相比&#xff0c;这一数据呈现了-14.6%的同比下降态势&#xff1b;而相较于上个月&#xff0c;则出现了-33.6%的环比暴跌情况。 为了更清晰…

SQL 递归 ---- WITH RECURSIVE 的用法

SQL 递归 ---- WITH RECURSIVE 的用法 开发中遇到了一个需求&#xff0c;传递一个父类id&#xff0c;获取父类的信息&#xff0c;同时获取其所有子类的信息。 首先想到的是通过程序中去递归查&#xff0c;但这种方法着实孬了一点&#xff0c;于是想&#xff0c;sql能不能递归查…

【机器学习实战入门项目】使用深度学习创建您自己的表情符号

深度学习项目入门——让你更接近数据科学的梦想 表情符号或头像是表示非语言暗示的方式。这些暗示已成为在线聊天、产品评论、品牌情感等的重要组成部分。这也促使数据科学领域越来越多的研究致力于表情驱动的故事讲述。 随着计算机视觉和深度学习的进步&#xff0c;现在可以…

windows 搭建flutter环境,开发windows程序

环境安装配置&#xff1a; 下载flutter sdk https://docs.flutter.dev/get-started/install/windows 下载到本地后&#xff0c;随便找个地方解压&#xff0c;然后配置下系统环境变量 编译windows程序本地需要安装vs2019或更新的开发环境 主要就这2步安装后就可以了&#xff0…

【Linux】15.Linux进程概念(4)

文章目录 程序地址空间前景回顾C语言空间布局图&#xff1a;代码1代码2代码3代码4代码5代码6代码7 程序地址空间前景回顾 历史核心问题&#xff1a; pid_t id fork(); if(id 0) else if(id>0) 为什么一个id可以放两个值呢&#xff1f;之前没有仔细讲。 C语言空间布局图&am…

一文读懂服务器的HBA卡

什么是 HBA 卡 HBA 卡&#xff0c;全称主机总线适配器&#xff08;Host Bus Adapter&#xff09; &#xff0c;是服务器与存储装置间的关键纽带&#xff0c;承担着输入 / 输出&#xff08;I/O&#xff09;处理及物理连接的重任。作为一种电路板或集成电路适配器&#xff0c;HBA…

oracle使用case when报错ORA-12704字符集不匹配原因分析及解决方法

问题概述 使用oracle的case when函数时&#xff0c;报错提示ORA-12704字符集不匹配&#xff0c;如下图&#xff0c;接下来分析报错原因并提出解决方法。 样例演示 现在有一个TESTTABLE表&#xff0c;本表包含的字段如下图所示&#xff0c;COL01字段是NVARCHAR2类型&#xff0…

Linux-----线程同步(条件变量)

目录 相关API restrict关键字 线程间条件切换函数 条件变量pthread_cond_t 案例 在前面的锁的基础上进一步提高线程同步效率&#xff0c;也就是两个线程只用锁去执行的话依然会存在资源竞争的情况&#xff0c;也就是抢锁&#xff0c;这里就需要在锁的这边加上限制&#xf…