UDP网络编程套接

目录

本文核心

预备知识

1.端口号

认识TCP协议

认识UDP协议

网络字节序

socket编程接口

sockaddr结构

UDP套接字编程

服务端

客户端

TCP与UDP传输的区别

可靠性:

传输方式:

用途:

头部开销:

速度:

linux相关的指令操作

ip地址转化函数


本文核心

认识IP地址, 端口号, 网络字节序等网络编程中的基本概念;
学习socket api的基本用法;
能够实现一个简单的udp客户端/服务器;

预备知识

1.端口号

端口号 (port) 是传输层协议的内容 .
端口号是一个 2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用,但是一个进程可以占用多个端口。
理解 " 端口号 " " 进程 ID"
我们之前在学习系统编程的时候 , 学习了 pid 表示唯一一个进程 ; 此处我们的端口号也是唯一表示一个进程 .只不过端口号用在网络通讯中。
这其实是一种解耦操作,防止一方出问题,另一方也造成的功能崩塌。

pid是进程管理的模块,纳入到网络板块不符合低耦合

万一进程版块出错,那么就会出现牵一发而动全身的效果

理解源端口号和目的端口号
传输层协议 (TCP UDP) 的数据段中有两个端口号 , 分别叫做源端口号和目的端口号 . 就是在描述 " 数据是谁发的 , 要发给谁 ";
举个例子
玩抖音:客户端---服务器

端口号:c:客户端 s:服务器

基于ip+端口的通信方式称为socket

进程如何绑定端口号呢?

通过哈希运算,把pcb绑定到特定的哈希表中

认识TCP协议

此处我们先对 TCP(Transmission Control Protocol 传输控制协议 ) 有一个直观的认识 ; 后面我们再详细讨论 TCP的一些细节问题 .
传输层协议
有连接
可靠传输
面向字节流

认识UDP协议

此处我们也是对 UDP(User Datagram Protocol 用户数据报协议 ) 有一个直观的认识 ; 后面再详细讨论 .
传输层协议
无连接
不可靠传输
面向数据报

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
小小小:小权值位放在小地址处为小端
为使网络程序具有可移植性 , 使同样的 C 代码在大端和小端计算机上编译后都能正常运行 ,可以调用以下库函数做网络字节序和主机字节序的转换。
对于port,需要将主机序转为网络字节序,就需要使用htons()接口
这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回

socket编程接口

基于ip+端口当通信方式称为socket(套接字)

套接字(Socket)是计算机网络通信中一个抽象层,它提供了进程间通信的端点。在网络编程中,套接字可以被看作是不同计算机进程间通信的一个虚拟端点,允许数据通过计算机网络进行传输。

以下是套接字的定义:

套接字:在计算机网络中,套接字是一个软件抽象层,它代表了一个网络连接的一端。每个套接字都有唯一的标识,由一个IP地址和一个端口号组成。套接字使得应用程序可以发送或接收数据,而不需要了解底层网络协议的细节

套接字分为以下几种类型:

流套接字(Stream Sockets):提供面向连接、可靠的数据传输服务,通常使用TCP(传输控制协议)来实现。适用于需要数据完整性和顺序保证的应用,如Web浏览器和电子邮件服务器。

数据报套接字(Datagram Sockets):提供无连接的数据传输服务,通常使用UDP(用户数据报协议)来实现。适用于不需要可靠传输的应用,如视频会议或在线游戏,它们可以容忍一定的数据丢失。

原始套接字(Raw Sockets):允许直接发送和接收IP协议数据包,通常用于特殊用途,如网络诊断工具或实现新的协议。

套接字的类型:

流套接字(SOCK_STREAM):提供可靠的、面向连接的服务,通常基于TCP协议。

数据报套接字(SOCK_DGRAM):提供不可靠的、无连接的服务,通常基于UDP协议。

原始套接字(SOCK_RAW):允许直接访问网络层协议,如IP或ICMP。

套接字的地址家族:

AF_INET:用于IPv4网络协议。

AF_INET6:用于IPv6网络协议。

AF_UNIX:用于Unix域套接字,用于同一主机上的进程间通信。

