网络编程套接字----UDP协议

news2024/9/28 11:20:58

文章目录

  • 前言
  • 一、理解源IP地址和目的IP地址
  • 二、认识端口号
    • 理解"端口号"和"进程ID"
    • 理解源端口号和目的端口号
  • 三、认识TCP协议
  • 四、认识UDP协议
  • 五、网络字节序
  • 六、socket编程接口
    • socket常见API
    • sockaddr结构
    • sockaddr结构
    • sockaddr_in 结构
    • in_addr结构
  • 七、地址转换函数
    • 关于inet_ntoa
  • 八、简单的UDP网络程序
    • 封装UDPSocket
    • Windows下的客户端实现
  • 总结


前言

  1. 认识IP地址,端口号,网络字节序等网络编程中的基本概念;
  2. 学习socket api的基本用法;
  3. 能够实现一个简单的UDP客户端/服务器;
  4. 能够实现一个简单的TCP客户端/服务器(单连接版本,多进程版本,多线程版本);
  5. 理解TCP服务器建立连接,发送数据,断开连接的流程.

正文开始!

一、理解源IP地址和目的IP地址

  • 源IP地址:对应的就是标定通信主机的本机主机.
  • 目的IP地址:对应的就是标定通信主机的目的主机.

二、认识端口号

在这里插入图片描述

端口号(port)是传输层协议的内容:

  • 端口号是一个2字节16位的整数;
  • 端口号用来表示一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
  • IP地址+端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用;

理解"端口号"和"进程ID"

之前在进程的学习中可以知道pid表示唯一一个进程;此处端口号也是表示一个进程,那么这两者有什么关系呢?

一个进程可以绑定多个端口号;但是一个端口号不能被多个进程绑定.

理解源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号.就是在描述"数据是谁发的,要发给谁";
在这里插入图片描述

三、认识TCP协议

在这里我们先对TCP(Transmission Control Protocol 传输控制协议)做一个简单的认识;

  • 传输层协议
  • 有连接
  • 可靠传输
  • 面向字节流

四、认识UDP协议

在这我们也是对UDP(User Dategram Protocol 用户数据报协议)做一个简单的认识;

  • 传输层协议
  • 无连接
  • 不可靠传输
  • 面向数据报

五、网络字节序

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

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
  • 接收主机把从网络上接到的字节一次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
  • 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
  • TCP/IP协议规定:W昂罗数据流应采用大端字节序,即低地址高字节.
  • 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据.
  • 如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可.

在这里插入图片描述

为了使网络程序具有可移植性,使用同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下的库函数做网络字节序和主机字节序的转换.

在这里插入图片描述

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

六、socket编程接口

socket常见API

//创建 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);

sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6.
然后各种不同网络协议的地址格式并不相同.
在这里插入图片描述

  • IPv和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以及各种类型的sockaddr结构体指针作为参数;

sockaddr结构

在这里插入图片描述

sockaddr_in 结构

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

in_addr结构

在这里插入图片描述
in_addr用来表示一个IPv4的IP地址.其实就是一个32位整数.

七、地址转换函数

基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr,sin_addr表示32位的IP地址.但是我们通常使用点分十进制的字符串表示IP地址,以下的函数可以在字符串表示和in_addr表示之间转换:

在这里插入图片描述

关于inet_ntoa

inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果,那么是否需要调用者手动释放呢?
在这里我们查看man手册
在这里插入图片描述

man手册上面说,inet_ntoa函数是把这个结果放到了静态存储区.这个时候不需要我们手动释放.

那么问题来了,如果我们多次调用这个函数,会有什么样的效果呢?

参考如下代码

#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr=0;
    addr2.sin_addr.s_addr=0xffffffff;
    char* ptr1 = inet_ntoa(addr1.sin_addr);
    char* ptr2 = inet_ntoa(addr2.sin_addr);
    printf("ptr1: %s,ptr2: %s\n",ptr1,ptr2);
    return 0;
}

在这里插入图片描述
因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果.

思考:如果有多个线程调用 inet_ntoa,是否会出现异常情况呢?—>一定会的!
在APUE这本书中,明确提出 inet_ntoa 不是线程安全的函数;
在多线程的环境下,推荐使用 inet_ntop,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题;

八、简单的UDP网络程序

实现一个大小写转化的功能
在这里插入图片描述

封装UDPSocket

makefile

.PHONY:all
all:udpClient udpServer
udpClient:udpClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
udpServer:udpServer.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf udpClient udpServer

Log.hpp

