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.xdnf.cn/news/1483477.html

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

相关文章

迁移学习在乳腺浸润性导管癌病理图像分类中的应用

1. 引言 乳腺癌主要有两种类型:原位癌:原位癌是非常早期的癌症&#xff0c;开始在乳管中扩散&#xff0c;但没有扩散到乳房组织的其他部分。这也称为导管原位癌(DCIS)。浸润性乳腺癌:浸润性乳腺癌已经扩散(侵入)到周围的乳腺组织。侵袭性癌症比原位癌更难治愈。将乳汁输送到乳…

2024717-VSCode-1.19.1-部署gcc13-C++23-win10-22h2

2024717-VSCode-1.19.1-部署gcc13-C++23-win10-22h2 一、软件环境 标签:C++ VSCode mingw gcc13分栏:C++操作系统:Windows10 x64 22h2二、操作步骤 1. 下载安装VScode 1.1官网 打开官网【https://code.visualstudio.com/Download】,选择【System Installer】【x64】,按…

Java面试八股之什么是Redis的缓存更新

什么是Redis的缓存更新 Redis的缓存更新是指当缓存中的数据发生变化时&#xff0c;需要将这些变化同步到缓存中以保持数据的一致性。缓存更新的目的是确保缓存中的数据始终是最新的&#xff0c;以便用户可以获取到最新的数据。 常见的缓存更新策略包括&#xff1a; 直接覆盖…

AWS基础知识

VPC (Virtual Private Cloud): 参考&#xff1a;https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html With Amazon Virtual Private Cloud (Amazon VPC), you can launch AWS resources in a logically isolated virtual network that you’ve defined…

昇思25天学习打卡营第30天 | MindNLP ChatGLM-6B StreamChat

今天是第30天&#xff0c;学习了MindNLP ChatGLM-6B StreamChat。 今天是参加打卡活动的最后一天&#xff0c;经过这些日子的测试&#xff0c;昇思MindSpore效果还是不错的。 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;具有62亿参数&#xff0c;基于 …

PyTorch 深度学习实践-卷积神经网络高级篇

视频指路 参考博客笔记 参考笔记二 文章目录 上课笔记10.1GoogleNet&#xff08;Inception 层&#xff09;代码实现10.2 Residual Net代码实现 上课笔记 可以设置padding‘same’ 使输入输出大小一致 10.1GoogleNet&#xff08;Inception 层&#xff09; 说明&#xff1a;In…

【Node.js】初识 Node.js

Node.js 概念 Node.js 是一个开源与跨平台的 JavaScript运行时环境 &#xff0c;在浏览器外运行 V8 JavaScript 引擎(Google Chrome的内核)&#xff0c;利用事件驱动、非阻塞和异步输入输出 等技术提高性能。 可以理解为 Node.js就是一个服务器端的、非阻塞式 l/O 的、事件驱…

Mac 安装MySQL 配置环境变量 修改密码

文章目录 1 下载与安装2 配置环境变量3 数据库常用命令3.1 Mac使用设置管理mysql服务启停 4 数据库修改root密码4.1 知道当前密码4.2 忘记当前密码4.3 问题 参考 1 下载与安装 官网&#xff1a;https://www.mysql.com/ 找到开源下载方式 下载社区版 2 配置环境变量 对于Mac…

NVIDIA Container Toolkit 安装与配置帮助文档(Ubuntu,Docker)

NVIDIA Container Toolkit 安装与配置帮助文档(Ubuntu,Docker) 本文档详细介绍了在 Ubuntu Server 22.04 上使用 Docker 安装和配置 NVIDIA Container Toolkit 的过程。 概述 NVIDIA 容器工具包使用户能够构建和运行 GPU 加速容器。即可以在容器中使用NVIDIA显卡。 架构图如…

观测云对接 Fluentd 采集业务日志最佳实践

概述 Fluentd 是一个开源数据收集器&#xff0c;专为简化日志管理和使日志数据更加易于访问、使用而设计。作为一个高度可扩展的工具&#xff0c;它能够统一数据收集和消费过程&#xff0c;使得构建实时分析的日志系统变得更加高效。 观测云目前已集成 Fluentd &#xff0c;可…

milvus的collection操作