求同存异

可以看到,调用接口的时候必须强转成const struct sockaddr*的结构,但是不同协议簇使用的套接字是不同的,那怎么做到统一呢?

他们前16位是一样的大小,可以确定协议簇的类型,只需要强转之后取前16字节,就可以获得不同的结构。

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, 以及后面要讲的UNIX Domain Socket. 然而 , 各种网络协议的地址格式并不相同。
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结构体指针做为参数;
sockaddr 结构
sockaddr_in 结构
虽然 socket api 的接口是 sockaddr, 但是我们真正在基于 IPv4 编程时 , 使用的数据结构是 sockaddr_in; 这个结构里主要有三部分信息 : 地址类型, 端口号, IP地址.
在这个结构中,我们核心关注的是前三个字段
in_addr 结构
in_addr 用来表示一个 IPv4 IP 地址 . 其实就是一个 32 位的整数 ;

UDP套接字编程

服务端


#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 namespace std;

using func_t = std::function<std::string(const std::string&)>;   //using xxx = 类型,是C++风格的typede
//typedef std::function<std::string(const std::string&)> func_t;

Log lg;

enum{
    SOCKET_ERR=1,
    BIND_ERR
};

uint16_t defaultport = 8080;    //端口号是一个16位无符号整数
uint32_t defaultip = INADDR_ANY;    //ip地址是一个32位无符号整数 #define	INADDR_ANY		((in_addr_t) 0x00000000)
const int sz = 1024;

class UdpServer
{
public:
    UdpServer(uint16_t port = defaultport, string ip = to_string(defaultip)) //运行的时候,只需要知道端口号和ip即可
        : _sockfd(0)
        , _ip(ip)
        , _port(port)
        , _isrunning(false)
        {
            bzero(&_local, sizeof(_local));
            _local.sin_family = AF_INET;     //网络层协议族为IPv4
            _local.sin_addr.s_addr = inet_addr(_ip.c_str());      //将ip字符串转换为网络字节序的32位整数
            _local.sin_port = htons(_port);     //将端口号转换为网络字节序的16位整数

            /*           
            local.sin_family = AF_INET;
            local.sin_port = htons(port_); //需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的
            local.sin_addr.s_addr = inet_addr(ip_.c_str()); //1. string -> uint32_t 2. uint32_t必须是网络序列的 // ??
            // local.sin_addr.s_addr = htonl(INADDR_ANY);
            /*
            单网络接口:如果你的服务器只有一个网络接口,设置 INADDR_ANY 意味着无论客户端通过哪个IP地址连接到这个服务器,服务端都会接受连接。
            多网络接口:如果你的服务器有多个网络接口,每个接口有不同的IP地址,设置 INADDR_ANY 则意味着无论客户端连接到哪个IP地址,服务端都会接受连接。
            例如,服务器可能有一个IP地址用于内部网络,另一个IP地址用于外部网络。
            多IP地址:如果服务器的一个网络接口配置了多个IP地址(比如通过虚拟接口或别名),设置 INADDR_ANY 允许服务端在这些所有IP地址上接受连接。

            也就是说,如果这个服务器存在多个ip,那么客户端与服务器通信时,可以接入本服务器的任意一个ip
            */


            //将peer暂时初始化,后续会获得peer的地址信息
            bzero(&_peer, sizeof(_peer));
            _peer.sin_family = AF_INET;
            _peer.sin_addr.s_addr = INADDR_ANY;
            _peer.sin_port = htons(0);
        }

    ~UdpServer()
    {
        if (_sockfd) close(_sockfd);
    }

public:
    void Init()
    {
        //1.创建UDP套接字
        _sockfd = socket(AF_INET,SOCK_DGRAM, 0);      //int socket(int domain, int type, int protocol);
        if (_sockfd < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", _sockfd);
            exit(SOCKET_ERR);
        }

        lg(Info, "socket create success, sockfd: %d", _sockfd);


        //绑定端口号和ip地址
        if (bind(_sockfd, (const struct sockaddr*)&_local, sizeof(_local)) < 0)
        {
            lg(Fatal, "bind error, sockfd: %d", _sockfd);
            exit(BIND_ERR);
        }

        lg(Info, "bind success, sockfd: %d, ip: %s, port: %d", _sockfd, _ip.c_str(), _port);
    }

