Linux网络——套接字与UdpServer

目录

一、socket 编程接口

1.1 sockaddr 结构

1.2 socket 常见API

二、封装 InetAddr

三、网络字节序

四、封装通用 UdpServer 服务端

4.1 整体框架

4.2 类的初始化 

4.2.1 socket

4.2.2 bind

4.2.3 创建流式套接字

4.2.4 填充结构体

4.3 服务器的运行 

4.3.1 recvfrom

4.3.2 sendto

4.3.3 接收数据

4.3.4 发送数据

4.4 UdpServer.hpp

五、封装通用 UdpClient 客户端


OSI 参考模型与 TCP/IP 分层模型的对比

一、socket 编程接口

1.1 sockaddr 结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、 IPv6、UNIX Domain Socket。然而, 各种网络协议的地址格式并不相同:

它们都定义在 netinet/in.h 中,其中,

struct sockaddr

  • 它是一个通用的套接字地址结构体,通常在需要传递通用地址结构体指针的地方使用。
  • 定义:
    struct sockaddr 
    {
        unsigned short sa_family;    // 地址族
        char sa_data[14];            // 地址数据
    };
    
  • sa_family 指定地址族,比如 AF_INETAF_UNIX 等。

struct sockaddr_in

  • 它专门用于 IPv4 地址的套接字编程。
  • 定义:
    struct sockaddr_in 
    {
        short int sin_family;        // 地址族 (AF_INET)
        unsigned short int sin_port; // 端口号
        struct in_addr sin_addr;     // IP 地址
        unsigned char sin_zero[8];   // 填充,使结构体大小与 `struct sockaddr` 一致
    };
    
    struct in_addr 
    {
        unsigned long s_addr;        // 32 位的 IP 地址
    };
    
  • sin_family 通常为 AF_INET,表示使用 IPv4;AF_INET6,表示使用 IPv6 
  • sin_port 存储端口号,使用 htons 函数转换为网络字节序
  • sin_addr 存储 IPv4 地址,使用 inet_addrinet_pton 函数进行设置。
  • in_addr中的 s_addr初始化时使用 INADDR_ANY

struct sockaddr_un

  • 它专门用于 UNIX 域套接字编程。
  • 定义:
    struct sockaddr_un 
    {
        sa_family_t sun_family;      // 地址族 (AF_UNIX)
        char sun_path[108];          // 路径名
    };
    
  • sun_family 通常为 AF_UNIX,表示使用 UNIX 域套接字。
  • sun_path 存储文件系统路径名,表示套接字文件的位置。

IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容

socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

1.2 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);

二、封装 InetAddr

InetAddr 将一套接字结构体的服务号与端口号封装成类,以后调用某一套接字的IP地址与端口号时就可以直接使用语言层面的一些数据类型,如 string 、uint16_t ,这样比较统一。

因为我们传入的 ip 地址是 "xxx.xxx.xxx.xxx" ,这是一个 string 类,在服务端的 main 函数中,可以使用 inet_addr 将其传入 struct addr_in 的 s_addr 中。

class InetAddr
{
private:
    struct sockaddr_in _addr;
    string _ip;
    uint16_t _port;
};


接下来,就是类的成员函数,保证类可以返回 sockaddr_in \ ip \ port 即可。
但是,需要注意的是,struct sockaddr_in 中的 IP 地址与端口号与我们定义的类型不同,系统中也提供了相应的函数便于我们的转化, ntohs 与 inet_ntoa 前者用于网络字节序转化为主机字节序,后者用于将网络字节顺序给出的主机地址转化为IPv4点分十进制的字符串。

void GetAddr()
{
    _ip = inet_ntoa(_addr.sin_addr.s_addr);
    _port = ntohs(_addr.sin_port);
}
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