#pragma once
#include<cstdio>
#include<ctime>
#include<cstdarg>
#include<cassert>
#include<cstdlib>
#include<cstring>
#include<cerrno>
#define DEBUG     0
#define NOTICE    1
#define WARINING  2
#define FATAL     3

const char* log_level[]={"DEBUG","NOTICE","WARINING","FATAL"};

//logMessage(DEBUG,"%d",10);
void logMessage(int level,const char* format,...)
{
    assert(level>=DEBUG);
    assert(level<=FATAL);
    char logInfo[1024];
    char* name=getenv("USER");
    va_list ap; //ap--->char*
    va_start(ap,format);

    vsnprintf(logInfo,sizeof(logInfo)-1,format,ap);

    va_end(ap); //ap=NULL

    FILE* out=(level==FATAL)?stderr:stdout;
    fprintf(out,"%s | %u | %s | %s\n",\
    log_level[level],(unsigned int)time(nullptr),\
    name==nullptr?"unknow":name,logInfo);
}

// char* s=format;
// while(s)
// {
//     case: '%'
//     if(*(s+1)=='d')int x=va_arg(ap,int);
//     break;
// }

udpClient.cc

#include <iostream>
#include <string>
#include <cstdlib>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
using namespace std;

struct sockaddr_in server;

static void Usage(string proc)
{
    printf("Usage\n\t%s server_ip server_port\n", proc.c_str());
}
void *recverAndPrint(void *args)
{
    while (true)
    {
        int sockfd = *(int *)args;
        char buffers[1024];
        memset(buffers,0,sizeof buffers);
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sockfd, buffers, sizeof(buffers) - 1, 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffers[s] = 0;
            cout << "server echo# " << buffers << endl;
        }
    }
}
// ./udpClient server_ip server_port
//如果一个客户端要连接server必须知道server对应的ip和port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 1.根据命令行设置要访问的服务器IP
    string server_ip = argv[1];
    uint16_t server_port = stoi(argv[2]);

    // 2.创建客户端
    // 2.1创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);

    // 2.2 client 需不需要bind???--->需要bind,但是不需要用户自己bind,而是OS自动给你bind
    //  所谓的"不需要",指的是::不需要用户自己bind端口信息!因为OS会自动给你绑定,你也最好这么做!
    //如果我非要自己bind呢?  可以!  严重不推荐!
    //  所有的客户端软件 <->服务器 通信的时候,必须得有 client[ip,port] <->server[ip,port]
    //  为什么呢? client很多,不能给客户端bind指定的port,port可能被别的client使用了,你的client就无法使用了
    //  那么server凭什么要bind呢?server提供的服务,必须要被所有人知道!server不能随便改变!
    // 2.2填写服务器对应的信息
    bzero(&server, sizeof server);
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    pthread_t t;
    pthread_create(&t, nullptr, recverAndPrint, (void *)&sockfd);

    // 3.通讯过程
    string buffer;
    while (true)
    {
        cerr << "Please Enter# ";
        getline(cin, buffer);
        //发送消息给server
        sendto(sockfd, buffer.c_str(),
               buffer.size(), 0, (const struct sockaddr *)&server, len);
        //首次调用sendto函数的时候,我们client会自动bind自己的ip和port
    }
    close(sockfd);
    return 0;
}

udpServer.cc

#include <iostream>
#include<unordered_map>
#include <sys/types.h>
#include <strings.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

using namespace std;