    //对代码进行分层
    void Run(func_t func)       //这个地方需要传入一个函数指针
    {
        _isrunning = true;
        char buffer[sz] = {0};
        while (_isrunning)
        {
            socklen_t len = sizeof(_peer);

            cout << "recv not over" << endl;

            ssize_t n = recvfrom(_sockfd, buffer, sz, 0, (struct sockaddr*)&_peer, &len);
            if (n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;   //出错了,继续接收下一个数据包
            }

            cout << "recv over " <<endl;
            
            buffer[n] = 0;
            string data = buffer;
            string echo = func(data);
            sendto(_sockfd, echo.c_str(), echo.size(), 0, (const struct sockaddr*)&_peer, len);
        }
    }

private:
    int _sockfd;        //套接字描述符,一切皆文件
    string _ip;     //我们使用的ip一般是字符串样式
    uint16_t _port;
    bool _isrunning;
    struct sockaddr_in _local;    
    struct sockaddr_in _peer;
};
#include "UdpServer.hpp"
#include <memory>
#include <cstdio>

using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

string ExcuteCommand(const 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;
}

std::string Handler(const std::string &str)
{
    std::string res = "Server get a message: ";
    res += str;
    std::cout << res << std::endl;

    return res;
}

// ./udpserver port
int main(int argc, const char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = stoi(argv[1]);

    unique_ptr<UdpServer> svr = make_unique<UdpServer>(port);

    svr->Init();
    svr->Run(Handler);
    return 0;
}

需要注意的是

1.在对sockaddr_in成员进行初始化时,需要满足网络通讯的要求:1.端口号为网络字节序 2.ip地址从字符串风格编程点分式

2.recvfrom和sendto面向的是udp通信方式,传参的时候需要将sockaddr_in结构进行强转。

3.核心为:1.创建套接字 2.绑定端口号 3.接受发送

        创建套接字:

绑定端口ip时(sockaddr_in)初始化时,端口号一般ip采用通用ip。

           单网络接口:如果你的服务器只有一个网络接口,设置 INADDR_ANY 意味着无论客户端通过哪个IP地址连接到这个服务器,服务端都会接受连接。

            多网络接口:如果你的服务器有多个网络接口,每个接口有不同的IP地址,设置 INADDR_ANY 则意味着无论客户端连接到哪个IP地址,服务端都会接受连接。

            例如,服务器可能有一个IP地址用于内部网络,另一个IP地址用于外部网络。

            多IP地址:如果服务器的一个网络接口配置了多个IP地址(比如通过虚拟接口或别名),设置 INADDR_ANY 允许服务端在这些所有IP地址上接受连接。

            也就是说,如果这个服务器存在多个ip,那么客户端与服务器通信时,可以接入本服务器的任意一个ip。

在计算机网络中,存在一些端口号范围被操作系统保留用于特定的服务或进程。通常,这些端口号被称为“知名端口”(Well-known ports),它们的范围是从0到1023。

端口是一个16位的数字,可供选择。

4.用socket接口返回的套接字本质是一个文件描述符,不用的时候应该关掉sockfd。

5.popen接口

参数1:传入需要执行的命令

作用:

在内部自动fork,让父子进程建立管道,让子进程执行命令,将子进程的执行结果通过管道返回给调用方。

调用方想得到command的执行结果,可以用FILE*文件指针的方式读取

参数二:执行结果的打开方式(把这个命令当成一个文件)

客户端

#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, const char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }

    string ip = argv[1];
    uint16_t port = (uint16_t)stoi(argv[2]);

    struct sockaddr_in serveraddr;  
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(ip.c_str());
    serveraddr.sin_port = htons(port);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "create socket error" << endl;
        return -1;
    }
     // client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!
    // 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
    // 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!
    // 系统什么时候给我bind呢?首次发送数据的时候

    string msg;
    char buffer[1024];

    while (1)
    {
        cout << "Please Enter@ ";
        getline(cin ,msg);
        sendto(sockfd, msg.c_str(), msg.size(), 0, (const struct sockaddr*)&serveraddr, sizeof(serveraddr));

        cout << "sendto over" << endl;
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);

        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len); //udp的数据报(数据传输模式)类型的数据需要用recvfrom读取,tcp的字节流可以用read读取
        
        if(n > 0)
        {
            buffer[n] = 0;
            cout << buffer << endl;
        }
    }

    close(sockfd);    

    return 0;
}