class InetAddr
{
private:
    void GetAddress()
    {
        _port = ntohs(_addr.sin_port);
        _ip = inet_ntoa(_addr.sin_addr);
    }

public:
    InetAddr(const struct sockaddr_in &addr) : _addr(addr)
    {
        GetAddress();
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

三、网络字节序

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

换句话说,如果从一个大端存储的计算机传输数据至一个小端存储的计算机,那么如果网络层不进一步优化的话,传过去的数据不就都乱套了吗。

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

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

#include <arpa/inet.h>
uint32_t htonl(uint32_t, hostlong);
uint16_t htons(uint16_t, hostshort);
uint32_t ntohl(uint32_t, netlong);
uint16_t ntohs(uint16_t, netshort);

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

四、封装通用 UdpServer 服务端

服务端要负责的是接收客户端的请求并给予客户端一定的响应,对 UdpServer 的封装要包括套接字的建立、服务的绑定、数据的收集、数据的传输等。

4.1 整体框架

首先,我们知道每个Mac都有其独特的IP,那么Mac中有那么多的应用软件,应该如何才能确定当前服务需要在哪个应用中呢?这就引入了端口号,用于标识一台Mac中唯一的应用。所以在UdpServer中,不仅要创建流式套接字,还要有唯一的端口号。除此之外,如果在外部需要停止服务端的响应,可以设置一个布尔类型的变量来标识UdpServer是否在运行。

其次,在编写通用的 UdpServer 类时,构造函数通常不会将套接字文件描述符 (sockfd) 作为参数进行传递。这是因为套接字文件描述符是在类的内部创建和管理的,而不是由外部提供。

#include <iostream>

static const int gdefaultsockfd = -1;
class UdpServer
{
    public:
    UdpServer(uint16_t port):_sockfd(gdefaultsockfd), _port(port), _isrunning(false)
    {}
private:
    int _sockfd;
    uint16_t _port;
    bool _isrunning;
};

4.2 类的初始化 

上面我们提到编写通用的 UdpServer类时,构造函数通常不需要传入 sockfd ,在后面会将的 TcpServer 也是如此,所以在初始化函数时,就要对套接字进行创建以及与 sockaddr 的绑定。

4.2.1 socket

socket 函数用于创建一个新的套接字。套接字是网络通信的端点。 

#include <sys/types.h>      
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

传入参数

  • domain: 指定协议族,如 AF_INET(IPv4)或 AF_INET6(IPv6)等。
  • type: 指定套接字类型,如 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP)。
  • protocol: 通常为 0,表示自动选择合适的协议。如果需要特定协议,可以传递协议编号。

返回值

    成功时返回一个文件描述符,失败时返回 -1,并设置 errno 来指示错误。

4.2.2 bind

bind 函数将一个套接字绑定到一个特定的本地地址和端口上。这通常用于服务器端。 

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

传入参数

  • socket: 由 socket 函数返回的套接字文件描述符。
  • address: 指向一个 struct sockaddr 类型的指针,包含要绑定的地址信息。
  • address_len: 地址结构体的长度。

返回值

成功时返回 0,失败时返回 -1,并设置 errno 来指示错误。

4.2.3 创建流式套接字

    void UdpInit()
    {
        // 1.创建流式套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno); 
            exit(SOCKET_ERROR);
        }
    }

我们带入了上一节中的日志宏,同时因为 socket 函数可以带出错误信息,所以当套接字创建失败是,可以使用 strerror 打印一下错误信息,并可以通过枚举使 exit 时的信息更明确:

#include <cstring>
#include "Log.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
}; 

4.2.4 填充结构体

这里使用的是 struct sockaddr_in 结构体,首先把结构体成员都初始化为0,这里使用 bezero 函数,sockaddr_in 结构体中的各个成员对 sin_family\sin_addr.s_addr 初始化,初始化的参数详见1.1 sockaddr 结构,然后向其中的 sin_port 填充我们输入的端口号。

        // 2.0创建struct sockaddr_in 并填充
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(_port);
        // 2.1 bind 将一个套接字绑定到一个特定的本地地址和端口上。这通常用于服务器端。 
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        LOG(INFO, "socket bind success\n");
    void InitServer()
    {
        // 1.创建流式套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno); // strerror->#include<string.h>
            exit(SOCKET_ERROR);
        }
        // 2.0创建struct sockaddr_in 并填充
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(_port);
        // 2.1 bind 将一个套接字绑定到一个特定的本地地址和端口上。这通常用于服务器端。 
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        LOG(INFO, "socket bind success\n");
    }

4.3 服务器的运行 

首先,我们希望服务器一直运行,所以需要设置死循环。其次,服务器进行收发信息要使用到函数recvfrom 与 sendto

4.3.1 recvfrom

recvfrom 函数用于从一个UDP套接字接收数据。它可以用于接收来自任意地址的数据,因此特别适合于UDP服务器。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);

传入参数

  • socket: 套接字文件描述符,由 socket 函数返回。
  • buffer: 用于存储接收到的数据的缓冲区指针。
  • length: 缓冲区的长度。
  • flags: 通常为 0,也可以是一些控制操作行为的标志,例如 MSG_DONTWAIT(非阻塞操作)。
  • address: 指向 struct sockaddr 的指针,用于存储发送数据的源地址
  • address_len: 指向 socklen_t 的指针,指示 address 的大小,并在函数返回时设置为实际地址的长度。

