【Linux网络】:套接字之UDP

一、UDP和TCP协议

TCP (Transmission Control Protocol 传输控制协议)的特点:

  • 传输层协议
  • 连接(在正式通信前要先建立连接)
  • 可靠传输(在内部帮我们做可靠传输工作)
  • 面向字节流

UDP (User Datagram Protocol 用户数据报协议)的特点:

  • 传输层协议
  • 连接
  • 不可靠传输(可能会出现网络丢包或数据包乱序、重复等问题)
  • 面向数据报

 

        UDP和TCP协议都是隶属于传输层的协议,并且这两个协议距离用户来说是最近的。所以一般以数据通信为目的的代码都是使用的是关于传输层提供的这些接口,那在传输层提供的协议总共有UDP和TCP两种协议。

        其中TCP协议被叫做是传输控制协议,并且它的特点是有连接,可靠传输,面向字节流这些特点,这些特点会在后续进行讨论,而UDP协议是用户数据包协议,它的特点是无连接,不可靠传输,面向数据报。现在只是需要知道的是,TCP协议对于传输的内容要进行严格的追踪,必须要确保这个数据包能够完整的被对方接受了才会善罢甘休,而对于UDP来说却不是这样,它只保证自己发送了这个数据即可,至于对方有没有接受到这个信息不属于它的关心范围。

