UDP客户端服务器通信

news2024/11/29 5:28:21

        在这篇博客中,我们将探索 UDP(用户数据报协议) 通信,简要地说,UDP 是一种无连接、快速但不可靠的通信协议,适用于需要快速数据传输但对丢包容忍的场景,比如视频流和在线游戏。就像《我是如此相信》的图片所表达的那样,UDP 没有过多的保障机制,它直接、简单、快捷,就像信念一样,直接而坚定。 

1、网络协议中的下三层主要解决的是,数据安全可靠得发送到远端机器

2、用户使用应用层软件,完成数据的发送和接收,先把软件启动起来,那就是进程,因此网络通信的本质就是进程间的通信。

一、认识端口号

端口号无论是客户端还是服务器,都能唯一的标识该主机上的一个特定应用层的进程,ip地址唯一的标识一个主机,端口号只能用来标识该主机上的唯一进程。IP+Port能够标识全网唯一的进程。

二、套接字

客户端和服务器通信的时候就好像插头和插座的关系

端口号VS进程pid,但是pid已经可以唯一标识一个进程,为什么还要端口号?

1、不是所有的进程都要网络通信

2、pid属于操作系统中的进程管理,但是要是系统出问题,网络部分也要改,牵一发而动全身,因此要解耦。

我们的客户端怎么知道服务器的端口号?每个服务器的端口必须是众所周知的,被客户端知道。

一个进程可以绑定多个端口号,一个端口号不可以被多个进程绑定。经过哈希找的进程就不一样了

2、  套接字的种类:域间(本地通信)、原始(编写网络工具,抓包)和网络套接字

三、UDP和TCP

TCP(Transmission Control Protocol,传输控制协议)网络必须连通

  • 面向连接:在数据传输之前,发送端和接收端需要通过三次握手建立连接。这一过程确保了双方都准备好进行数据传输,并且知道对方的存在和状态,为可靠的数据传输奠定基础。例如,当你使用浏览器访问一个网站时,浏览器与网站服务器之间会先建立 TCP 连接,然后才开始传输网页数据.
  • 可靠性高:TCP 协议通过多种机制来确保数据的准确无误传输。它使用确认机制,接收方在收到数据后会向发送方发送确认消息,发送方只有在收到确认后才会继续发送下一部分数据,否则会重传未确认的数据。同时,TCP 还具备重传机制,对于在传输过程中丢失或损坏的数据,发送方会自动重新发送,直到数据被正确接收。此外,流量控制和拥塞控制也是 TCP 保证可靠性的重要手段,它可以根据网络状况和接收方的处理能力,动态调整数据传输的速率,避免网络拥塞导致数据丢失.
  • 数据有序:TCP 会对数据进行编号和排序,确保数据按照发送的顺序到达接收端。即使数据在网络中经过不同的路径传输,到达接收端时也会被正确地重组,保证应用程序能够接收到完整、有序的数据。例如,在文件传输过程中,文件的各个部分会按照顺序依次传输并在接收端正确拼接。
  • 适用于多种应用:由于其可靠性高,TCP 适用于对数据准确性要求严格的应用场景,如文件传输(FTP)、电子邮件(SMTP、POP3 等)、远程登录(Telnet)、网页浏览(HTTP、HTTPS)等,这些应用需要确保数据的完整性和准确性,不容许数据丢失或出错。