//我们想写一个简单的udpServer
//云服务器有一些特殊情况
// 1.禁止你bind云服务器上的任何确定IP,只能使用INADDR_ANY,如果是虚拟机的话就可以!
class UdpServer
{
public:
    UdpServer(uint16_t port, string ip = "")
        : _sockfd(-1), _port(port), _ip(ip)
    {
    }
    ~UdpServer()
    {
    }
    void init()
    {
        // 1.创建socket套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0); //就是打开了一个文件
        if (_sockfd < 0)
        {
            logMessage(FATAL, "socket:%s:%d", strerror(errno), _sockfd);
            exit(1);
        }
        logMessage(DEBUG, "socket create success: %d", _sockfd);
        // 2.绑定网络信息,知名ip+port
        // 2.1先填充基本信息到 struct sockaddr_in
        struct sockaddr_in local;    // local在用户栈上开辟的空间--->临时变量--->写入内核中
        bzero(&local, sizeof local); // memset
        local.sin_family = AF_INET;  // 填充协议家族,域
        //填充服务器对应得端口号信息,一定是会发给对方的,_port一定回到网络中的
        local.sin_port = htons(_port);
        //服务器都必须具有IP地址,"81.70.251.220",字符串风格的点分十进制-->四字节IP-->uint32_t ip
        // INADDR_ANY(0):程序员不关心会bind到哪一个ip,任意地址bind,强烈推荐的做法,所有服务器一般的做法
        // inet_addr:指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行h->n
        local.sin_addr.s_addr = _ip.empty() ? htonl(INADDR_ANY) : inet_addr(_ip.c_str());
        // 2.2
        if (bind(_sockfd, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind:%s:%d", strerror(errno), _sockfd);
            exit(2);
        }
        logMessage(DEBUG, "socket bind success: %d", _sockfd);
        // done
    }
    void start()
    {
        //服务器设计的时候,都是死循环
        char inbuffer[1024];  //将来读取到的数据,都放在这里
        char outbuffer[1024]; //将来发送到的数据,都放在这里
        while (true)
        {
            struct sockaddr_in peer;      //输出型参数
            socklen_t len = sizeof(peer); //输入输出型参数
            // UDP是无连接的
            //对方给你发了消息,你想不想给对方回消息?要的!后面两个参数是输出型参数
            ssize_t s = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0,
                                 (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                inbuffer[s] = '\0'; //当做字符串
            }
            else if (s == -1)
            {
                logMessage(WARINING, "recvfrom: %s:%d", strerror(errno), _sockfd);
                continue;
            }
            //读取成功,除了读取到对方的数据,你还要读取到对方的网络地址[ip,port]
            string peerIp=inet_ntoa(peer.sin_addr);//拿到了对方的ip
            uint32_t peerPort=ntohs(peer.sin_port);//拿到了对方的port
            
            checkOnlineUser(peerIp,peerPort,peer);//如果存在,什么都不做,如果不存在就添加
            
            
            //打印出客户端给服务器发送的消息
            logMessage(NOTICE, "[%s|%d]# %s",peerIp.c_str(),peerPort,inbuffer);
            for(int i=0;i<strlen(inbuffer);i++)
            {
                if(isalpha(inbuffer[i])&&islower(inbuffer[i]))
                    outbuffer[i]=toupper(inbuffer[i]);
                else
                    outbuffer[i]=inbuffer[i];
            }
            messageRoutine(peerIp,peerPort,inbuffer);//消息路由
            
            //线程池!


            //sendto(_sockfd,outbuffer,strlen(outbuffer),0,(const struct sockaddr*)&peer,sizeof(peer));
            
            // logMessage(NOTICE,"server provide service succsee...");
            // sleep(1);
        }
    }
    void checkOnlineUser(string& ip,uint32_t port,struct sockaddr_in& peer)
    {
        string key=ip;
        key+=":";
        key+=to_string(port);
        auto iter =_users.find(key);
        if(iter==_users.end())
        {
            _users.insert({key,peer});
        }
        else
        {
            //do nothing
        }
    }
    void messageRoutine(string ip,uint32_t port,string info)
    {
        string message="[";
        message+=ip;
        message+=":";
        message+=to_string(port);
        message+="]";
        message+=info;
        for(auto& e:_users)
        {
            sendto(_sockfd,message.c_str(),message.size(),0,(const struct sockaddr*)&(e.second),sizeof(e.second));
        }
    }
private:
    //服务器socket fd信息
    int _sockfd;
    // 服务器必须得有端口号信息
    uint16_t _port;
    //服务器必须要有ip地址
    string _ip;
    //onlineuser
    std::unordered_map<std::string,struct sockaddr_in> _users;
};
// struct ip
// {
//     uint32_t part1:8;
//     uint32_t part2:8;
//     uint32_t part3:8;
//     uint32_t part4:8;
// };

static void Usage(const string proc)
{
    printf("Usage:\n\t%s port [ip]\n", proc.c_str());
}

// ./udpServer port [ip]
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    uint16_t port = stoi(argv[1]);
    string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    UdpServer svr(port, ip);
    svr.init();
    svr.start();
    return 0;
}

在这里插入图片描述

Windows下的客户端实现

