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. 创建socketint 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;
}

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

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

相关文章

在树莓派4B上部署yolov8环境完成高帧率检测任务

目录 前言 在树莓派上安装Pytorch 在树莓派上安装Ultralytics环境 在树莓派上初步测试模型 在树莓派上安装NCNN 用NCNN模型实现高帧率检测 前言 在我前面的文章里讲了如何用yolov8从0开始训练自己的模型&#xff0c;现在这篇文章将教大家如何在树莓派上部署yolov8的环境…

【BUG】已解决:TypeError: Descriptors cannot not be created directly.

已解决&#xff1a;TypeError: Descriptors cannot not be created directly. 目录 已解决&#xff1a;TypeError: Descriptors cannot not be created directly. 【常见模块错误】 【错误原因】 【解决方案】 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来…

适用于618/7xx芯片平台 AT开发 远程FOTA升级指南教程

简介 AT版本的远程升级主要是对AT固件版本进行升级&#xff0c;实际方式为通过合宙官方IOT平台升级或者使用自己搭建的服务器进行升级服务。 该文档教程流程适用于 618/716S/718P 芯片平台的Cat.1模块 合宙IOT平台配置 升级日志 —— 如何查看 升级日志 —— 响应码列表 响应…

【BUG】已解决:ModuleNotFoundError: No module named ‘torch‘

已解决&#xff1a;ModuleNotFoundError: No module named ‘torch‘ 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页&#xff0c;我是博主英杰&#xff0c;211科班出身&#xff0c;就职于医疗科技公司&#xff0c;热衷分享知识&#xff0c;武汉城市…

Unity-URP-SSAO记录

勾选After Opacity Unity-URP管线&#xff0c;本来又一个“bug”, 网上查不到很多关于ssao的资料 以为会不会又是一个极度少人用的东西 而且几乎都是要第三方替代 也完全没有SSAO大概的消耗是多少&#xff0c;完全是黑盒(因为用的人少&#xff0c;研究的人少&#xff0c;优…

ubuntu2204配置anacondacuda4090nvidia驱动

背景 某个机房的几台机器前段时间通过dnat暴露至公网后被入侵挖矿&#xff0c;为避免一些安全隐患将这几台机器执行重装系统操作&#xff1b; 这里主要记录配置nvidia驱动及cuda&anaconda。 步骤 大概分为几个步骤 禁用nouveau配置grub显示菜单install nvidia-driveri…

Qt Style Sheets-使用样式表自定义 Qt 部件

使用样式表自定义 Qt 部件 在使用样式表时&#xff0c;每个小部件都被视为具有四个同心矩形的框&#xff1a;边距矩形、边框矩形、填充矩形和内容矩形。框模型对此进行了更详细的描述。 盒模型 以下是四个同心矩形在概念上的呈现方式&#xff1a; 边距超出边框。边框绘制在边…

1.Fabric框架

要了解Fabric&#xff0c;首先要知道Hyperledger开源项目。 2015年12月&#xff0c;由开源世界的旗舰组织Linux基金会牵头&#xff0c;30家初始企业成员共同宣布Hyperledger联合项目成立。Hyperledger 超级账本&#xff0c;是首个面向企业应用场景的分布式账本平台&#xff0c…

【Spark官方文档部分翻译】RDD编程指南(RDD Programming Guide)

写在前面 内容如何选择 本翻译只翻译本人认为精华的部分&#xff0c;本人认为的Spark的一些核心理念&#xff0c;编程思想。一些特别基础的操作包括但不限于搭建环境就不在此赘述了。 配套版本 本系列基于Spark 3.3.1&#xff0c;Scala 2.12.10&#xff0c;进行翻译总结 原…

nginx通过nginx_upstream_check_module实现后端健康检查

1、简介说明 nginx是常用的反向代理和负载均衡服务&#xff0c;具有强大并发能力、稳定性、丰富的功能集、低资源的消耗。 nginx自身是没有针对后端节点健康检查的&#xff0c;但是可以通过默认自带的ngx_http_proxy_module 模块和ngx_http_upstream_module模块中的相关指令来完…