可能你会质疑UDP的传输,这是不是也意味着UDP的传输就不如TCP呢?为什么还要用UDP?

        其实这两个概念都是中性词,并没有说到底是哪个协议就好,哪个协议就坏,TCP的传输虽然很稳定,不可置疑,但是带来的问题是追踪每一个包的相关信息到底有没有送达就意味着需要消耗额外的资源来进行管理,而对比UDP来说就没有这些额外的消耗,所以并没有一个严格的定义哪个就比哪个更优,只是在特定的场景可能会略有区分。

    二、网络字节序

    首先要清楚大小端的概念:

    • 小端:低权值的数放入低地址。
    • 大端:低权值的数放入高地址。

    我们已经知道, 内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分。

    如何定义网络数据流的地址呢?(如果一个大端机用大端的方式发送数据到一个小端机,现在跨网络我们也不知道数据到底是大端和小端) 在网络诞生之前,就已经有了大小端的概念了,但是大小端到底谁好谁坏?这其实很难做出一个具体的区分,不同的技术厂商会采取不同的使用方法,但是网络诞生之后,必须解决的问题是数据到底是用小端来传输还是用大端来传输,如果不解决这个问题就无法进行适当的网络传输。

    那怎么办呢?最终网络选择的一个方法是,不管是大端机器还是小端机器,只要想要在网络上传输,必须传递的是大端数据,换句话说大端机器的数据就可以直接在网络上进行传输,但是小端机器的数据就必须要进行合适的转换才可以,所以也对应的提供了一套接口,来表示把数据进行转换:

    1. h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
    2. 例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地址转换后准备发送。
    3. 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回。
    4. 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

    三、socket套接字 

    socket编程常见的接口

    // 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
    int socket(int domain, int type, int protocol);
    // 绑定端口号 (TCP/UDP, 服务器)
    int bind(int socket, const struct sockaddr *address,
    socklen_t address_len);
    // 开始监听socket (TCP, 服务器)
    int listen(int socket, int backlog);
    // 接收请求 (TCP, 服务器)
    int accept(int socket, struct sockaddr* address,
    socklen_t* address_len);
    // 建立连接 (TCP, 客户端)
    int connect(int sockfd, const struct sockaddr *addr,
    socklen_t addrlen);
    

    未来在进行写套接字的时候,一般默认情况下是需要把本主机的ip地址和端口号这样的套接字信息,通过系统调用来和对应打开的网络套接字来进行绑定,那么其中网络套接字也有很多的类型,例如:

    1. 域间套接字
    2. 原始套接字
    3. 网络套接字

    域间套接字:它的侧重点更多是同一个机器内,这个域表示的是你的机器本身,在里面进行套接,有点类似于之前管道的概念,通过文件路径的方式标识一份公共资源,然后再以套接字的方式实现双方的通信,这个就是域间套接字。其中域间套接字表示的是本地通信

    原始套接字:看做它是一个网络工具,它一般是绕过传输层,使用底层的一些接口来进行开发工具,比如说来进行检查计算机当前是否联通,比如要进行抓包等等行为,都是借助原始套接字来进行完成的。

    网络套接字:通常是用来标识用户之间的网络通信,也是本篇的重点内容,是指使用TCP或者是UDP的协议来实现了用户间的数据通信

    在这之中有一个需要注意的点,那就是网络接口的设计者想要做成的效果是,理论上来说未来的不同套接字可能需要三套接口,但是他并不想这样设计,他想要进行高度抽象出一套共同的接口,来方便进行使用,但是现在的问题是他该如何进行保证网络接口的统一的呢?接口想要统一,第一个面临的问题就是参数必须统一,可是该如何解决这个问题呢?

    在真实情况下进行网络通信的时候,使用的结构体里面首先要包含16位的端口号,还有30位的ip地址,还有8位的填充等等,但是如果想用一个接口来实现,就意味着要想办法克服让不同的人看到参数后能转换成自己的资源,那对应的解决方案是,不管是网络通信还是本地通信,对应的前2个字节,就表明了通信的类型,如下图所示:

    可以看到 sockaddr_in 和 sockaddr_un 是两个不同的通信场景,区分它们就用 16 地址类型协议家族的标识符。但是,这两个结构体都不用,我们用 sockaddr。

    比方说我们想用网络通信,虽然参数是 const struct sockaddr *addr,但实际传递进去的却是 sockaddr_in 结构体(注意要强制类型转换)。在函数内部一视同仁,全部看成 sockaddr 类型,然后根据前两个字节判断到底是什么通信类型然后再强转回去。可以把 sockaddr 看成基类,把 sockaddr_in 和 sockaddr_un 看成派生类,构成了多态体系。

    • IPv4 和 IPv6 的地址格式定义在 netinet/in.h 中,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型,16 位端口号和 32 位 IP 地址。
    • IPv4、IPv6 地址类型分别定义为常数 AF_INET、AF_INET6。这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容。
    • socket API 可以都用 struct sockaddr * 类型表示, 在使用的时候需要强制转化成 sockaddr_in,这样的好处是程序的通用性,可以接收 IPv4,IPv6,以及 UNIX Domain Socket 各种类型的 sockaddr 结构体指针做为参数。

    每一种通信结构体的前面部分都是一样的,这也就意味着当需要进行匹配的时候,会首先匹配一下前两个字节,看前两个字节是哪种结构体的,进而就可以进行区分开了,所以最终,我们对应的网络套接字在使用的时候需要进行对应的强转,转换成所需要的具体的结构体就可以了,有点类似于void的概念,不过由于当时还没有出现void的概念,所以也就沿用至今了,在使用的时候直接看成是void*来使用就没有什么使用压力了。

    sockaddr 结构

    sockaddr_in 结构

     

    虽然  socket api  的接口是  sockaddr, 但是我们真正在基于  IPv4  编程时, 使用的数据结构是  sockaddr_in, 这个结构里主要有三部分信息: 地址类型, 端口号, IP  地址。

    in_addr 结构 

    四、UDP网络编码 

    这个文件的主要作用就是可以打印一些日志信息,用来方便编程测试代码:

    // Log.hpp
    #pragma once
    #include <iostream>
    #include <time.h>
    #include <stdarg.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    #include <stdlib.h>#define SIZE 1024
    #define Info 0
    #define Debug 1
    #define Warning 2
    #define Error 3
    #define Fatal 4
    #define Screen 1
    #define Onefile 2
    #define Classfile 3
    #define LogFile "log.txt"class Log
    {
    public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);printLog(level, logtxt);}private:int printMethod;std::string path;
    };
    
    // udpserver.hpp
    #pragma once
    #include <iostream>
    #include <string>
    #include <strings.h>
    #include <cstring>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <functional>
    #include "Log.hpp"
    using func_t = std::function<std::string(const std::string &)>;
    using namespace std;enum
    {SOCKET_ERROR = 1,BIND_ERROR
    };uint16_t defaultport = 8080;
    string defaultip = "0.0.0.0";
    const int size = 1024;
    Log lg;class UdpServer
    {
    public:UdpServer(const uint16_t &port = defaultport, const string &ip = defaultip): _sockfd(0), _port(port), _ip(ip), _isrunning(false){}void Init(){// 1. 创建UDP socket_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (_sockfd < 0){lg(Fatal, "socket create error, sockfd: %d", _sockfd);exit(SOCKET_ERROR);}lg(Info, "socket create success, sockfd: %d", _sockfd);// 2. 绑定socketstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = inet_addr(_ip.c_str());if (bind(_sockfd, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERROR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}void Run() // 对代码进行分层{_isrunning = true;char inbuffer[size];while (_isrunning){struct sockaddr_in client;socklen_t len = sizeof(client);ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&client, &len);if (n < 0){lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}inbuffer[n] = 0;string info = inbuffer;string echo_string = "sever echo#" + info;cout << echo_string << endl;sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const sockaddr *)&client, len);}}~UdpServer(){if (_sockfd > 0)close(_sockfd);}private:int _sockfd;string _ip;uint16_t _port;bool _isrunning;
    };
    
    // udpclient.cc
    #include <iostream>
    #include <cstdlib>
    #include <unistd.h>
    #include <strings.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>using namespace std;void Usage(std::string proc)
    {std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
    }// ./udpclient serverip serverport
    int main(int argc, char *argv[])
    {if (argc != 3){Usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socker error" << endl;return 1;}string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);std::cout << message << std::endl;// 1. 数据 2. 给谁发sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len);if (s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
    }
    
    // main.cc
    #include "UdpServer.hpp"
    #include <memory>
    using namespace std;void Usage(string proc)
    {cout << "\n\rUsage: " << proc << " port[1024+]\n"<< endl;
    }string ExcuteCommand(const std::string &cmd)
    {FILE *fp = popen(cmd.c_str(), "r");if (nullptr == fp){perror("popen");return "error";}std::string result;char buffer[4096];while (true){char *ok = fgets(buffer, sizeof(buffer), fp);if (ok == nullptr)break;result += buffer;}pclose(fp);return result;
    }int main(int argc, char *argv[])
    {if (argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);unique_ptr<UdpServer> svr(new UdpServer(port));svr->Init(/**/);svr->Run();return 0;
    }
    

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

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

    相关文章

    React19 useOptimistic 用法

    用法 乐观更新 发起异步请求时&#xff0c;先假设请求会成功立即更新 UI 给用户反馈若请求最终失败&#xff0c;再将 UI 恢复到之前的状态 const [optimisticState, addOptimistic] useOptimistic(state, updateFn) 参数 state&#xff1a;实际值&#xff0c;可以是 useSta…

    Deepseek-v3+cline+vscode java自动化编程

    1、Deepseek DeepSeek 充值后&#xff0c;创建apikey 2、vscode Visual Studio Code - Code Editing. Redefined 3、下载插件cline 4、配置deepeseek-v3 的密钥到cline 5、不可用 在开始的几次调用能正常使用起来&#xff0c;用了几次后&#xff0c;不能使用了&#xff0c;请求…

    数据分析案例:环境数据分析

    目录 数据分析案例&#xff1a;环境数据分析1. 项目背景2. 数据加载与预处理2.1 数据说明2.2 读取与清洗 3. 探索性数据分析&#xff08;EDA&#xff09;3.1 时序趋势3.2 日内变化3.3 气象与污染物相关性 4. 特征工程4.1 时间特征4.2 滞后与滚动统计4.3 目标变量 5. 模型构建与…

    网络原理 - 8

    目录 补充 网络层 IP 协议 基本概念&#xff1a; 协议头格式 地址管理 如何解决 IP 地址不够用呢&#xff1f;&#xff1f;&#xff1f; 1. 动态分配 IP 地址&#xff1a; 2. NAT 机制&#xff08;网络地址映射&#xff09; 3. IPv6 网段划分 一些特殊的 IP 地址 …

    向量检索新选择:FastGPT + OceanBase,快速构建RAG

    随着人工智能的快速发展&#xff0c;RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;技术日益受到关注。向量数据库作为 RAG 系统的核心基础设施&#xff0c;堪称 RAG 的“记忆中枢”&#xff0c;其性能直接关系到大模型生成内容的精准度与…

    dify对接飞书云文档,并且将图片传入飞书文档

    前面讲了如何让dify展示图片&#xff0c;但是如果想让智能体回答的带图片的内容生成个文档该怎么弄呢&#xff1f;今天来实践一下。 dify工具带的有飞书云文档&#xff0c;正好&#xff0c;咱们就利用飞书云文档。 1、首先配置飞书云文档的key跟secret 注意要开头左侧的权限&a…

    Linux系统之设置开机启动运行桌面环境

    Linux 开机运行级别介绍与 Ubuntu 桌面环境配置指南 一、Linux 开机运行级别(Runlevel) 在传统的 Linux 系统(如 SysV init 初始化系统)中,运行级别定义了系统启动时加载的服务和资源。常见的运行级别如下: 运行级别模式用途0Halt(停机模式)关闭系统1Single User Mode…

    Spring Cloud Gateway配置双向SSL认证(完整指南)

    本文将详细介绍如何为Spring Cloud Gateway配置双向SSL认证,包括证书生成、配置和使用。 目录结构 /my-gateway-project ├── /certs │ ├── ca.crt # 根证书 │ ├── ca.key # 根私钥 │ ├── gateway.crt # 网关证书 │ ├── …

    【虚幻5蓝图Editor Utility Widget:创建高效模型材质自动匹配和资产管理工具,从3DMax到Unreal和Unity引擎_系列第二篇】

    虚幻5蓝图Editor Utility Widget 一、基础框架搭建背景&#xff1a;1. 创建Editor Utility Widget2.根控件选择窗口3.界面功能定位与阶段4.查看继承树5.目标效果 二、模块化设计流程1.材质替换核心流程&#xff1a;2.完整代码如下 三、可视化界面UI布局1. 添加标题栏2. 构建滚动…

    LabVIEW实现DMM与开关模块扫描测量

    该程序基于 LabVIEW&#xff0c;用于控制数字万用表&#xff08;DMM&#xff09;与开关模块进行测量扫描。通过合理配置触发源、测量参数等&#xff0c;实现对多路信号的自动化测量与数据获取&#xff0c;在电子测试、工业测量等领域有广泛应用。 ​ 各步骤功能详解 开关模块…

    OpenAvatarChat要解决UnicodeDecodeError

    错误信息如下 ailed to import handler module client/h5_rendering_client/client_handler_lam Traceback (most recent call last):File "E:\Codes\Python\aigc\OpenAvatarChat\src\demo.py", line 82, in <module>main()File "E:\Codes\Python\aigc\O…

    数据库中的主键(Primary Key)

    数据库中的主键&#xff08;Primary Key&#xff09; 主键是数据库表中用于唯一标识每一行记录的一个或多个列的组合&#xff0c;是关系型数据库中的重要概念。 主键的核心特性 唯一性&#xff1a;主键值必须唯一&#xff0c;不能重复非空性&#xff1a;主键列不能包含NULL值…

    MySQL 9.3 正式发布!备份、用户管理与开发支持迎来革命性升级

    开源数据库领域的标杆产品MySQL迎来重大更新——MySQL 9.3正式发布&#xff01;作为企业级数据库的“扛把子”&#xff0c;此次版本更新聚焦备份效率、用户管理精细化、开发支持增强三大核心领域&#xff0c;同时在高可用性和性能优化上实现突破。以下为你逐一解读新版本的亮点…

    Rmarkdown输出为pdf的方法与问题解决

    R 是一种在数据分析与统计计算领域广泛使用的编程语言。其关键优势之一是能够生成高质量的报告和文档&#xff0c;这些报告和文档可以使用 RMarkdown 轻松定制和更新。在本文中&#xff0c;我们将探讨使用 R 从 RMarkdown 文件生成.pdf 文件 1.生成方法 新建Rmarkdown&#xf…

    毕业设计-基于机器学习入侵检测系统

    选题背景与意义 随着互联网技术的飞速发展&#xff0c;网络在人们的生活、工作各个领域都发挥着至关重要的作用。但与此同时&#xff0c;网络安全问题也日益严峻&#xff0c;各类网络攻击事件频发&#xff0c;给个人、企业乃至国家都带来了巨大的经济损失和安全威胁。入侵检测…

    React 实现爱心花园动画

    主页&#xff1a; import React, { useEffect, useRef, useState } from react; import /assets/css/Love.less; import { Garden } from /utils/GardenClasses;// 组件属性接口 interface LoveAnimationProps {startDate?: Date; // 可选的开始日期messages?: { // 可…

    从零开始了解数据采集(二十一)——电子制造行业趋势分析案例

    这次分享一个偏行业性的趋势分析案例,在项目中为企业实实在在的提高了良品率。不懂什么是趋势分析的同学,可以翻看前面的文章。 在广东某电子制造厂中,管理层发现最近几个月生产良品率有所波动,但无法明确波动原因,也无法预测未来的趋势。为了优化生产过程并稳定良品率,…

    在 Git 中,撤销(回退)merge 操作有多种方法

    在 Git 中&#xff0c;撤销&#xff08;回退&#xff09;merge 操作有多种方法&#xff0c;具体取决于是否已提交、是否已推送&#xff0c;以及是否需要保留历史记录。以下是几种常见的撤销 merge 的方法&#xff1a; 1. 未提交 merge&#xff08;未 commit&#xff09; 如果 …

    基于 Python 的实现:居民用电量数据分析与可视化

    基于 Python 的实现:居民用电量数据分析与可视化 本文将介绍如何利用 Python 技术栈(包括 pymysql、pandas、matplotlib 等库)对居民用电量数据进行分析和可视化,以帮助我们更好地理解用电行为模式。 数据准备 在MySQL数据库中创建数据,,数据库表结构如下: date:记录…

    Flow原理

    fun main() {runBlocking {launch {flow4.collect{println("---collect-4")}println("---flow4")}}val flow4 flow<Boolean>{delay(5000)emit(false) } 我们分析下整个流程 1.flow为什么之后在collect之后才会发送数据 2.collect的调用流程 我…