UDP(User Datagram Protocol,用户数据报协议)

  • 无连接:UDP 在发送数据之前不需要与接收方建立连接,发送方可以随时向目标地址发送数据报。这使得 UDP 的传输过程更加简单、快速,减少了连接建立和拆除的开销1.
  • 不可靠传输:UDP 不提供数据传输的可靠性保证,它不会对数据进行确认、重传或排序等操作。数据报在网络中可能会出现丢失、重复或乱序的情况,并且 UDP 本身不会对这些问题进行处理,而是由应用层来负责处理数据的可靠性问题。不过,在一些对实时性要求较高但对数据准确性要求相对较低的应用中,这种不可靠性是可以接受的,例如在线游戏中的玩家位置信息更新、视频会议中的语音和视频数据传输等,偶尔丢失一两个数据包不会对整体效果产生太大影响1.
  • 低延迟:由于不需要建立连接和进行复杂的可靠性控制,UDP 的传输延迟相对较低,能够快速地将数据发送到目标地址。这对于实时性要求高的应用非常有利,如音频流、视频流等实时多媒体应用,可以在保证一定流畅度的前提下,尽可能减少延迟1.
  • 高效性:UDP 协议的头部开销较小,只有 8 个字节,相比 TCP 的 20 个字节头部,UDP 可以在相同的网络带宽下传输更多的数据,提高了数据传输的效率1.
  • 适用于特定应用:UDP 常用于一些对实时性和效率要求较高,但对数据可靠性要求相对较低的应用场景,如网络视频会议、在线游戏、音频流播放、实时监控等。例如,在网络视频会议中,即使偶尔丢失一些视频帧,也不会对会议的进行产生严重影响,而较低的延迟可以保证参会者之间的交互更加流畅。

 四、网络字节序 

1、判断是小端还是大端 小权重放到小地址就是小端(小小小)

大端:数据的高位字节存于低地址,低位字节存于高地址。 

程序:判断机器是大端还是小端?

#include <stdio.h>
int main() {
    int num = 0x12345678;
    char *ptr = (char *)&num;
    if (*ptr == 0x78) {
        printf("Little - Endian\n");
    } else if (*ptr == 0x12) {
        printf("Big - Endian\n");
    } else {
        printf("Error\n");
    }
    return 0;
}

2、网络规定都是大端 ,低地址高字节

五、UDP客户端服务器通信 

步骤:1、创建UDP套接字 下面为创建套接字的接口 socket         

第一个参数表示的是使用的协议家族,域到底是什么,第二个表示定义的套接字类型,流,还是数据报,第三个参数表示协议参数,填0就行

返回值 On success, a file descriptor for the new socket is returned.  On error, -1 is returned, and errno is set appropriately. 

           2、绑定 bind

插入:如何快速地将整数ip转为字符串? 比如12345667转为0.0.36.123 ,我们经常用的就是点分十进制,但是我们要知道如何转换。有接口inet_addr

【0,255】.【0,255】.【0,255】.【0,255】

struct ip
{
    uint8_t part1;  //占一个字节
    uint8_t part2;
    uint8_t part3;
    uint8_t part4;
}
int src_ip=12345678;
struct ip *p=(struct ip*)src_ip; //由于 src_ip 是一个 32 位的整数,而 struct ip 是一个包含四个 uint8_t 类型字段的结构体,直接把 src_ip 赋值给 struct ip 类型的变量是不可能的(类型不兼容)。但是,通过指针,我们可以将 src_ip 的内存解释为 struct ip 类型,并按字节访问它。
to_string(p->part1)+"."+to_string(p->part2)+"."+to_string(p->part3)+"."+to_string(p->part4);

就把四字节转换为字符串风格的ip
结果是0.0.3.182

字符串转整数 ”192.168.50.100“  ”192“ ”168“”50“”100“

//192.168.50.100 转换为 123455556
uint32_t Ip; 4个字节
struct ip* x=(struct ip*)&Ip;
x->part1=stoi("192");
x->part2=stoi("168");
x->part3=stoi("50");
x->part4=stoi("100");

查看服务器启动的命令:netstat -nau

 一个关于ip

云服务器禁止直接bind公网ip,一个机器可能有多个ip,bind(IP:0),IP为0叫做任意地址绑定凡是发给我这台主机的数据,我们都要根据端口号向上交付。 

 一个关于port

【0,1023】属于系统内定的端口号,一般都要有固定的应用层协议使用 http:80 https:443 mysql:3306 我们绑定1024以上的

结论:IP地址绑定0,端口号绑定1024以上的