Redis之List列表

目录 一.列表讲解 二.列表命令 三.内部编码 四.应用场景 Redis的学习专栏&#xff1a;http://t.csdnimg.cn/a8cvV 一.列表讲解 列表类型是用来存储多个有序的字符串&#xff0c;如下所示&#xff0c;a、b、c、d、e五个元素从左到右组成了一个有序的列表&#xff0c;列表中的…

单例模式_Golang

目录 一、单例模式 1.1 基本概念 1.2 使用场景 二、Golang实现 2.1 懒汉模式&#xff08;Lazy Loading&#xff09; 一、单例模式 1.1 基本概念 一个类只能生成一个实例&#xff0c;且该类能自行创建这个实例的一种模式,这个定义个人感觉可以拆的通俗一些,在项目的生命周…

uniapp小程序上传pdf文件

<template><view class"mainInnBox"><view class"formBox"><!-- 注意&#xff0c;如果需要兼容微信小程序&#xff0c;最好通过setRules方法设置rules规则 --><u-form :model"form" ref"uForm" :rules&quo…

C语言:数组-学习笔记(万字笔记)——翻新版

目录 前言&#xff1a; 1、 数组的概念 1.1 什么是数组 1.2 为什么学习数组&#xff1f; 2. ⼀维数组的创建和初始化 2.1 数组创建 2.2 数组的初始化 2.3 数组的类型 2.3.1 什么是数组类型&#xff1f; 2.3.2 数组类型的作用 3、 一维数组的使用 3.1 数组下标 3.2 数…

ExoPlayer架构详解与源码分析(15)——Renderer

系列文章目录 ExoPlayer架构详解与源码分析&#xff08;1&#xff09;——前言 ExoPlayer架构详解与源码分析&#xff08;2&#xff09;——Player ExoPlayer架构详解与源码分析&#xff08;3&#xff09;——Timeline ExoPlayer架构详解与源码分析&#xff08;4&#xff09;—…

CentOS 7 初始化环境配置详细

推荐使用xshell远程连接&#xff0c;如链接不上 请查看 CentOS 7 网络配置 修改主机名 hostname hostnamectl set-hostname xxx bash 关闭 SElinux 重启之后生效 配置yum源&#xff08;阿里&#xff09; 先备份CentOS-Base.repo&#xff0c;然后再下载 mv /etc/yum.repos…

《昇思25天学习打卡营第24天|基于 MindSpore 实现 BERT 对话情绪识别》

1. BERT 模型概述 BERT&#xff08;Bidirectional Encoder Representations from Transformers&#xff09;是一个预训练的语言表示模型&#xff0c;可以应用于多种自然语言处理任务&#xff0c;包括对话情绪识别。BERT 通过双向 Transformer 编码器&#xff0c;能同时考虑句子…

什么是单例模式,有哪些应用?

目录 一、定义 二、应用场景 三、6种实现方式 1、懒汉式&#xff0c;线程不安全。 2、懒汉式&#xff0c;线程安全 3、双检锁/双重校验锁&#xff08;DCL&#xff0c;即 double-checked locking&#xff09; 4、静态内部类方式-------只适用于静态域 5、饿汉式 6、枚举…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 图像物体的边界(200分) - 三语言AC题解(Python/Java/Cpp)

🍭 大家好这里是清隆学长 ,一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导 👏 感谢大家的订阅➕ 和 喜欢💗 🍿 最新华为OD机试D卷目录,全、新、准,题目覆盖率达 95% 以上,支持题目在线…

NFT革命:数字资产的确权、营销与元宇宙的未来

目录 1、NFT&#xff1a;数字社会的数据确权制度 2、基于低成本及永久产权的文化发现 3、PFP&#xff1a;从“小图片”到“身份表达”&#xff0c;再到社区筛选 4、透明表达&#xff1a;NFT 在数字化营销中的商业价值 5、可编程性&#xff1a;赋予 NFT 无限可能的应用 5.…