返回值

成功时返回接收到的数据字节数,失败时返回 -1,并设置 errno 来指示错误。

4.3.2 sendto

sendto 函数用于通过一个UDP套接字发送数据。它可以用于发送数据到指定的地址,因此特别适合于UDP客户端和服务器。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len);

传入参数

  • socket: 套接字文件描述符,由 socket 函数返回。
  • message: 指向要发送的数据缓冲区的指针。
  • length: 要发送的数据的长度。
  • flags: 通常为 0,也可以是一些控制操作行为的标志,例如 MSG_DONTWAIT(非阻塞操作)。
  • dest_addr: 指向 struct sockaddr 的指针,包含目标地址信息
  • dest_len: 目标地址结构体的长度。

返回值

成功时返回发送的数据字节数,失败时返回 -1,并设置 errno 来指示错误。

4.3.3 接收数据

首先,所有的操作都要定义在一个 while 的死循环中。其次,因为 recvfrom 中需要使用缓冲区,所以还要定义一个缓冲区。同时, recvfrom 可以标明发送数据的源地址,所以可以定义一个 sockaddr_in 的结构体,用于存储发送数据的源地址,当接收成功时,可以使用之前定义的 InetAddr 类来接收该源地址。

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
            }
        }
        _isrunning = false;
    }

4.3.4 发送数据

既然已经接收到数据了,我们需要让客户端知道服务端已经接收到了数据,所以当接收数据成功时,在使用 sendto 发送数据至客户端。 

    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
                sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
    }

以下是为什么服务端需要接收数据还需要传输到客户端的原因,究其原因还是与Udp最初的开发有关:

在 UDP 服务器接收到客户端的信息后使用 sendto 函数发回响应,是为了实现双向通信,使得客户端可以知道服务器已经正确接收到并处理了请求。以下是这种设计背后的主要原因:

1. 确认信息接收

在无连接的 UDP 协议中,数据包的发送和接收是独立的,且没有内建的机制来确认数据包是否成功到达对方。通过服务器发回一个响应,客户端可以确认其发送的信息已经被接收到并处理。

2. 双向通信

多数网络应用需要双向通信,不仅客户端需要向服务器发送数据,服务器也需要向客户端发送数据。比如,客户端发送请求数据,服务器处理后返回相应的结果。这种交互模式在很多应用场景中都是必须的。

3. 应用层协议实现

通过在应用层协议中定义请求-响应模式,可以更好地实现和管理通信过程。服务器接收到请求后返回响应,是许多协议(例如 DNS、DHCP 等)基本工作方式的一部分。

4. 保持通信会话

在某些应用中,客户端和服务器需要保持持续的通信会话。服务器向客户端发回响应,可以作为会话的一部分,确保双方在同一上下文中进行通信。

4.4 UdpServer.hpp

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstring>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    USAGE_ERROR
};

static const int gdefaultsockfd = -1;
class UdpServer
{
public:
    UdpServer(uint16_t port) : _sockfd(gdefaultsockfd), _port(port), _isrunning(false)
    {
    }
    void InitServer()
    {
        // 1.创建流式套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error, %s, %d\n", strerror(errno), errno); // strerror->#include<string.h>
            exit(SOCKET_ERROR);
        }
        // 2.0创建struct sockaddr_in 并填充
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(_port);
        // 2.1 bind 将一个套接字绑定到一个特定的本地地址和端口上。这通常用于服务器端。 
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(FATAL, "bind error, %s, %d\n", strerror(errno), errno);
            exit(BIND_ERROR);
        }
        LOG(INFO, "socket bind success\n");
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buffer[1024];
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buffer[n] = 0;
                InetAddr addr(peer);
                LOG(DEBUG, "get message from [%s:%d]: %s\n", addr.Ip().c_str(), addr.Port(), buffer);
                sendto(_sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    bool _isrunning;
};

如果以后有其他的业务,可以在类内定义一个回调函数成员指针或者使用 function 封装一个回调函数,在构造函数中传入该回调函数,并在 Start 中执行相应的回调函数即可,大致思路如下,具体改动的是 Start 中 sendto 的传入参数。