客户端不需要手动绑定端口号,只需要OS去绑定即可。

客户端需要对服务端的sockaddr_in结构合理的初始化,才能接收和发送到正确的Server。

TCP与UDP传输的区别

字节流(Byte Stream)和数据报(Datagram)是网络通信中两种不同的传输方式,它们在数据传输的可靠性、传输方式和用途上有所区别:

可靠性:

字节流:通常指的是面向连接的传输方式,如TCP(传输控制协议)。它提供了可靠的数据传输,确保数据按照发送顺序到达,且不会丢失或重复。

数据报:通常指的是无连接的传输方式,如UDP(用户数据报协议)。它不保证数据的可靠传输,数据包可能会丢失、重复或到达顺序错乱。

传输方式:

字节流:在字节流传输中,数据像水流一样连续传输。发送方和接收方之间存在一个持续的连接,数据按照顺序到达。

数据报:数据报传输将数据分割成小的、独立的数据包进行发送。每个数据包携带目的地址信息,但不保证按照顺序到达,也不保证所有数据包都能到达。

用途:

字节流:适用于需要高可靠性的应用,如网页浏览、文件传输、电子邮件等,这些应用需要确保数据的完整性和顺序。

数据报:适用于对实时性要求高,但可以容忍一定丢包率的场景,如视频会议、在线游戏等。这些应用更关注数据的快速传输而不是完整性。

头部开销:

字节流:由于需要维护连接状态,通常头部开销较大,因为TCP头部包含了序列号、确认号、窗口大小等信息。

数据报:UDP头部相对简单,开销较小,只包含少量的控制信息。

速度:

字节流:由于其可靠性机制,如重传机制,可能会影响传输速度。

数据报:因为没有复杂的可靠性保证机制,通常传输速度较快。

在选择字节流还是数据报时,需要根据应用的具体需求来决定。如果数据完整性和顺序至关重要,应该选择字节流;如果实时性更重要,能够容忍一定的数据丢失,那么数据报可能是更好的选择。

telnet不能链接udp服务,因为telnet底层采用的是tcp的协议。

linux相关的指令操作

netstat,可以查看本地的服务器

telnet 127.0.0.1

本地环回连接服务器,常用于测试服务器是否存在BUG。

ip地址转化函数

本节只介绍基于 IPv4 socket 网络编程 ,sockaddr_in 中的成员 struct in_addr sin_addr 表示 32 位 的 IP 地址但是我们通常用点分十进制的字符串表示 IP 地址 , 以下函数可以在字符串表示 和 in_addr 表示之间转换 ;
字符串转in_addr的函数:
aton的第二个参数
in_addr转字符串的函数:
其中inet_ptoninet_ntop不仅可以转换IPv4in_addr,还可以转换IPv6in6_addr,因此函数接口是void *addrptr
关于 inet_ntoa
inet_ntoa 这个函数返回了一个 char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存 ip 的结果 . 那么是否需要调用者手动释放呢 ?
man 手册上说 , inet_ntoa 函数 , 是把这个返回结果放到了静态存储区 . 这个时候不需要我们手动进行释放.那么问题来了 , 如果我们调用多次这个函数 , 会有什么样的效果呢 ? 参见如下代码
运行结果如下 :
因为 inet_ntoa 把结果放到自己内部的一个静态存储区 , 这样第二次调用时的结果会覆盖掉上一次的结果 .
如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?
在APUE中, 明确提出inet_ntoa不是线程安全的函数;
但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;
在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis-Plus中isNull与SQL语法详解:处理空值的正确姿势

