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 客户端
一、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_INET
、AF_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
,表示使用 IPv6sin_port
存储端口号,使用htons
函数转换为网络字节序。sin_addr
存储 IPv4 地址,使用inet_addr
或inet_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++ 中,argc
和 argv
是命令行参数的标准输入参数,用于在程序启动时获取命令行参数。
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;
}
相关文章:
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 rec…...
SpringBoot源码深度解析
今天,聊聊SpringBoot的源码,本博客聊的版本为v2.0.3.RELEASE。目前SpringBoot的最新版为v3.3.2,可能目前有些公司使用的SpringBoot版本高于我这个版本。但是没关系,因为版本越新,新增的功能越多,反而对Spri…...
【Qt】常用控件
文章目录 QWidgetenabledgeometrywindow framewindowTitlewindowIconqrc资源管理windowOpacitycursorfonttoolTipfocusPolicystyleSheet 按钮类PushButtonRadioButtonCheckBoxSignals 显示类LabelLCDNumberProgressBarCalendar 输入类LineEditTextEditComboBoxSpinBoxDateTimeE…...
electron 主进程和渲染进程通信
在Electron中,主进程(main process)和渲染进程(renderer process)之间的通信是非常重要的,因为Electron应用通常会将用户界面(由Web技术如HTML, CSS, 和JavaScript构建)和原生功能(如系统对话框、文件I/O等)分开处理。主进程管理应用的生命周期和创建渲染进程,而渲染…...
【ARM】MDK-解决CMSIS_DAP.DLL missing报错
【更多软件使用问题请点击亿道电子官方网站】 1、 文档目标 记录解决CMSIS_DAP.DLL missing的报错情况,对应相关报错信息,供后续客户参考,快速解决客户问题。 2、 问题场景 客户进行硬件调试时,发现Target设置内有CMSIS_DAP.DL…...
CSS 的环境变量函数env()
在CSS中,env() 函数并不是传统意义上的“环境变量”函数,如你在编程语言中可能遇到的那样。相反,env() 是CSS中的一个函数,它用于访问由宿主环境(如浏览器)提供给CSS的自定义属性(也称为环境变量…...
数学建模--国赛备赛---TOPSIS算法
目录 1.准备部分 1.1提交材料 1.2MD5码相关要求 2.TOPSIS算法 2.1算法概述 2.2基本概念 2.3算法核心思想 2.4拓展思考 3.适用赛题 3.1适用赛题说明 3.2适用赛题举例 4.赛题分析 4.1指标的分类 4.2数据预处理 4.2.1区间型属性的变换 4.2.2向量规范化 4.3数据加…...
均值滤波算法及实现
均值滤波器的使用场景: 均值滤波器使用于处理一些如上述蓝色线的高斯噪声场景 红色曲线是经过均值滤波处理后的数据。主要因为均值滤波设置数据缓冲区(也即延时周期),使得测量值经过缓冲不会出现特别大的变化。 黄色曲线为高斯噪声…...
【Apache Doris】周FAQ集锦:第 16 期
【Apache Doris】周FAQ集锦:第 16 期 SQL问题数据操作问题运维常见问题其它问题关于社区 欢迎查阅本周的 Apache Doris 社区 FAQ 栏目! 在这个栏目中,每周将筛选社区反馈的热门问题和话题,重点回答并进行深入探讨。旨在为广大用户…...
单例模式_Golang
目录 一、单例模式 1.1 基本概念 1.2 使用场景 二、Golang实现 2.1 懒汉模式(Lazy Loading) 一、单例模式 1.1 基本概念 一个类只能生成一个实例,且该类能自行创建这个实例的一种模式,这个定义个人感觉可以拆的通俗一些,在项目的生命周…...
代码随想录 day 18 二叉树
第六章 二叉树part06 详细布置 530.二叉搜索树的最小绝对差 需要领悟一下二叉树遍历上双指针操作,优先掌握递归 题目链接/文章讲解:https://programmercarl.com/0530.%E4%BA%8C%E5%8F%89%E6%90%9C%E7%B4%A2%E6%A0%91%E7%9A%84%E6%9C%80%E5%B0%8F%E7%B…...
降雨量预测 | Matlab基于ARIMA-RBF降雨量预测
目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 降雨量预测 | Matlab基于ARIMA-RBF降雨量预测 注:程序和数据放在一个文件夹。 程序语言为matlab,程序可出预测效果图,指标图; 代码特点:参数化编程、参数可方便更改、代…...
包含示例和模板的流程文档指南
当您的业务扩展时,您会得到越来越多的移动部件,并且需要有人来跟踪复杂性。人员和任务需要以尽可能最高效的方式进行组织,并且您必须找到某种方法让员工知道如何执行有效完成工作所需的流程。 为了使流程可重复,需要对其进行记录…...
51单片机嵌入式开发:15、STC89C52RC操作蜂鸣器实现一个music音乐播放器的音乐盒
STC89C52RC操作蜂鸣器实现一个music音乐播放器的音乐盒 1 概述2 蜂鸣器操作方法3 蜂鸣器发出音声4 硬件电路5 软件实现6 整体工程:7 总结 1 概述 要实现一个基于STC89C52RC单片机的音乐盒,可以按照以下步骤进行: (1)硬…...
B树(B-Tree)数据结构
1. 什么是B树? B树(B-Tree)是一种多路搜索树,用于存储和检索大量数据。它是自适应的,适用于各种存储设备和各种数据量。B树的特点是高效的搜索、插入和删除操作,且可以在各种情况下保持树的平衡。 2. B树…...
【BUG】已解决:ModuleNotFoundError: No module named ‘torch‘
已解决:ModuleNotFoundError: No module named ‘torch‘ 欢迎来到英杰社区https://bbs.csdn.net/topics/617804998 欢迎来到我的主页,我是博主英杰,211科班出身,就职于医疗科技公司,热衷分享知识,武汉城市…...
数据结构——队列(链式结构)
一、队列链式结构定义 队列的链式存储结构是一种用链表实现的队列,它不像顺序存储结构那样需要预先分配固定大小的空间。链式存储结构的队列由节点组成,每个节点包括数据和指向下一个节点的指针。队列的链式存储结构可以动态地分配内存,更灵活地处理数据。在链式存储结构中…...
解决GoLand添加GOROOT提示The selected directory is not a valid home for Go Sdk的问题
现象 解决 在Go安装路径下找到zversion.go文件,我的在D:\Program Files\Go1.21.1\src\runtime\internal\sys下面 打开文件,添加如下内容: const TheVersion go1.21.1保存后再重新添加GOROOT即可...
51单片机(STC8H8K64U/STC8051U34K64)_RA8889驱动TFT大屏_I2C_HW参考代码(v1.3) 硬件I2C方式
本篇介绍单片机使用硬件I2C方式控制RA8889驱动彩屏。 提供STC8H8K64U和STC8051U34K64的参考代码。 【硬件部份】STC8H8K64U/STC8051U34K64 RA8889开发板 7寸TFT 800x480 1. 实物连接图:STC8H8K64URA8889开发板,使用P2口I2C接口: 2.实物连…...
【Python其他检查字符串占字节数的方法】
在Python中,检查字符串在特定编码下占用的字节数,最标准且常用的方法是通过字符串的.encode()方法将字符串转换为字节串,然后使用len()函数来获取这个字节串的长度。这是因为字符串(在Python 3中)是以Unicode形式存储的…...
梧桐数据库: 数据库技术中的重写子查询技术
数据库技术中的重写子查询技术,是数据库查询优化的一种重要手段。该技术主要通过改变子查询的形式,使其在执行效率和性能上得到优化。以下是对重写子查询技术的详细解析: 一、定义与目的 定义:重写子查询技术是指在数据库查询优…...
PHP连接MySQL数据库
PHP本身不具备操作MySQL数据库的能力,需要借助MySQL扩展来实现。 1、PHP加载MySQL扩展:php.ini文件中。(不要用记事本打开) 2、PHP中所有扩展都是在ext的文件夹中,需要指定扩展所在路径:extension_dir。 3、…...
STM32自己从零开始实操:PCB全过程
一、PCB总体分布 以下只能让大家看到各个模块大致分布在板子的哪一块,只能说每个人画都有自己的理由: 电源:从外部接入电源,5V接到中间,向上变成4V供给无线,向下变成3V供给下面的接口(也刻意放…...
error `slot` attributes are deprecated vue/no-deprecated-slot-attribute
旧的代码如下: <template slot"title">{{ item.title }}</template> {{ item.title }} 是一个模板标签,它在模板中插入了一个元素(slot),并指定了元素的名称为 “title”。这个标签在模板中显示…...
Websocket自动消息回复服务端工具
点击下载《Websocket自动消息回复服务端工具》 1. 前言 在进行Websocket开发时,前端小伙伴通常是和后端开发人员同步进行项目开发,经常会遇到后端开发人员接口还没开发完,也没有可以调试的环境,只能按照接口文档进行“脑回路开发…...
【软考】UML中的关联关系
目录 一、说明二、具体类型2.1 普通关联2.2 单向关联2.3 双向关联2.4 自关联2.4 聚合关系(Aggregation)2.5 组合关系(Composition) 三、关联关系中的多重性 一、说明 1.UML(Unified Modeling Language,统一…...
贪吃蛇超精讲(C语言)
前言 如果你还是个萌新小白,那么该项目的攻克过程一定会十分艰难。虽然作者已经将文章尽可能写的逻辑清晰,内容详细。但所谓“纸上得来终觉浅”,在讲到陌生结构和函数时,大家请一定自己动手去敲一遍代码,这很重要&…...
掌握Rust:函数、闭包与迭代器的综合运用
掌握Rust:函数、闭包与迭代器的综合运用 引言:解锁 Rust 高效编程的钥匙函数定义与模式匹配:构建逻辑的基石高阶函数与闭包:代码复用的艺术迭代器与 for 循环:高效数据处理的引擎综合应用案例:构建一个简易…...
【LeetCode】80.删除有序数组中的重复项II
1. 题目 2. 分析 3. 代码 class Solution:def removeDuplicates(self, nums: List[int]) -> int:if len(nums) < 3:return len(nums)i 0j 1k 2while(k < len(nums)):if (nums[i] nums[j]):while(k < len(nums) and nums[j] nums[k] ):k1if (k < len(nums…...
Armpro搭建教程全开源版的教程
Armpro搭建教程 全开源版的教程,其他未知 资源宝整理分享 www.httple.net 首先ssh执行指令安装运行环境 yum install java-1.8.0-openjdk* -y导入文件服务器 导入arm.zip到www目录下然后解压 导入jar包.zip到www目录然后解压 导入basic.zip到www目录然后解压在宝塔…...
全屋定制十大名牌排行2023/北京seo推广外包
在上一篇文章里,我介绍了如何对一个简单的Activity进行单元测试。(参见上一篇)我们为Activity提供了两个参数LastName和FirstName,Activity会根据这两个参数生成一个Email地址。在上一篇中,我们输入了两个“合法”的参…...
哪个网站可以上传设计的作品/重庆百度
Flutter webview插件是用来在APP内部加载网页的,它跟Flutter url_launcher插件的不同之处在于:前者只是在app内部打开web网页,而url_launcher则是调用手机默认的功能做事情,例如调用默认的浏览器打开web网页(跳出app了)࿰…...
星月教你做网站回顾文档/百度推广售后客服电话
***服务大家都知道,虚拟专用网络,今天我们就来说说怎么用linux搭建pptp *** 说实话,我这也算是第一次照着命令敲然后成功了的,最后才搞明白怎么回事,特贡献出来给大家,希望大家共同进步 系统环境: 2.6.18-9…...
注册网站会员需要详细/广东互联网网络营销推广
题目描述 暑假期间,小龙报名了一个模拟野外生存作战训练班来锻炼体魄,训练的第一个晚上,教官就给他们出了个难题。由于地上露营湿气重,必须选择在高处的树屋露营。小龙分配的树屋建立在一颗高度为N1尺(N为正整数&#…...
无法访问服务器上网站/互联网广告公司
本文主要讲诉在使用VS2012SQL Server数据库做系统中,通常会遇到几个问题.使用dataGridView控件在修改、删除、插入数据后,怎样刷新数据显示操作后的结果.同时在对数据操作时通常会判断数据的主键是否存在或重复,判断外键是否重复,这几个问题我推荐使用函数的形式完成,同时推荐一…...
外贸主动营销网站建设/app推广营销
/*** 1、除非元素为null,否则向集合添加元素*/ CollectionUtils.addIgnoreNull(personList,null); /*** 2、将两个已排序的集合a和b合并为一个已排序的列表,以便保留元素的自然顺序*/ CollectionUtils.collate(Iterable<? extends O> a, Iterable…...