using func_t = std::function<std::string(const std::string&, bool &ok)>;
class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func) : _sockfd(defaultfd), _port(port), _isrunning(false), _func(func)
    {
    }
    void Start()
    {
        while ()
        {
            if ()
            {
                std::string response = _func(request, ok); 
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
    }
private:
    int _sockfd;
    uint16_t _port; 
    bool _isrunning;
    // 给服务器设定回调,用来让上层进行注册业务的处理方法
    func_t _func;
};

五、封装通用 UdpClient 客户端

在 C/C++ 中,argcargv 是命令行参数的标准输入参数,用于在程序启动时获取命令行参数。

  • argc (argument count): 表示命令行参数的个数,包括程序名本身。
  • argv (argument vector): 是一个字符指针数组,包含了命令行输入的参数。argv[0] 通常是程序的名称argv[1]argv[argc-1] 是实际的命令行参数。

当程序正确启动时,应输入以下参数

./UdpClient 127.0.0.1 8080
  • argc 的值为 3。
  • argv 的内容如下:
    • argv[0]"./UdpClient",程序名。
    • argv[1]"127.0.0.1",服务器 IP。
    • argv[2]"8080",服务器端口。
#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << " serverip serverport\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
    }

    // 构建目标主机的socket信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    server.sin_addr.s_addr = inet_addr(serverip.c_str());

    std::string message;
    // 2. 直接通信即可
    while(true)
    {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);
        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));

        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        char buffer[1024];
        ssize_t n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    return 0;
}

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

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

相关文章

全国区块链职业技能大赛国赛考题区块链产品需求分析与方案设计

任务1-1:区块链产品需求分析与方案设计 本任务需要依据项目背景完成需求分析与方案设计,具体要求如下: 依据给定区块链食品溯源系统的业务架构图,对考题进行业务分析,尽可能多的去考虑一个业务系统所需要的模块,使用Visio或思维导图工具展现本系统的基本设计概念和处理流…

基于ffmepg的视频剪辑

1.ffmpeg命令实现视频剪辑 FFmpeg是一个非常强大的视频处理工具&#xff0c;可以用来剪辑视频。以下是一个基本的FFmpeg命令行示例&#xff0c;用于剪辑视频&#xff1a; $ ffmpeg -i ./最后一滴水.mp4 -ss 0:0:20 -t 50 -c copy output.mp4-i ./最后一滴水.mp4 输入文件  …

图像生成(Text-to-Image)发展脉络

这篇博客对 图像生成&#xff08;image generation&#xff09; 领域的经典工作发展进行了梳理&#xff0c;包括重要的一些改进&#xff0c;目的是帮助读者对此领域有一个整体的发展方向把握&#xff0c;并非是对每个工作的详细介绍。 脉络发展&#xff08;时间顺序&#xff0…

探究大语言模型(LLM)漏洞和安全优秀实践

你可能已听说过LLM强势亮相&#xff0c;至少ChatGPT就是代表。 大语言模型(LLM)指语言处理模型。这类模型经过训练&#xff0c;可以执行各种各样的语言任务&#xff1a;翻译、文本生成和问题回答等。 有几个LLM家族和架构&#xff0c;最著名的是GPT(生成式预训练Transformer)…

Grafana :利用Explore方式实现多条件查询

背景 日志统一推送到Grafana上管理。所以&#xff0c;有了在Grafana上进行日志搜索的需求&#xff0c;而进行日志搜索通常需要多条件组合。 解决方案 通过Grafana的Explore的方式实现多条件查询。 直接看操作步骤&#xff1a; 在主页搜索框中输入“Explore” 进入这个界面…

python—日期相差多少天(PythonTip)

[题目描述] 编写一个程序&#xff0c;计算两个日期之间的天数。 导入datetime模块。定义函数calculate_days_between()数&#xff0c;其中有两个参数&#xff1a;(date1, date2)&#xff0c;类型为字符串&#xff0c;格式为YYYY-MM-DD。在函数内&#xff0c;将字符串转换为date…

全面战争模拟器免费下载地址,纯分享

全面战争模拟器以其独特的物理引擎和搞笑的战斗场面吸引了大量玩家&#xff0c;并在游戏社区中赢得了极高的评价。它不仅提供了丰富的策略性玩法&#xff0c;还通过滑稽的视觉效果和搞笑的战斗带来了极大的娱乐性。游戏的沙盒模式和自定义功能更是让玩家能够充分发挥创意&#…

磁盘的作业

1、新添加一块硬盘&#xff0c;大小为5g&#xff0c;给这块硬盘分一个mbr格式的主分区(大小为3g)&#xff0c;给此主分区创建ext2的文件系统&#xff0c;挂载到/guazai1目录&#xff0c;并写入文件内容为"this is fist disk"文件名为1.txt的文件。 [rootwyk ~]# fdis…