六、程序实现

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 logmessage(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\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    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);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }
Main.cc
#include "UdpServer.hpp"
#include <memory>
#include "Log.hpp"
Log log;

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage:"<<proc<<"proc[1024+]"<<std::endl;
}
//./udpserver port
//"192.168.1.123"点分十进制字符串风格的ip地址,一共占了13个字节
int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t port=std::stoi(argv[1]);  //字符串转整数
    std::unique_ptr<UdpServer> svr(new UdpServer());
    svr->Init();
    svr->Run();
    return 0;
}

Makefile
.PHONY:all
all:Udpserver Udpclient
Udpserver:Main.cc
	g++ -o $@ $^ -std=c++11
Udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++11
.PHONY:
clean: 
	rm -f Udpserver Udpclient
Udpclient
#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<sys/types.h>
#include<string.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"<<std::endl;
}
// ./udpclient serverip serverport 启动客户端的时候,必须要是ip和端口号是是什么
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);
    //client要绑定,要有自己的IP和端口,只不过不需要用户显示的bind,一般由os随机选择!肯定是客户端先向服务器发起请求,端口号能唯一标识客户端就行,随机分配,但是服务器的端口号必须要准确知道,不然客户端绑定哪一个
    //系统什么时候给我Bind,首次放松数据的时候就绑定,sendto
    string message;
    char buffer[1024];//接收时候的buffer
    while(1)
    {
        cout<<"Please Enter@";
        std::getline(cin,message);
        cout<<message<<endl;
        //1.数据 2.给谁发 &server 发送给server
        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;
};
Udpserver
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
#include "Log.hpp"
extern Log log;
enum{
    SOCKET_ERR=1,
    BIND_ERR=2
};
uint16_t defaultport=8080;
std::string defaultip="0.0.0.0";
const int size=1024;
class UdpServer
{
public:
    UdpServer(const uint16_t &port=defaultport,const std::string &ip=defaultip):port_(port),sockfd_(0),ip_(ip),isrunning_(false)//一个服务器要启动起来,端口号要有,Ip地址也要有
    {

    }
    void Init()
    {
        sockfd_=socket(AF_INET,SOCK_DGRAM,0);//创建套接字
        if(sockfd_<0)
        {
            log(Fatal,"socket create error,sockfd:%d",sockfd_);
            exit(SOCKET_ERR);
        }
        printf("socket create success,sockfd:%d\n",sockfd_);
        log(Info,"socket create success,sockfd:%d",sockfd_);
        struct 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());  //1.string->uint32_t   将string转char* string.c_str()
        local.sin_addr.s_addr=INADDR_ANY;//0,任意绑定,因为是全0,主机转网络不需要做
        bind(sockfd_,(const struct sockaddr *)&local,sizeof(local));
    }
    //服务器应该周而复始的运行
    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,(sockaddr*)&client,&len);
           if(n<0)
           {
                log(Warning,"recvform error");
                continue;
           }
           inbuffer[n]=0;
           //对字符串进行加工
           std::string info=inbuffer;
           std::string echo_string="server echo#"+info;
           //再把数据发送回给对方 string的长度  string.size
           //你本地的套接字;发送的字符串和长度;默认为0;输入性参数,我知道对方是谁,因为当时对方给我发过消息
           sendto(sockfd_,echo_string.c_str(),echo_string.size(),0,(const sockaddr*)&client,len);

        }
        
    }
    ~UdpServer()
    {
        if(sockfd_>0)  close(sockfd_);
    }
private:
    int sockfd_;//网络文件描述符
    uint16_t port_; //端口号是两个字节,16个比特位,服务器要有自己的端口号
    std::string ip_;//任意地址绑定,填为0
    bool isrunning_;
};

改进版:

