使用UDP套接字编程详解【C语言】

news2024/9/23 2:30:35

UDP(User Datagram Protocol,用户数据报协议)是一种面向无连接的传输层协议,用于在计算机网络上发送数据。它与 TCP(Transmission Control Protocol,传输控制协议)相比具有轻量、高效的特点,但牺牲了可靠性和顺序传输的保证。udp的通信过程默认也是阻塞的。

  • 无连接:UDP 是无连接的,不会维护连接状态,每次发送数据都是独立的。
  • 不可靠性:UDP 不保证数据的可靠传输,数据报文可能丢失、重复或顺序错乱。
  • 数据包大小:UDP 数据报文大小受限制,超过网络的最大传输单元(MTU)会被分片,可能导致性能下降。
  • 顺序保证:UDP 不保证数据报文的顺序交付,接收方收到数据的顺序可能与发送方不同。
  • 适用场景:适合实时数据传输,如实时音视频流、游戏数据更新等,对实时性要求高而对数据完整性要求较低的场景。

1.UDP通信流程

  • 创建套接字

    • 首先,服务器端和客户端分别创建 UDP 套接字。套接字是应用程序与网络之间的接口,用于发送和接收数据。
  • 绑定地址和端口(可选):

    • 在服务器端,可以选择将套接字绑定到特定的 IP 地址和端口上,以便客户端能够发送数据到指定的地址。客户端通常不需要绑定,而是直接向服务器发送数据。
  • 发送数据报文

    • 客户端通过 sendto 函数将数据报文发送到服务器端的指定地址和端口。UDP 不需要建立连接,因此每个数据报文都是独立发送的,不会进行握手或状态管理。
  • 接收数据报文

    • 服务器端使用 recvfrom 函数接收从客户端发送过来的数据报文。这个函数能够获取发送方的地址信息,以便服务器端可以知道从哪个客户端接收到数据。
  • 处理数据

    • 服务器端和客户端在接收到数据后,可以根据协议和应用程序的需求对数据进行处理,例如解析、验证、响应等操作。
  • 关闭套接字

    • 当通信结束或者程序不再需要使用套接字时,服务器端和客户端分别使用 close 函数关闭套接字,释放资源。

2.相关操作函数

2.1 socket 函数

#include <sys/socket.h>
int socket(int domain, int type, int protocol);
功能:创建一个套接字。
参数:
    domain:指定协议族,常用的有 AF_INET(IPv4 地址)和 AF_INET6(IPv6 地址)。
    type:指定套接字类型,常用的有 SOCK_DGRAM(数据报套接字,UDP)和 SOCK_STREAM(流式套接字,TCP)。
    protocol:通常设置为 0,表示使用默认协议。
返回值:返回一个非负的文件描述符(套接字描述符),表示套接字创建成功;返回 -1 表示创建失败。

2.2 bind 函数

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将一个本地地址绑定到套接字。
参数:
    sockfd:套接字描述符,即 socket 函数返回的文件描述符。
    addr:指向 sockaddr 结构体的指针,包含要绑定的 IP 地址和端口信息。
    addrlen:addr 结构体的长度。
返回值:成功返回 0,失败返回 -1。
注意事项:
服务器端通常需要绑定一个固定的 IP 地址和端口,以便客户端能够发送数据到指定的地址。
客户端一般不需要绑定,而是直接发送数据到服务器端。

2.3 sendto 函数

#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:套接字描述符。
    buf:指向要发送数据的缓冲区。
    len:要发送数据的长度。
    flags:通常设置为 0,表示默认行为。
    dest_addr:指向 sockaddr 结构体的指针,包含目标地址(IP 地址和端口)信息。IP和端口都存储在这里边, 是大端存储的
    addrlen:dest_addr 结构体的长度。
返回值:成功返回发送的字节数,失败返回 -1。
注意事项:
UDP 是无连接的,每次发送数据时都要指定目标地址。
数据报文的大小应小于网络的最大传输单元(MTU),以免发生分片,影响性能。

2.4 recvfrom 函数

#include <sys/socket.h>
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:指向 sockaddr 结构体的指针,用于存放发送方的地址信息。IP和端口都存储在这里边, 是大端存储的
    addrlen:src_addr 结构体的长度,在调用时需传入 src_addr 的实际长度。
返回值:成功返回接收到的字节数,失败返回 -1。
注意事项:
recvfrom 函数会阻塞直到接收到数据或发生错误。
可以通过 src_addr 参数获取发送方的 IP 地址和端口信息,用于处理接收到的数据。

2.5 close 函数

#include <unistd.h>
int close(int sockfd);
功能:关闭套接字。
参数:
    sockfd:要关闭的套接字描述符。
返回值:成功返回 0,失败返回 -1。
注意事项:
在不再需要使用套接字时应及时关闭,释放系统资源。
关闭套接字后,相关的文件描述符将不再可用,不能再进行数据发送和接收操作。