02线性表 - 链表

这里是只讲干货不讲废话的炽念&#xff0c;这个系列的文章是为了我自己以后复习数据结构而写&#xff0c;所以可能会用一种我自己能够听懂的方式来描述&#xff0c;不会像书本上那么枯燥和无聊&#xff0c;且全系列的代码均是可运行的代码&#xff0c;关键地方会给出注释^_^ 全…

【时时三省】(C语言基础)函数和数组

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ——csdn时时三省 函数 跟数学里面的函数很相似 数组 一组相同类型的元素的集合 比如把5个整形1-5存起来 int arr&#xff3b;10&#xff3d;&#xff1d;&#xff5b;1&#xff0c;2&#xff0c;3&#x…

逻辑门的题目怎么做?

FPGA语法练习——二输入逻辑门&#xff0c;一起来听~~ FPGA语法练习——二输入逻辑门 题目介绍&#xff1a;F学社-全球FPGA技术提升平台 (zzfpga.com)

高功能自闭症:挑战与潜能并存

高功能自闭症是一种复杂的神经发育障碍&#xff0c;其患者通常在认知、语言和行为方面存在发育障碍&#xff0c;但保持相对正常的社交互动和自理能力。这种疾病由大脑神经发育异常引起&#xff0c;涉及神经递质、脑结构和功能连接等多个方面&#xff0c;导致信息处理和整合困难…

WebRTC音视频-前言介绍

目录 效果预期 1&#xff1a;WebRTC相关简介 1.1&#xff1a;WebRTC和RTC 1.2&#xff1a;WebRTC前景和应用 2&#xff1a;WebRTC通话原理 2.1&#xff1a;媒体协商 2.2&#xff1a;网络协商 2.3&#xff1a;信令服务器 效果预期 1&#xff1a;WebRTC相关简介 1.1&…

SpringCloud的认识和初步搭建

目录 一.认识SpringCloud 二.SpringCloud的部署 2.1开发环境 2.2数据库的建立 2.3SpringCloud的部署 第一步&#xff1a; 创建Maven项目 第二步&#xff1a;完善pom文件 第三步&#xff1a;创建两个子项目 第四步&#xff1a;声明项目依赖以及构建插件 第五步&#xf…

Docker核心技术:容器技术要解决哪些问题

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;容器技术要解决哪些问题&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进容器技术要解决哪些问题&#xff08;本文&#xff09;Docker的基本使用Docker是如何实…

Ubuntu20.04从零开搭PX4MavrosGazebo环境并测试

仅仅是个人搭建记录 参考链接&#xff1a; https://zhuanlan.zhihu.com/p/686439920 仿真平台基础配置&#xff08;对应PX4 1.13版&#xff09; 语雀 mkdir -p ~/tzb/catkin_ws/src mkdir -p ~/tzb/catkin_ws/scripts cd catkin_ws && catkin init catkin build cd…

RV1103使用rtsp和opencv推流视频到网页端

参考&#xff1a; Luckfox-Pico/Luckfox-Pico-RV1103/Luckfox-Pico-pinout/CSI-Camera Luckfox-Pico/RKMPI-example Luckfox-Pico/RKMPI-example 下载源码 其中源码位置&#xff1a;https://github.com/luckfox-eng29/luckfox_pico_rtsp_opencv 使用git clone由于项目比较大&am…

共享模型之无锁

一、问题提出 1.1 需求描述 有如下的需求&#xff0c;需要保证 account.withdraw() 取款方法的线程安全&#xff0c;代码如下&#xff1a; interface Account {// 获取余额Integer getBalance();// 取款void withdraw(Integer amount);/*** 方法内会启动 1000 个线程&#xf…

纯净IP代理网站解析与代理推荐

一、纯净IP代理网站解析 在浩瀚的代理服务市场中&#xff0c;纯净IP代理以其特有的优势脱颖而出。它们不仅提供高质量、无污染的IP资源&#xff0c;还注重服务的稳定性和安全性&#xff0c;确保用户在使用过程中能够享受到好的网络体验。纯净IP代理的优势在于其能够有效解决因…

科技论文在线--适合练习期刊写作和快速发表科技成果论文投稿网站

中国科技论文在线这个平台可以作为练手的一个渠道&#xff0c;至少可以锻炼一下中文写作&#xff0c;或者写一些科研方向的简单综述性文章。当然&#xff0c;如果你的老师期末要求也是交一份科技论文在线的刊载证明的话&#xff0c;这篇文章可以给你提供一些经验。 中国科技论…