Main.cc
#include "UdpServer.hpp"
#include <memory>
#include <cstdio>
#include "Log.hpp"
Log log;

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage:"<<proc<<"proc[1024+]"<<std::endl;
}
std::string Handler(const std::string &str)
{
    std::string res="Server get a message:";
    res+=str;
    return res;
    //服务器收到消息,回调式的调用你传进来的方法,把数进行加工,加工处理完后返回,最后把这个结果发出去
}
std::string ExecuteCommand(const std::string &cmd)
{
   // SafeCheck(cmd);
    FILE* fp=popen(cmd.c_str(),"r");
    if(nullptr==fp)
    {
        perror("popen");
        return "error";
    }
    std::string result;
    //从fp中把命令执行结果都拿出来
    char buffer[4096];
    while(true)
    {
        char * res=fgets(buffer,4096,fp);//读到一行
        if(nullptr==res) break;
        result+=buffer;
    }
    return result;
    pclose(fp);
}
//./udpserver port
//"192.168.1.123"点分十进制字符串风格的ip地址,一共占了13个字节
int main(int argc,char *argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t port=std::stoi(argv[1]);  //字符串转整数
    std::unique_ptr<UdpServer> svr(new UdpServer());
    svr->Init();
    svr->Run(ExecuteCommand);
    return 0;
}

Udpserver.hpp
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<string.h>
#include<string>
#include<netinet/in.h>
#include<arpa/inet.h>
#include <functional>
#include "Log.hpp"
using func_t=std::function<std::string(const std::string&)>; //返回值为string,参数为const std::string&的函数传递给Udpserver
//等价于typedef std::function<std::string(const std::string&)> func_t;
extern Log log;
enum{
    SOCKET_ERR=1,
    BIND_ERR=2
};
uint16_t defaultport=8080;
std::string defaultip="0.0.0.0";
const int size=1024;
class UdpServer
{
public:
    UdpServer(const uint16_t &port=defaultport,const std::string &ip=defaultip):port_(port),sockfd_(0),ip_(ip),isrunning_(false)//一个服务器要启动起来,端口号要有,Ip地址也要有
    {

    }
    void Init()
    {
        sockfd_=socket(AF_INET,SOCK_DGRAM,0);//创建套接字
        if(sockfd_<0)
        {
            log(Fatal,"socket create error,sockfd:%d",sockfd_);
            exit(SOCKET_ERR);
        }
        printf("socket create success,sockfd:%d\n",sockfd_);
        log(Info,"socket create success,sockfd:%d",sockfd_);
        struct 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());  //1.string->uint32_t   将string转char* string.c_str()
        local.sin_addr.s_addr=INADDR_ANY;//0,任意绑定,因为是全0,主机转网络不需要做
        bind(sockfd_,(const struct sockaddr *)&local,sizeof(local));
    }
    //服务器应该周而复始的运行
    //这样的好处是实现代码的分层
    void Run(func_t func)//运行的时候需要用户传进来一个怎么处理数据的方法
    {
        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,(sockaddr*)&client,&len);
           if(n<0)
           {
                log(Warning,"recvform error");
                continue;
           }
           inbuffer[n]=0;
           std::string info=inbuffer;
           std::string echo_string =func(info);
           sendto(sockfd_,echo_string.c_str(),echo_string.size(),0,(const sockaddr*)&client,len);
        }
        
    }
    ~UdpServer()
    {
        if(sockfd_>0)  close(sockfd_);
    }
private:
    int sockfd_;//网络文件描述符
    uint16_t port_; //端口号是两个字节,16个比特位,服务器要有自己的端口号
    std::string ip_;//任意地址绑定,填为0
    bool isrunning_;
};

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

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

相关文章

Flutter:封装发送验证码组件,注册页使用获取验证码并传递控制器和验证码类型

验证码&#xff1a;view import package:flutter/material.dart; import package:get/get.dart; import index.dart;class SendcodePage extends GetView<SendcodeController> {// 接收注册页面&#xff0c;传进来的手机号控制器&#xff0c;和发送验证码的类型final Tex…

【再谈设计模式】装配器模式 ~复杂结构构建的巧匠