3.示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8888
#define MAXLINE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    char buffer[MAXLINE];
    socklen_t client_len;
    ssize_t n;

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));
    memset(&client_addr, 0, sizeof(client_addr));

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    // 绑定服务器地址到套接字
    if (bind(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

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

    while (1) {
        client_len = sizeof(client_addr);

        // 接收来自客户端的消息
        n = recvfrom(sockfd, (char *)buffer, MAXLINE, MSG_WAITALL, (struct sockaddr *)&client_addr, &client_len);
        buffer[n] = '\0';

        // 打印客户端信息
        char client_ip[INET_ADDRSTRLEN];
        inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
        printf("Received message from %s:%d - %s\n", client_ip, ntohs(client_addr.sin_port), buffer);

        // 发送响应消息给客户端
        strcpy(buffer, "Hello from server");
        sendto(sockfd, (const char *)buffer, strlen(buffer), MSG_CONFIRM, (const struct sockaddr *)&client_addr, client_len);
        printf("Server : Hello message sent\n");
    }

    close(sockfd);
    return 0;
}

作为数据接收端,服务器端通过bind()函数绑定了固定的端口,然后基于这个固定的端口通过recvfrom()函数接收客户端发送的数据,同时通过这个函数也得到了数据发送端的地址信息(recvfrom的第三个参数),这样就可以通过得到的地址信息通过sendto()函数给客户端回复数据了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define PORT 8888
#define MAXLINE 1024

int main() {
    int sockfd;
    struct sockaddr_in server_addr;
    char buffer[MAXLINE];
    socklen_t server_len;
    ssize_t n;

    // 创建UDP套接字
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket creation failed");
        exit(EXIT_FAILURE);
    }

    memset(&server_addr, 0, sizeof(server_addr));

    // 设置服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(PORT);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    while (1) {
        printf("Enter message to send : ");
        fgets(buffer, MAXLINE, stdin);

        // 发送消息给服务器
        sendto(sockfd, (const char *)buffer, strlen(buffer), MSG_CONFIRM, (const struct sockaddr *)&server_addr, sizeof(server_addr));
        printf("Message sent to server.\n");

        // 接收服务器的响应消息
        n = recvfrom(sockfd, (char *)buffer, MAXLINE, MSG_WAITALL, (struct sockaddr *)&server_addr, &server_len);
        buffer[n] = '\0';
        printf("Server : %s\n", buffer);
    }

    close(sockfd);
    return 0;
}

作为数据发送端,客户端不需要绑定固定端口,客户端使用的端口是随机绑定的(也可以调用bind()函数手动进行绑定)。客户端在接收服务器端回复的数据的时候需要调用recvfrom()函数,因为客户端在发送数据之前就已经知道服务器绑定的固定的IP和端口信息了,所以接收服务器数据的时候就可以不保存服务器端的地址信息,直接将函数的最后两个参数指定为NULL即可。

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

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

相关文章

redis原理之底层数据结构-跳表

1.什么是跳表 1.1 链表及其不足 链表是在程序设计中最常见的数据结构之一&#xff0c;它通过指针将多个链表节点连接起来&#xff0c;这样就可以将逻辑上同一类的数据存储到不连续的内存空间上。链表结构如下&#xff1a; 但是链表有一个问题&#xff0c;就是当链表需要查询一…

JSON 文件第一段飘红

问题 原因 这个问题通常发生在尝试用 ESLint 去解析 JSON 文件时。ESLint 主要设计用于检查 JavaScript 代码的语法和风格&#xff0c;而JSON是一种数据交换格式&#xff0c;不包含 JavaScript 的逻辑结构&#xff0c;如函数、变量声明等。 解释报错原因 当ESLint遇到它不能识…

Nginx 如何处理 WebSocket 连接?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01; 文章目录 Nginx 如何处理 WebSocket 连接&#xff1f;一、WebSocket 连接简介二、Nginx 处理 WebSocket 连接的基本原理三、配置 Nginx 支持 WebSocket 连接四、Nginx 中的…

MyBatis-Plus的基本使用(一)

目录 前言 特性 MyBatis-Plus入门案例 常用注解 小结 前言 这篇文章主要来学习MyBatis-Plus这个非常强大的框架. 在学习MyBatis-Plus之前,需要有MyBatis的学习基础.因为MyBatis -Plus 是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#x…

Window下安装Zookeeper

一、下载 地址&#xff1a;https://archive.apache.org/dist/zookeeper/zookeeper-3.5.6/ 解压&#xff1a;非中文、没有空格目录下 新建data目录&#xff0c;用于存放数据文件 二、配置 进入conf目录&#xff0c;复制zoo_sample.cfg 为zoo.cfg 打开zoo.cfg 修改dataDir&…

PyTorch基于注意力的目标检测模型DETR

【图书推荐】《PyTorch深度学习与计算机视觉实践》-CSDN博客 目标检测是计算机视觉领域的一个重要任务&#xff0c;它的目标是在图像或视频中识别并定位出特定的对象。在这个过程中&#xff0c;需要确定对象的位置和类别&#xff0c;以及可能存在的多个实例。 DETR模型通过端…