#pragma warning(disable:4996)
#pragma comment(lib,"Ws2_32.lib")
#include<iostream>
#include<cstdio>
#include<string>
#include<cassert>
#include<cstdlib>
#include<WinSock2.h>
using namespace std;
int main()
{
	WSADATA data;
	WSAStartup(MAKEWORD(2,2),&data);

    string server_ip = "81.70.251.220";
    uint16_t server_port = 8081;

    // 2.创建客户端
    // 2.1创建socket
    SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);

    // 2.2 client 需不需要bind???--->需要bind,但是不需要用户自己bind,而是OS自动给你bind
    //  所谓的"不需要",指的是::不需要用户自己bind端口信息!因为OS会自动给你绑定,你也最好这么做!
    //如果我非要自己bind呢?  可以!  严重不推荐!
    //  所有的客户端软件 <->服务器 通信的时候,必须得有 client[ip,port] <->server[ip,port]
    //  为什么呢? client很多,不能给客户端bind指定的port,port可能被别的client使用了,你的client就无法使用了
    //  那么server凭什么要bind呢?server提供的服务,必须要被所有人知道!server不能随便改变!
    // 2.2填写服务器对应的信息
    struct sockaddr_in server;
    memset(&server,'\0',sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    // 3.通讯过程
    string buffer;

    while (true)
    {
        cerr << "Please Enter# ";
        getline(cin, buffer);
        //发送消息给server
        sendto(sockfd, buffer.c_str(), \
            buffer.size(), 0, (const struct sockaddr*)&server, sizeof(server));
        //首次调用sendto函数的时候,我们client会自动bind自己的ip和port
    } 
    closesocket(sockfd);
    WSACleanup();
	return 0;
}

在这里插入图片描述

中间的乱码问题可能是因为编码问题,但是其他的内容我们就可以直接的进行通讯.


总结

(本章完!)

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

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

相关文章

第三方软件测试▏有效保障软件产品质量的关键性步骤

软件测试作为软件产品生命周期中不可或缺的重要步骤&#xff0c;被许多软件企业所重视。主要是通过对软件产品进行全面的测试&#xff0c;确保软件质量以及满足用户需求。但软件测试不仅仅是个简单的检测工作&#xff0c;而是一个系统性的、有组织性的测试过程&#xff0c;包含…

Linux:安装 telnet 命令

我是 ABin-阿斌&#xff1a;写一生代码&#xff0c;创一世佳话&#xff0c;筑一览芳华。如果小伙伴们觉得不错就一键三连吧~ 声明&#xff1a;原文地址&#xff1a;https://www.pudn.com/news/6332b44a272bb74d44053074.html 其他参考文章&#xff1a;https://www.cnblogs.com…

尚医通-上传医院接口-需求准备(十七)

目录&#xff1a; &#xff08;1&#xff09;数据接口-上传医院接口-需求准备 &#xff08;1&#xff09;数据接口-上传医院接口-需求准备 在医院接口设置的时候说过&#xff0c;我们做的是预约挂号平台&#xff0c;里面有数据的显示&#xff0c;挂号等等相关业务&#xff0c;…

计算机组成原理【by王道考研计算机】

文章目录第一章1. 什么是计算机系统2. 硬件的发展3. 计算机硬件的基本组成冯诺依曼结构现代计算机结构主存储器运算器控制器工作过程实例4. 计算机系统的层次结构五层结构三种级别的语言5. 计算机的性能指标存储器的容量CPU其他常用时间单位第二章1. 进制转换2. 字符与字符串3.…

下一代,容器工具Podman

一、简介 Kubernetes 团队发布了最新的 1.20 版本时&#xff0c;有一枚重磅炸弹&#xff1a;正式宣布弃用 Docker 支持的功能。弃用 Docker 之后&#xff0c;开发者们对其替代品的讨论逐渐热烈&#xff0c;其中 Containerd 和 Podman 倍受期待。 Podman 是一个开源的容器运行…

云原生、20.3k Star......时序数据库 TDengine 的 2022 年精彩纷呈

日月其迈&#xff0c;时盛岁新 2022 的进度条已经加载至“100%” 疫情肆虐下&#xff0c;毫无疑问 这仍然是头顶风雪攀登山峰的一年 好在如今曙光已现 回望这一年&#xff0c;TDengine 也硕果满满 2022 年是 TDengine 创立的第六个年头 我们付出着&#xff0c;也在收获着…

英伟达Orin芯片平台使用TensorRT加速部署YOLO

前言 自动驾驶行业想要在开发板上部署一套算法还是需要花费很大功夫的&#xff0c;因为计算资源的限制以及实时性要求高。并不像在服务器端部署算法那样粗犷。接下来就记录一下YOLO系列部署的过程&#xff0c;后期会把开发板上的问题都记录在此。 一、部署环境 计算平台&…

11.简单的CSS按钮悬停特效

效果 源码 <!DOCTYPE html> <html> <head><title>Button Hover Effects</title><link rel="stylesheet" type="text/css" href="style.css"> </head> <body><a href="#"><…