目录 前言1. 探讨2. 基本知识3. 总结 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 基本的Java知识推荐阅读&#xff1a; java框架 零基础从入门到精通的学习路线 附开源项目面经等&#xff08;超全&#x…

EGO Swarm翻译

目录 摘要 Ⅰ 介绍 Ⅱ 相关工作 A . 单四旋翼局部规划 B . 拓扑规划 C. 分布式无人机集群 Ⅲ 基于梯度的局部规划隐式拓扑轨迹生成 A.无需ESDF梯度的局部路径规划 B.隐式拓扑轨迹生成 Ⅳ 无人机集群导航 A 机间避碰 B. 定位漂移补偿 C. 从深度图像中去除agent Ⅴ …

电商数据采集电商,行业数据分析,平台数据获取|稳定的API接口数据

电商数据采集可以通过多种方式完成&#xff0c;其中包括人工采集、使用电商平台提供的API接口、以及利用爬虫技术等自动化工具。以下是一些常用的电商数据采集方法&#xff1a; 人工采集&#xff1a;人工采集主要是通过基本的“复制粘贴”的方式在电商平台上进行数据的收集&am…

PostgreSQL和Postgis安装

Windows下PostgreSQL和对应的版本的Postgis安装 PostgreSQL安装 1、官网下载地址 https://www.enterprisedb.com/downloads/postgres-postgresql-downloads 2、根据自己的系统下载完成&#xff0c;Windows下可以直接傻瓜式安装就OK 建议不要通过自带的这个程序安装postgis,…

代码开发相关操作

使用Vue项目管理器创建项目&#xff1a;&#xff08;vue脚手架安装一次就可以全局使用&#xff09; windowR打开命令窗口&#xff0c;输入vue ui&#xff0c;进入GUI页面&#xff0c;点击创建-> 设置项目名称&#xff0c;在初始化git下面输入&#xff1a;init project&…

Vulnhub DC-6靶机攻击实战(一)

导语   之前的分享中我们介绍了关于Vulnhub虚拟机前五个机器的攻防演练测试,接下来我们继续分享Vulnhub DC-6靶机攻击实战。 文章目录 搭建测试环境第一步、信息采集第二步、wpscan爆破第三步、开始查找其他的用户第四步、提权总结搭建测试环境 首先需要从Vulnhub官网中下载…

深度学习之超分辨率算法——FRCNN

– 对之前SRCNN算法的改进 输出层采用转置卷积层放大尺寸&#xff0c;这样可以直接将低分辨率图片输入模型中&#xff0c;解决了输入尺度问题。改变特征维数&#xff0c;使用更小的卷积核和使用更多的映射层。卷积核更小&#xff0c;加入了更多的激活层。共享其中的映射层&…

深度学习从入门到精通——图像分割实战DeeplabV3

DeeplabV3算法 参数配置关于数据集的配置训练集参数 数据预处理模块DataSet构建模块测试一下数据集去正则化模型加载模块DeepLABV3 参数配置 关于数据集的配置 parser argparse.ArgumentParser()# Datset Optionsparser.add_argument("--data_root", typestr, defa…

大数据操作实验一

1.Postgresql 1.1 数据库的对象创建 1.1.1 创建数据库(Database) 鼠标右键database进行创建 1.1.2 创建图(Schema) 鼠标右键schema&#xff0c;然后创建schema图纸 1.1.3 创建表(Table) 鼠标右键Table&#xff0c;创建表 1.2数据库实列化 1.2.1 实列化静态数据 提…

IDEA2024如何创建一个普通的Java Web项目工程(JSP)

本章教程,主要介绍如何在IDEA2024 专业版本中,创建一个普通的Java Web项目。 一、新建项目 二、配置项目 依次点击File——Project Structure——Modules 修改路径中的web为webapp,然后点击Create Artifact默认保存。 至此,一个基础的Java web就创建完成了。

Linux下mysql 8.0安装教程

本文介绍了如何在Linux下安装MySQL8.0,供大家参考,具体内容如下 准备工作: mysql8.0 rpm文件 测试工具(比如 idea的database工具) 安装步骤: 1. 下载mysql的repo源,下载地址: 进入Linux系统,输入指令: 1 wgethttps://dev.mysql.com/get/mysql80-community-rele…