milvus的collection操作 创建collection import uuidfrom pymilvus import (connections,FieldSchema, CollectionSchema, DataType,Collection, )collection_name "hello_milvus" host "192.168.230.71" port 19530 username "" password…

VSCode中通过launch.json文件打断点DeBug调试代码(详细图文教程)

先吐槽 IDE编译工具调试代码是非常重要的&#xff0c;之前使用Pycharm很方便&#xff0c;直接在Configuration中配置参数就行&#xff0c;见下。使用VSCode进行有命令代码调试时相对麻烦一些&#xff0c;看其它教程没撤清楚&#xff0c;这里做个总结&#xff0c;学者耐心学习。…

01 MySQL

学习资料&#xff1a;B站视频-黑马程序员JavaWeb基础教程 文章目录 JavaWeb整体介绍 MySQL1、数据库相关概念2、MySQL3、SQL概述4、DDL:数据库操作5、DDL:表操作6、DML7、DQL8、约束9、数据库设计10、多表查询11、事务 JavaWeb整体介绍 JavaWeb Web&#xff1a;全球广域网&…

网络准入控制设备是什么?有哪些?网络准入设备臻品优选

小李&#xff1a;“小张&#xff0c;最近公司网络频繁遭遇外部攻击&#xff0c;我们得加强一下网络安全了。” 小张&#xff1a;“是啊&#xff0c;我听说实施网络准入控制是个不错的选择。但具体什么是网络准入控制设备&#xff1f;我们有哪些选择呢&#xff1f;” 小李微笑…

基于 MelosBoom ,捕获 DePIN 赛道发展红利

Melos是一个Web3音乐领域的先驱性生态&#xff0c;其允许任何人通过其工具创作音乐&#xff0c;生成的内容可以保存为NFT并进入流通&#xff0c;同时支持该音乐资产支持开放再创作。最为最具影响力以及发展潜力的Web3音乐生态&#xff0c;其不仅获得了来自于头部VC Binance Lab…

分布式缓存-Redis持久化

使用缓存的时候&#xff0c;我们经常需要对内存中的数据进行持久化&#xff08;将内存中的数据写入到硬盘中&#xff09;。 原因&#xff1a;重用数据&#xff08;比如重启机器、机器故障之后恢复数据&#xff09;&#xff0c;做数据同步&#xff08;比如 Redis 集群的主从节点…

零基础STM32单片机编程入门(十五) DHT11温湿度传感器模块实战含源码

文章目录 一.概要二.DHT11主要性能参数三.DHT11温度传感器内部框图四.DTH11模块原理图五.DHT11模块跟单片机板子接线和通讯时序1.单片机跟DHT11模块连接示意图2.单片机跟DHT11模块通讯流程与时序 六.STM32单片机DHT11温度传感器实验七.CubeMX工程源代码下载八.小结 一.概要 DH…

超声波清洗机买哪款?口碑炸裂的眼镜清洗机推荐

各位时尚潮人们、眼镜党们注意啦&#xff01;现在超声波清洗机火得一塌糊涂&#xff0c;各种品牌各种款式功能五花八门&#xff0c;但你知道吗&#xff1f;不是每一款都能名副其实&#xff0c;特别是那些便宜货&#xff0c;宣传得天花乱坠&#xff0c;拆开却发现内部只有一个破…

MySQL运维实战之Clone插件(10.2)Clone插件原理

作者&#xff1a;俊达 clone插件实现 clone操作主要分为几个阶段&#xff1a; 1、初始阶段。 初始阶段&#xff0c;会开启页面跟踪&#xff08;Page Tracking&#xff09;。开启页面跟踪后&#xff0c;修改过的页面的编号会被记录下来。页面的修改可分为两个阶段&#xff1a…

【Unity学习笔记】第二十 · 物理引擎脉络梳理(数值积分、碰撞检测、约束解决)

转载请注明出处: https://blog.csdn.net/weixin_44013533/article/details/139808452 作者&#xff1a;CSDN|Ringleader| 物理引擎综述 物理引擎是利用物理规则模拟物体运动和碰撞的模块&#xff0c;以在重力、弹力、摩擦力等各种力作用下做出真实运动表现&#xff0c;并对碰…