2.3 大模型硬件基础:AI芯片(上篇) —— 《带你自学大语言模型》系列

本系列目录 《带你自学大语言模型》系列部分目录及计划&#xff0c;完整版目录见&#xff1a;带你自学大语言模型系列 —— 前言 第一部分 走进大语言模型&#xff08;科普向&#xff09; 第一章 走进大语言模型 1.1 从图灵机到GPT&#xff0c;人工智能经历了什么&#xff1…

vue3 学习笔记17 -- 基于el-menu封装菜单

vue3 学习笔记17 – 基于el-menu封装菜单 前提条件&#xff1a;组件创建完成 配置路由 // src/router/index.ts import { createRouter, createWebHashHistory } from vue-router import type { RouteRecordRaw } from vue-router export const Layout () > import(/lay…

FlutterFlame游戏实践#16 | 生命游戏 - 编辑与交互

theme: cyanosis 本文为稀土掘金技术社区首发签约文章&#xff0c;30天内禁止转载&#xff0c;30天后未获授权禁止转载&#xff0c;侵权必究&#xff01; Flutter\&Flame 游戏开发系列前言: 该系列是 [张风捷特烈] 的 Flame 游戏开发教程。Flutter 作为 全平台 的 原生级 渲…

ios 15-16手机绕过ssl验证(抓取app上的https包)

绕过ssl验证的基本流程 前提概要&#xff1a;为什么你的charles抓不了https包 ios 越狱ios rootful安装ios 越狱商店sileo安装substitute越狱商店安装SSL Kill Switch3 全流程坑点巨多&#xff0c;博主亲身踩坑&#xff0c;务必按着步骤来 准备工作 type b to c 的数据线苹果…

读论文《Hi-Net: Hybrid-fusion Network for Multi-modalMR Image Synthesis》

论文题目&#xff1a;Hi-Net:用于多模态磁共振图像合成的混合融合网络 论文地址&#xff1a;arxiv 项目地址&#xff1a;github 原项目可能在训练的时候汇报version的错&#xff0c;这是因为生成器和辨别器的优化有些逻辑错误&#xff0c;会改的话多加一个生成操作可以解决&…

数字信号处理基础知识(二)

在介绍完“离散时间序列”基本概念和性质后&#xff0c;实际上就已经踏入了“数字信号处理”这门学科的学习征程&#xff0c;这篇文章里主要去说明“线性时不变系统”的定义概念和探讨“周期采样”的注意细节&#xff0c;相信更加理解这些概念定义和底层逻辑&#xff0c;对于大…

python+vue3+onlyoffice在线文档系统实战20240723笔记,项目界面设计和初步开发

经过之前的学习,已经能够正常打开文档了。 目前为止,我们的代码能够实现: 打开文档编辑文档手动保存自动保存虽然功能依然比较少,但是我们已经基本实现了文档管理最核心的功能,而且我们有个非常大的优势,就是支持多人同时在线协同编辑。 现在我们要开发项目,我们得做基…

Golang | Leetcode Golang题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; // 判断是否为完全平方数 func isPerfectSquare(x int) bool {y : int(math.Sqrt(float64(x)))return y*y x }// 判断是否能表示为 4^k*(8m7) func checkAnswer4(x int) bool {for x%4 0 {x / 4}return x%8 7 }func numSquares(n int) i…

Python的注释怎么写

今天我们讲一下Python的注释怎么写&#xff0c;Python的注释的写法主要就是用""" &#xff08;注释&#xff09;"""和 #&#xff08;注释&#xff08;多半就是一行&#xff09;&#xff09; 来写 第一种&#xff1a; 使用""" &…

【linux】Shell脚本三剑客之sed命令的详细用法攻略

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…

从零开始:神经网络(1)——什么是人工神经网络

声明&#xff1a;本文章是根据网上资料&#xff0c;加上自己整理和理解而成&#xff0c;仅为记录自己学习的点点滴滴。可能有错误&#xff0c;欢迎大家指正。 人工神经网络&#xff08;Artificial Neural Network&#xff0c;简称ANN&#xff09;是一种模仿生物神经网络结构和功…

【vue教程】三. 组件复用和通信(7 种方式)

目录 本章涵盖知识点回顾 组件开发与复用组件的创建和注册全局定义局部定义单文件组件&#xff08;.vue 文件&#xff09;组件的注册方式在实例中注册在 Vue 中注册 组件的 props定义 props传递 props 组件事件自定义事件的创建和触发父组件监听子组件事件父组件处理事件 Vue 实…

网格布局 HTML CSS grid layout demo

文章目录 页面效果代码 (HTML CSS)参考 页面效果 代码 (HTML CSS) <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"…

Golang | Leetcode Golang题解之第275题H指数II

题目&#xff1a; 题解&#xff1a; func hIndex(citations []int) int {n : len(citations)return n - sort.Search(n, func(x int) bool { return citations[x] > n-x }) }