libaom 源码分析:熵编码模块介绍

AV1 熵编码原理介绍 关于AV1 熵编码原理介绍可以参考:AV1 编码标准熵编码技术概述libaom 熵编码相关源码介绍 函数流程图 核心函数介绍 av1_pack_bitstream 函数:该函数负责将编码后的数据打包成符合 AV1 标准的比特流格式;包括写入序列头 OBU 的函数 av1_write_obu_header…

[数据结构#1] 并查集 | FindRoot | Union | 优化 | 应用

目录 1. 并查集原理 问题背景 名称与编号映射 数据结构设计 2. 并查集基本操作 (1) 初始化 (2) 查询根节点 (FindRoot) (3) 合并集合 (Union) (4) 集合操作总结 并查集优化 (1) 路径压缩 (2) 按秩合并 3. 并查集的应用 (1) 统计省份数量 (2) 判断等式方程是否成…

Centos创建共享文件夹拉取文件

1.打开VMware程序&#xff0c;鼠标右检你的虚拟机&#xff0c;打开设置 2.点击选项——共享文件夹——总是启用 点击添加&#xff0c;设置你想要共享的文件夹在pc上的路径&#xff08;我这里已经添加过了就不加了&#xff09; 注意不要中文&#xff0c;建议用share&#xff0c…

Element@2.15.14-tree checkStrictly 状态实现父项联动子项,实现节点自定义编辑、新增、删除功能

背景&#xff1a;现在有一个新需求&#xff0c;需要借助树结构来实现词库的分类管理&#xff0c;树的节点是不同的分类&#xff0c;不同的分类可以有自己的词库&#xff0c;所以父子节点是互不影响的&#xff1b;同样为了选择的方便性&#xff0c;提出了新需求&#xff0c;选择…

java版电子招投标采购|投标|评标|竞标|邀标|评审招投标系统源码

招投标管理系统是一款适用于招标代理、政府采购、企业采购和工程交易等领域的企业级应用平台。该平台以项目为主线&#xff0c;从项目立项到项目归档&#xff0c;实现了全流程的高效沟通和协作。通过该平台&#xff0c;用户可以实时共享项目数据信息&#xff0c;实现规范化管理…

【Verilog HDL 入门教程】 —— 学长带你学Verilog(基础篇)

文章目录 一、Verilog HDL 概述1、Verilog HDL 是什么2、Verilog HDL产生的背景3、Verilog HDL 和 VHDL的区别 二、Verilog HDL 基础知识1、Verilog HDL 语言要素1.1、命名规则1.2、注释符1.3、关键字1.4、数值1.4.1、整数及其表示1.4.2、实数及其表示1.4.3、字符串及其表示 2、…

龙迅#LT7911E适用于EDP/DP/TPYE-C转MIPIDSI应用,支持图像处理功能,内置I2C,主应用副屏显示,投屏领域!

1. 描述 LT7911E 是一款高性能 eDP 转 MIPI D-PHY 转换器&#xff0c;旨在将 eDP 源连接到 MIPI 显示面板。 LT7911E 集成了一个符合 eDP1.4 标准的接收器&#xff0c;支持 1.62Gbps 至 5.67Gbps 的输入数据&#xff0c;以 270Mbps 的递增步长&#xff0c;以及一个 2 端口 D…

《算法SM9》题目

判断题 SM9密码算法系统参数由KGC选择。 A.正确 B.错误 正确答案A 多项选择题 SM9密码算法KGC是负责&#xff08; &#xff09;的可信机构。 A.选择系统参数 B.生成主密钥 C.生成用户标识 D.生成用户私钥 正确答案ABD 判断题 SM9密钥封装机制封装的秘密密钥是根据…

C语言——实现求出最大值

问题描述&#xff1a;利用C语言自定义函数求出一维数组里边最大的数字 //利用函数找最大数#include<stdio.h>int search(int s[9]) //查找函数 {int i , max s[0] , max_xia 0;for(i0;i<9;i){if(s[i] > max){max_xia i;max s[max_xia];}}return max; } in…