一、引言 在软件开发过程中&#xff0c;创建复杂对象往往是一项具有挑战性的任务。传统的直接实例化对象的方式在面对复杂对象构建时&#xff0c;可能会导致代码的可维护性、可读性和扩展性变差。这时候&#xff0c;我们就需要一些设计模式来优雅地解决这些问题&#xff0c;装配…

牛客周赛 Round 69 C-E

C——仰望水面的歪 一、题目描述&#xff1a; 一看这个题目是不是觉得是物理问题&#xff0c;我也觉得是这样的&#xff0c;全反射我都快忘记了&#xff0c;结果发现他居然还能这样看&#xff0c;请看图片&#xff1a; 第一种情况&#xff1a;当目标点在小歪所在平面的上面得…

【C++】C++新增特性解析:Lambda表达式、包装器与绑定的应用

V可变参数模板与emplace系列 C语法相关知识点可以通过点击以下链接进行学习一起加油&#xff01;命名空间缺省参数与函数重载C相关特性类和对象-上篇类和对象-中篇类和对象-下篇日期类C/C内存管理模板初阶String使用String模拟实现Vector使用及其模拟实现List使用及其模拟实现…

狂野飙车8+(Asphalt 8+) for Mac 赛车竞速游戏 安装教程

Mac分享吧 文章目录 狂野飙车8(Asphalt 8) for Mac 赛车竞速游戏软件 效果图展示一、狂野飙车8(Asphalt 8) 赛车竞速游戏 Mac电脑版——v2.1.11️⃣&#xff1a;下载软件2️⃣&#xff1a;安装软件2.1 左侧安装包拖入右侧文件夹中&#xff0c;等待安装完成&#xff0c;运行软件…

共享售卖机语音芯片方案选型:WTN6020引领智能化交互新风尚

在共享经济蓬勃发展的今天&#xff0c;共享售卖机作为便捷购物的新形式&#xff0c;正逐步渗透到人们生活的各个角落。为了提升用户体验&#xff0c;增强设备的智能化和互动性&#xff0c;增加共享售卖机的语音功能就显得尤为重要。 共享售卖机语音方案选型&#xff1a; WTN602…

.net core MVC入门(一)

文章目录 项目地址一、环境配置1.1 安装EF core需要包1.2 配置数据库连接二、使用EF创建表2.1 整体流程梳理2.1 建表详细流程2.1.1 创建一个Category的Model2.1.2 创建Category的EF实体三、添加Category页面的视图3.1整体流程梳理3.2 添加Controller3.3 添加View 视图四、使用E…

vue修改el-table-column背景色和上下margin外边距调整行间距方法教程

在vue中需要修改el-table表格中每一行el-table-column背景色&#xff0c;还有table表格中数据太多&#xff0c;看起来臃肿&#xff0c;需要margin调整上下边距&#xff0c;直接使用margin不生效&#xff0c;是因为display使用的是table属性&#xff0c;所以我们要利用table特性…

Spring框架整合单元测试

目录 一、配置文件方式 1.导入依赖 2.编写类和方法 3.配置文件applicationContext-test.xml 4.测试类 5.运行结果 二、全注解方式 1.编写类和方法 2.配置类 3.测试类 4.运行结果 每次进行单元测试的时候&#xff0c;都需要编写创建工厂&#xff0c;加载配置文件等相关…

微信小程序按字母顺序渲染城市 功能实现详细讲解

在微信小程序功能搭建中&#xff0c;按字母渲染城市会用到多个ES6的方法&#xff0c;如reduce&#xff0c;map&#xff0c;Object.entries()&#xff0c;Object.keys() &#xff0c;需要组合熟练掌握&#xff0c;才能优雅的处理数据完成渲染。 目录 一、数据分析 二、数据处理 …

可视化建模与UML《状态图实验报告》

其实我并不是一个实验报告博主。 大家收拾收拾准备期末复习了嗷&#xff0c;差不多了&#xff0c;不想太赶可以开始准备了。 一、实验目的&#xff1a; 1、熟悉状态图的基本功能和使用方法。 2、掌握使用建模工具软件绘制状态图的方法 二、实验环境&#xff1a; window7 | 10…