信贷产品年终总结之风控评分模型

叮咚&#xff0c;信贷年终总结的又一个专题来了&#xff0c;作为报告总结类的系列型文章&#xff0c;近期我们番茄知识星球平台陆续发布了相关年终总结专题&#xff0c;依次为客群特征画像、贷中行为分析、贷后逾期表现等&#xff0c;以上文章可详见之前陆续发布的内容。该业务…

nacos配置部署与管理

nacos配置部署与管理配置文件配置获取顺序&#xff1a;nacos配置热部署方式一&#xff1a;RefreshScope方式二&#xff1a;ConfigurationProperties配置文件 首先新建配置文件 Data ID&#xff1a;配置文件的名称&#xff08;唯一&#xff09;命名规范&#xff1a;服务名称-运…

广州车展|继原力技术之后,长安深蓝半固态电池呼之欲出

新年将至&#xff0c;万象更新。12月30日&#xff0c;2022第二十届广州国际汽车展览会在广州中国进出口商品交易会展馆隆重举办。全新数字纯电品牌长安深蓝携旗下首款战略车型深蓝SL03重磅参展&#xff0c;并邀请深蓝SL03车主亲临车展现场&#xff0c;或进行产品讲解&#xff0…

面向对象分析与设计的底层逻辑

作者&#xff1a;高福来 阿里全球化业务平台团队 在面向对象出现之前&#xff0c;已有面向过程的分析方法&#xff0c;那为什么面向对象被提出了呢&#xff1f;究其本质&#xff0c;人们发现面向过程并非按照人正常认识事物的方式去分析软件。面向过程是一种归纳的分析方法&a…

virtio技术(3)virtqueue机制

virtio技术&#xff08;3&#xff09;virtqueue机制 virtio的关键技术是virtqueue机制&#xff0c;其提供了一套统一的用于virito前端和后端的通信机制。virtqueue的核心数据结构是vring&#xff0c;这是virtio前端驱动和后端Hypervisor虚拟设备之间传输数据的载体。 vring数…

Word处理控件Aspose.Words功能演示:使用 Java 将 RTF 转换为 PDF

Aspose.Words 是一种高级Word文档处理API&#xff0c;用于执行各种文档管理和操作任务。API支持生成&#xff0c;修改&#xff0c;转换&#xff0c;呈现和打印文档&#xff0c;而无需在跨平台应用程序中直接使用Microsoft Word。此外&#xff0c; Aspose API支持流行文件格式处…

改变podman的存储路径

使用podman容器时&#xff0c;podman会默认使用/var/lib/containers路径作为存储路径&#xff0c;可能会导致根磁盘空间占用过大&#xff0c;那如何修改podman的存储路径呢&#xff1f;本文将带你一起来探讨。 前几天公司的服务器根目录磁盘空间不足了&#xff0c;经过查找问题…

JAVA - fastjson 中 JSONObject 的顺序问题

目录 1. JSONObject 存在的默认排序问题一 1.1. 解决方案一 1.2. 解决方案二 2. JSONObject 存在的默认排序问题二 2.1. 解决方案一 2.2. 解决方案二 在使用 fastjson 中的 JSONObject 有时候会遇到数据顺序发生了变化&#xff0c;而实际需求中需要保持原有的顺序。 1…

【软件测试】测试人的巅峰?测试专家?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 经常有人谈到&#…

如何实现全网置灰?CSS3来解决

文章目录前言正文网站示例核心代码前言 当发生大事时&#xff0c;部分小伙伴会发现&#xff1a;“怎么某APP是灰的&#xff1f;不会是手机出问题了吗&#xff1f;” 然后再打开其他APP&#xff0c;发现&#xff1a;“都是灰的啊&#xff01;明白了&#xff0c;看来是有大事发生…

UKF 无迹卡尔曼滤波

目录参考&#xff1a;UKF数学原理&#xff1a;UKF的基本非线性系统描述&#xff1a;计算sigma point和权重参数UKF的基本预测步和更新步&#xff1a;UKF代码实现&#xff1a;参考&#xff1a; UKF数学原理&#xff1a; UKF的基本非线性系统描述&#xff1a; The UKF takes i…

vue element-ui 手机号校验 验证码校验 获取验证码倒数60秒无样式实现模板

上一篇其实发过了。。。 但是实在真的是太丑了 丑到自己看不下去了 加个对话框好看很多&#xff0c;再发一次 原链接为&#xff1a;https://blog.csdn.net/ZZDT099/article/details/128496693?spm1001.2014.3001.5502 <template><el-dialog title"校验手机号…