单片机学习笔记 11. 外部中断

更多单片机学习笔记&#xff1a;单片机学习笔记 1. 点亮一个LED灯单片机学习笔记 2. LED灯闪烁单片机学习笔记 3. LED灯流水灯单片机学习笔记 4. 蜂鸣器滴~滴~滴~单片机学习笔记 5. 数码管静态显示单片机学习笔记 6. 数码管动态显示单片机学习笔记 7. 独立键盘单片机学习笔记 8…

Ubuntu下的Graphviz的基础使用方法

一、Graphviz介绍 graphviz是贝尔实验室开发的一个开源的工具包&#xff0c;它使用一个特定的DSL(领域特定语言):dot作为脚本语言&#xff0c;然后使用布局引擎来解析此脚本&#xff0c;并完成自动布局 1、什么是Graphviz 官网地址&#xff0c;https://www.graphviz.org/ Gr…

IDEA全局设置-解决maven加载过慢的问题

一、IDEA全局设置 注意&#xff1a;如果不是全局设置&#xff0c;仅仅针对某个项目有效&#xff1b;例在利用网上教程解决maven加载过慢的问题时&#xff0c;按步骤设置却得不到解决&#xff0c;原因就是没有在全局设置。 1.如何进行全局设置 a.在项目页面&#xff0c;点击f…

#渗透测试#红蓝攻防#HW#经验分享#溯源反制

免责声明 本教程仅为合法的教学目的而准备,严禁用于任何形式的违法犯罪活动及其他商业行为,在使用本教程前,您应确保该行为符合当地的法律法规,继续阅读即表示您需自行承担所有操作的后果,如有异议,请立即停止本文章阅 目录 一、溯源反制 1、溯源反制的重要性 2、溯源…

Level DB --- SkipList

class SkipList class SkipList 是Level DB中的重要数据结构&#xff0c;存储在memtable中的数据通过SkipList来存储和检索数据&#xff0c;它有优秀的读写性能&#xff0c;且和红黑树相比&#xff0c;更适合多线程的操作。 SkipList SkipList还是一个比较简单的数据结构&a…

YOLOv11融合PIDNet中的PagFM模块及相关改进思路

YOLOv11v10v8使用教程&#xff1a; YOLOv11入门到入土使用教程 YOLOv11改进汇总贴&#xff1a;YOLOv11及自研模型更新汇总 《PIDNet: A Real-time Semantic Segmentation Network Inspired by PID Controllers》 一、 模块介绍 论文链接&#xff1a;https://arxiv.org/pdf/2…

跨平台应用开发框架(4)-----Qt(样式篇)

目录 1.QSS 1.基本语法 2.QSS设置方式 1.指定控件样式设置 2.全局样式设置 1.样式的层叠特性 2.样式的优先级 3.从文件加载样式表 4.使用Qt Designer编辑样式 3.选择器 1.类型选择器 2.id选择器 3.并集选择器 4.子控件选择器 5.伪类选择器 4.样式属性 1.盒模型 …

uniapp定义new plus.nativeObj.View实现APP端全局弹窗

为什么要用new plus.nativeObj.View在APP端实现弹窗&#xff1f;因为uni.showModal在APP端太难看了。 AppPopupView弹窗函数参数定义 参数一:弹窗信息(所有属性可不填&#xff0c;会有默认值) 1.title:"", //标题 2.content:"", //内容 3.confirmBoxCo…

使用phpStudy小皮面板模拟后端服务器,搭建H5网站运行生产环境

一.下载安装小皮 小皮面板官网下载网址&#xff1a;小皮面板(phpstudy) - 让天下没有难配的服务器环境&#xff01; 安装说明&#xff08;特别注意&#xff09; 1. 安装路径不能包含“中文”或者“空格”&#xff0c;否则会报错&#xff08;例如错误提示&#xff1a;Cant cha…