Linux网络相关概念和重要知识(2)(UDP套接字编程、聊天室的实现、观察者模式)
目录
1.UDP套接字编程
(1)socket编程
(2)UDP的使用
①socket
②bind
③recvfrom
④sendto
2.聊天室的实现
(1)整体逻辑
(2)对sockaddr_in的封装
(3)客户端设计
①观察者模式
②User设计
③观察者的设计
⑤客户端主程序设计
(4)服务端设计
①服务端主程序的设计
②服务器端成员变量
③利用线程池实现服务端
(5)重定向文件
1.UDP套接字编程
(1)socket编程
套接字编程的种类比较多:网络socket、本地socket(如Unix域间socket)、原始socket。Linux提供了socket的系统调用,保证接口统一,以后实现Linux上的各种socket通信就很简单了。
(2)UDP的使用
使用socket套路非常简单,抓住IP和port,理解通信的大致过程就能很好掌握代码的方法了
①socket
站在用户层,创建socket就是创建了一个特殊的文件,这个文件包含了应该以什么方式,什么协议来通信。但此时,这个文件还应该有自己的网络层面的标识,如IP和port,这就是bind需要做的事了。
②bind
使用过程中,只需要创建对应通信方式的addr,写入对应的网络信息(如IP和port),最后再将addr转为父类指针,传入大小进行bind
总体bind流程:
向addr写入属性:
其中_net_addr是in类型的,注意字节序的调整,修改IP需要到sin_addr里面修改其成员s_addr
测试时常用的IP是127.0.0.1,表示本地机器。把这个IP绑定到socket之后,其它socket就可以通过这个IP向这个socket发消息。上述代码使用INADDR_ANY,意思是其它socket向任何IP发的消息都会被接收。
端口号是2字节16位的,端口号0 - 1023是知名端口号,1023 - 65535是OS动态分配的端口号,是客户端程序的端口号,我们随便选择,只要该端口号没有被使用。将端口号绑定后,对方socket向该socket发送消息,只有匹配IP和端口号才会被socket接收。
注意客户端不需要手动bind,因为客户端每次启动都不能保证相应端口号的空闲。而服务器端几乎不关机,可以指定而且必须指定。因此客户端创建了socket可以直接发消息,发消息时会系统自动bind,将相关IP和port写入socket中。
③recvfrom
没有收到消息时,线程会被阻塞在这个函数里面
④sendto
socket是全双工通信,既可以收的同时也可以发
2.聊天室的实现
(1)整体逻辑
整个聊天室重要的是逻辑,因为它的封装已经比较多了,所以接下来的实现会重点强调每个模块是如何设计,和其它模块耦合的。我们只需要详细理解前面UDP的通信过程即可理解聊天室的实现。
(2)对sockaddr_in的封装
我们可以快速创建管理sockaddr_in的结构体,并单独记录port和IP
服务器端可以快速初始化结构体
接收方也可以使用这个结构体,将接收到的in交给自定义结构体进行IP和port提取
全部代码:
#pragma once#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <stdint.h>
#include <strings.h>
using namespace std;class myInetAddr
{
private:void Set_Port() // 端口号的网络字节序转换为主机字节序{_port = ntohs(_net_addr.sin_port);}void Set_Ip() // IP的网络字节序转换为主机字节序{char ipbuffer[100] = {0};_ip = inet_ntop(AF_INET, &_net_addr.sin_addr, ipbuffer, sizeof(ipbuffer));// 相较于inet_ntoa,这个函数更安全,不会被覆盖}public:myInetAddr(const sockaddr_in &addr) : _net_addr(addr) // 传入一个sockaddr_in结构体,将其赋值给_net_addr{Set_Port(); // 调用Set_Port函数,将sockaddr_in结构体里面的端口号提取出来Set_Ip(); // 调用Set_Ip函数,将sockaddr_in结构体里面的IP地址提取出来}// 只需要传个端口号,就可以实现IP、端口号的自动初始化,其中IP默认为空,意味着局域网内任意IP都可以通信myInetAddr(uint16_t port, string ip = ""): _port(port), _ip(ip) // 传入端口号,IP默认为空,可自动初始化{// 初始化文件的属性bzero(&_net_addr, sizeof(_net_addr)); // 对_net_addr里面的空间清0,类似memset_net_addr.sin_family = AF_INET; // 网络通信,该属性必须要和socket的一致,后续才能绑定_net_addr.sin_port = htons(_port); // 写入端口号,htons保证字节序,主机字节序转网络字节序// sockaddr_in里面有struct in_addr sin_addr,这个结构体里面有s_addr,这个就是IP地址_net_addr.sin_addr.s_addr = INADDR_ANY; // 局域网内任意IP,只要端口号一致就能通信,也可以inet_addr(指定IP的c_str)}const sockaddr *Get_Const_Sockaddr_ptr(){return (const sockaddr *)&_net_addr;}socklen_t Get_Socklen(){return sizeof(_net_addr);}const string Get_Ip() const // 获取sockaddr_in结构体里面的IP地址,加const是为了保证不会修改_ip{return _ip;}const uint16_t Get_Port() const // 获取sockaddr_in结构体里面的端口号,加const是为了保证不会修改_ip{return _port;}private:// 使用sockaddr_in结构而不是sockaddrsockaddr_in _net_addr; // 管理属性的结构体,包括IP和端口号string _ip; // 单独记录IP地址uint16_t _port; // 单独记录端口号
};
(3)客户端设计
①观察者模式
在管理用户的UserManager中有一个链表,这个链表的实例化类型是BaseUser的指针,链表中的指针就是观察者,这些观察者可以通过多态实现管理User。当有什么操作需要完成时,会遍历链表中的指针,调用User里面的方法完成任务。整个过程中,UserManager都充当管理者、观察者,因此叫做观察者模式。
②User设计
这个User里面保存着用户的属性和方法,当服务器遍历list执行里面的方法时就会到这个类里面调用函数,每个观察者都知道观察对象的IP和port。
③观察者的设计
路由设计,服务器接收消息后就会在这里路由,遍历方法向别人发送信息。
注意这个sockfd是服务器的socket
全部代码
#pragma once#include <iostream>
#include <string>
#include <list>
#include <memory>
#include <algorithm>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "myLog.hpp"
#include "myMutex.hpp"
#include "myInetAddr.hpp"using namespace std;
using namespace myLogModule;
using namespace myMutexModule;class BaseUser // 多态,管理用户的基类
{
public:// 虚函数说明销毁的时候会调用子类的析构函数,default说明是默认的析构函数virtual ~BaseUser() = default;// 纯虚函数,第一个参数作用是socket的fd,发送消息时会根据fd发送消息和接收消息,顺便还会发送主机IP和端口号virtual void SendTo(int sockfd, const string &message) = 0;virtual bool CompareAddr(const myInetAddr &addr) = 0;
};class User : public BaseUser // 服务器用来保存用户的IP和端口号
{
public:User(const myInetAddr &addr): _addr(addr){}// 这个sockfd是服务器的socket,用于转发消息,_addr是接收转发消息的客户端的地址void SendTo(int sockfd, const string &message) override // override说明这个函数是重写的,用于规范化代码{LOG(DEBUG) << "服务器转发一条消息至" << _addr.Get_Ip() << ":" << _addr.Get_Port();// 将服务器收到的message发给客户端sendto(sockfd, message.c_str(), message.size(), 0, _addr.Get_Const_Sockaddr_ptr(), _addr.Get_Socklen());}bool CompareAddr(const myInetAddr &addr) override{return _addr.Get_Ip() == addr.Get_Ip() && _addr.Get_Port() == addr.Get_Port();}private:myInetAddr _addr;
};// 观察者,这里面会管理所有的用户
class UsersManager
{
public: // 添加和删除用户都只需要传入管理用户IP和端口号的结构体即可void AddUser(const myInetAddr &addr) // 只管添加用户,查重由该函数自己完成{myLockGuard lockguard(_mutex); // 一次性只允许一个进程访问公共资源,即用户链表for (auto &user : _online_users){if (user->CompareAddr(addr)){return;}}_online_users.push_back(make_shared<User>(addr)); // 多态,父类指针指向子类对象// 服务器新添加了一个用户,这个用户的管理的结构体是myInetAddr,但实际上是多态,所以可以调用子类的函数,这个结构体里面也有IP和端口号的信息LOG(INFO) << "新增用户:" << addr.Get_Ip() << ":" << to_string(addr.Get_Port());}void DeleteUser(const myInetAddr &addr){myLockGuard lockguard(_mutex); // 一次性只允许一个进程访问公共资源,即用户链表for (auto &user : _online_users){if (user->CompareAddr(addr)){_online_users.remove(user); // 这个函数会到链表里面找到这个user,然后删除,前面if已经找到了,所以这里不会有问题LOG(INFO) << addr.Get_Ip() << ":" << to_string(addr.Get_Port()) << "退出聊天室,服务器已删除用户";return; // 找到了就删除,不需要再继续找了}}}void Router(int sockfd, const string &message) // 路由函数,将服务器收到的消息转发给所有的用户{myLockGuard lockguard(_mutex); // 一次性只允许一个进程访问公共资源,即用户链表for (auto &user : _online_users) // 遍历所有用户{// 每一次获取数据都会调用一次这个函数,这个函数会将数据转发给所有的用户user->SendTo(sockfd, message); // 某一个用户发送过来信息,遍历所有用户,将信息转发给所有用户,这个socket是发信息的socket,这样其它用户才知道是他发的}}private:// 每个用户都是观察者list<shared_ptr<BaseUser>> _online_users; // 这是多态的体现,基类指针指向派生类对象,可以调用子类的函数myMutex _mutex; // 一把锁,保证线程安全
};
⑤客户端主程序设计
为了实现全双工,客户端主程序需要双线程执行,一端负责写消息,一端负责收信息(收不到信息时会一直阻塞在recvfrom中)
全部代码
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include "myCommon.hpp"using namespace std;int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// sockaddr_in写入要连接的服务器的IP和端口号
sockaddr_in server; // 可以向指定IP和端口号发送信息void ClientQuit(int signal)
{string message = "QUIT";sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr *)(&server), sizeof(server));cout << "你已退出聊天室" << endl;exit(0);
}void *ReceiveMessage(void *args)
{while (1){sockaddr_in temp;socklen_t len = sizeof(temp);char read_buffer[1024];int n = recvfrom(sockfd, read_buffer, sizeof(read_buffer) - 1, 0,(sockaddr *)(&temp), &len); // 接收对方服务器的IP和端口号read_buffer[n] = '\0';cerr << read_buffer << endl;// 输出接收到的信息,用错误流接收,后面专门用重定向设置一个聊天界面}
}// CS模式,client和server,client发送消息,server接收消息,服务器端永远不会主动发送消息,都是被动的
int main(int argc, char *argv[]) // 第二个参数是IP,第三个参数是端口号
{if (argc != 3){cerr << "三个参数,一个IP,一个端口号" << endl;exit(CLIENT_ERROR); // 2表示客户端错误}string server_ip = argv[1];uint16_t server_port = stoi(argv[2]);if (sockfd < 0){cerr << "客户端启动失败" << endl;exit(CLIENT_ERROR);}cout << "客户端启动成功" << endl;memset(&server, 0, sizeof(server)); // 用0初始化,0是char类型的0,即0x00server.sin_family = AF_INET;server.sin_port = htons(server_port); // 保证字节序server.sin_addr.s_addr = inet_addr(server_ip.c_str()); // 保证字节序signal(2, ClientQuit); // 捕获ctrl+c信号,退出程序,触发信号会执行ClientQuit函数,这个函数会向服务器发送QUIT信息,服务器会删除用户信息pthread_t tid;pthread_create(&tid, nullptr, ReceiveMessage, nullptr);while (true){cout << "请输入信息:";string message;getline(cin, message); // 获取信息// 不需要绑定socket,直接向文件写信息即可,client也有自己的属性,但IP和端口号不需要显式调用bind,客户端指定端口号可能会冲突,首次sendto会自动绑定// 客户端自动bind,一个端口号只能bind一次,一个进程可以绑定多个端口号sendto(sockfd, message.c_str(), message.size(), 0, // 根据打开的socket发送信息,向socket文件发送消息(const sockaddr *)(&server), sizeof(server)); // 要访问的服务端的IP和端口号// 发送信息的条件:向socket中写数据,socket和本机的IP和端口号绑定(显式绑定和自动绑定)// const sockaddr *指向目的的IP和端口号,发送出去后自己socket里面的IP和端口号也会发出去,对方就能找到你}return 0;
}
(4)服务端设计
①服务端主程序的设计
服务端最主要的就是利用回调函数将User的函数方法传过去,当要进行用户管理或者转发消息时,下层就能通过回调函数跑回顶层,通过这个唯一的um进行管理。
全部代码
#include "UdpServer.hpp"
#include "Users.hpp"int main()
{shared_ptr<UsersManager> um = make_shared<UsersManager>(); // 用户管理模块,服务器启动,这用来管理用户unique_ptr<UdpServer> server(make_unique<UdpServer>()); // 创建一个服务端对象server->RegisterService(//这是回调函数,服务端启动创建管理用户的对象,再用lambda将这个对象传入服务端,这样服务端就能回调上层的um的函数[&um](const myInetAddr &addr){um->AddUser(addr);},[&um](int sockfd, const string &message){um->Router(sockfd, message);},[&um](const myInetAddr &addr){um->DeleteUser(addr);});// 服务端启动,注册服务,这里注册了三个服务,分别是添加用户、路由、删除用户,完成RegisterService后,回调函数保证server里面访问的UsersManager的对象都是um的server->start(); // 启动服务端,之后会一直循环等待接收数据return 0;
}
②服务器端成员变量
三个函数指针是回调函数的,用于调用上层的对象的函数,转发消息
③利用线程池实现服务端
后面的代码逻辑都比较简单,就是让服务器陷入start()循环中,一直接收消息并通过回调函数转发消息。这个转发消息的任务被推到线程池中完成(线程池的实现不再展示)。唯一需要注意的是推送任务中要匹配任务参数,可以通过bind或者lambda调整,后面可以多注意一下。
全部代码
#pragma once#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdint.h>
#include <netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <functional>
#include "myLog.hpp"
#include "myInetAddr.hpp"
#include "myCommon.hpp"
#include "myThreadPool.hpp"using namespace std;
using namespace myLogModule; // 使用日志模块
using namespace myThreadPoolModule;const uint16_t default_port = 8888; // 默认端口号,无需指定IP
const int max_size = 1024; // 存储信息的最大字节数using adduser_t = function<void(const myInetAddr &addr)>;
using route_t = function<void(int sockfd, const string &message)>;
using delete_t = function<void(const myInetAddr &addr)>;class UdpServer
{
public:void RegisterService(adduser_t adduser, route_t route, delete_t del){_adduser = adduser;_route = route;_deluser = del;}UdpServer(uint16_t port = default_port): _socket_fd(-1), // 服务端的socket文件描述符_addr(port), // 服务端的端口号,默认为default_port,用于构造myInetAddr对象,自动初始化IP和端口号_isrunning(false) // 服务端的运行状态{// socket创建网络通信的文件_socket_fd = socket(AF_INET, SOCK_DGRAM, 0); // 网络通信,UDP通信方式,0默认if (_socket_fd == -1) // 创建失败返回-1{LOG(FATAL) << "服务端启动失败"; // 输出错误信息exit(SERVER_ERROR); // 直接退出程序,1表示服务端的异常退出}// 到这里创建了文件并写好了属性,接下来要bindif (bind(_socket_fd, _addr.Get_Const_Sockaddr_ptr(), _addr.Get_Socklen()) != 0) // 将属性和文件绑定,绑定成功返回0// 这个文件被绑定后,其它进程才能通过相应的IP和端口号找到这个文件{LOG(FATAL) << "绑定失败";exit(SERVER_ERROR); // 直接退出程序,1表示服务端的异常退出}// socket打开文件,bind将和文件通信的条件写入,至此其它进程才能找到这个进程LOG(INFO) << "服务端已绑定完毕,正在等待启动";}void start(){myThreadPool<task_t>::GetInstance()->StartAddTask(); // 启动线程池LOG(INFO) << "服务端(端口8888)启动成功";sockaddr_in client_addr; // sockaddr_in类型,这是用于获取第一次接收的用户端的数据所附带的IP、端口号信息socklen_t client_addr_len = sizeof(client_addr);_isrunning = true; // 启动服务器while (_isrunning) // 服务器运行时一直循环{bzero(_read_buffer, sizeof(_read_buffer)); // 对读写数组清0,数组名就是数组的首地址_write_buffer.clear(); // 对写数组清空// 会被阻塞在这个函数,直到收到数据,这个函数会将收到的数据写入_read_buffer,还会将客户端的IP和端口号信息写入client_addr和client_addr_lenssize_t n = recvfrom(_socket_fd, _read_buffer, sizeof(_read_buffer) - 1, 0, // 读到的数据存到_read_buffer里面(struct sockaddr *)&client_addr, &client_addr_len); // 读到属性并写入client_addr和client_addr_len// n不包含'\0',所以要手动加上if (n > 0){_read_buffer[n] = '\0'; // 读到的最后一个字符后面加上'\0',保证字符串安全// 处理收到的属性,client_addr是sockaddr_in类型,需要转换为myInetAddr类型,这样就可以有了客户端的myInetAddrmyInetAddr client_inet_addr(client_addr); // 直接将客户端的IP和端口号信息传入myInetAddr对象,自动初始化IP和端口号// 某一个用户开始向服务器发送数据,服务器开始接收数据,但先要添加用户_adduser(client_inet_addr); // 添加用户,调用回调函数,服务器的main函数定义的um里面的list添加用户,函数内部查重,这里不操作string _write_buffer = client_inet_addr.Get_Ip() + ":" + to_string(client_inet_addr.Get_Port()) + "发出消息:" + _read_buffer;if (strcmp(_read_buffer, "QUIT") == 0){// 移除该UsersManager里面保存的用户信息task_t task_deluser = bind(UdpServer::_deluser, client_addr);myThreadPool<task_t>::GetInstance()->AddTask(move(task_deluser));// 这里不能return,服务器一直在start函数里循环,return会直接退出函数//QUIT之后通知其它人_write_buffer = client_inet_addr.Get_Ip() + ":" + to_string(client_inet_addr.Get_Port()) + "已退出聊天室";}// 将回调函数_route绑定到新的task_t中,这个类型没有参数,将这个专门封装的类型推送到线程池中task_t execute_task = bind(UdpServer::_route, _socket_fd, _write_buffer);myThreadPool<task_t>::GetInstance()->AddTask(move(execute_task)); // 将这个任务推送到线程池中,这个线程池会执行um的函数}}}void stop(){_isrunning = false; // 服务器停止,自动根据while退出循环}~UdpServer(){if (_socket_fd != -1){close(_socket_fd);LOG(INFO) << "已关闭服务器端";}}private:int _socket_fd; // 调用socket之后创建文件后返回的文件fdbool _isrunning; // 记录当前服务端的运行状态myInetAddr _addr;string _write_buffer; // 服务器准备写出的信息char _read_buffer[max_size]; // 服务器读到的信息adduser_t _adduser; // 函数指针,用于添加用户delete_t _deluser; // 函数指针,用于删除用户route_t _route; // 函数指针,用于转发消息
};
(5)重定向文件
./test 1>test.txt可将输出重定向到文件中,标准输入、标准错误可以打印到不同文件中,方便我们进行debug,在这里我们也可以借此将聊天消息统一传到一个界面里面
注意:
1>log.txt 2>log.txt不可行,两个打开一个文件,其中一个流输出的内容会被清空,1>log.txt 2>>log.txt就可以了。
1>log.txt 2>&1也可以实现,把1的内容拷贝给2,让2也指向log.txt,这就意味着文件不是2打开的,只算做1打开文件,只是1单独将描述符私发给2,2不会清空文件
相关文章:
Linux网络相关概念和重要知识(2)(UDP套接字编程、聊天室的实现、观察者模式)
目录 1.UDP套接字编程 (1)socket编程 (2)UDP的使用 ①socket ②bind ③recvfrom ④sendto 2.聊天室的实现 (1)整体逻辑 (2)对sockaddr_in的封装 (3)…...
2025年03月18日柯莱特(外包宁德)一面前端面试
目录 自我介绍你怎么从0到1搭建项目的webpack 的构建流程手写webpack插件你有什么想问我的吗 2. 你怎么从 0 到 1 搭建项目的 在面试中回答从 0 到 1 搭建前端项目,可按以下详细步骤阐述: 1. 项目前期准备 需求理解与分析 和产品经理、客户等相关人…...
汇川EASY系列之以太网通讯(MODBUS_TCP做主站)
汇川Easy系列以太网通讯中(MODBUSTCP,plc做主站),终于可以不用使用指令就可以完成了,全程通过简单的配置就可通讯。本文将通过EASY系列PLC与调试助手之间完成此操作。具体演示如下; 关于主站和从站的介绍 A/请求:即主动方 向被动方发送的一个要求的信息。 B/主站:发…...
uni-app jyf-parser将字符串转化为html 和 rich-text
uni-app jyf-parser将字符串转化为html-CSDN博客 方法二: rich-text | uni-app...
PyTorch中Batch Normalization1d的实现与手动验证
PyTorch中Batch Normalization1d的实现与手动验证 一、介绍 Batch Normalization(批归一化)是深度学习中常用的技术,用于加速训练并减少对初始化的敏感性。本文将通过PyTorch内置函数和手动实现两种方式,展示如何对三维输入张量…...
LoRA中黑塞矩阵、Fisher信息矩阵是什么
LoRA中黑塞矩阵、Fisher信息矩阵是什么 1. 三者的核心概念 黑塞矩阵(Hessian) 二阶导数矩阵,用于优化问题中判断函数的凸性(如牛顿法),或计算参数更新方向(如拟牛顿法)。 Fisher信息矩阵(Fisher Information Matrix, FIM) 统计学中衡量参数估计的不确定性,反映数据…...
Transformer中,Fisher矩阵与权重之间关系
Transformer中,Fisher矩阵与权重之间关系 目录 Transformer中,Fisher矩阵与权重之间关系一、Fisher矩阵与Transformer权重的关系二、Fisher矩阵的更新方式三、举例说明给出案例:Transformer权重矩阵,Fisher矩阵,说明对应关系和怎么更新二阶导数计算需要大量算力有什么方法…...
本地安装deepseek大模型,并使用 python 调用
首先进入 ollama 官网 https://ollama.com/点击下载 下载完成后所有都是下一步,就可以 点击搜索 Models : https://ollama.com/search然后点击下载: 选择后复制: ollama run deepseek-r1:32b例如: 让它安装完成后࿱…...
asp.net mvc 向前端响应json数据。用到jquery
最近在给客户开发提醒软件时,用asp.net mvc 开发。该框架已经集成了bootstrap,直接贴asp.net mvc 端代码: {Layout null; }<!DOCTYPE html><html> <head><meta name"viewport" content"widthdevice-width" /…...
《基于深度学习的指纹识别智能门禁系统》开题报告
个人主页:大数据蟒行探索者 1研究背景 1.1开发目的和意义 指纹识别作为生物特征识别领域的一项重要技术,在安全认证、犯罪侦查和个人身份验证等方面具有广泛应用前景。随着深度学习技术的迅猛发展,基于深度学习的指纹识别系统成为了当前研究…...
数据预处理流程与关键步骤解析
数据预处理流程图(Markdown格式): #mermaid-svg-b3mhJcpFWaJ9qMZ8 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-b3mhJcpFWaJ9qMZ8 .error-icon{fill:#552222;}#mermaid-svg-b3m…...
单片机开发资源分析实战——以STM32F103ZET6为例子,分析我们的单片机
目录 第一件事情:为什么叫STM32F103ZET6 分析我们的资源手册 第二件事情,关心我们的GPIO引脚输出 第三件事情:去找对应外设的说明部分 本文章隶属于项目: Charliechen114514/BetterATK: This is a repo that helps rewrite ST…...
uv命令介绍(高性能Python包管理工具,旨在替代pip、pip-tools和virtualenv等传统工具)
文章目录 **主要功能**1. **快速安装和管理 Python 包**2. **生成和管理锁文件 (requirements.lock)**3. **创建虚拟环境**4. **与 poetry 兼容** **核心优势**1. **极快的速度**:基于 Rust 实现,利用多线程和缓存大幅加速依赖解析。2. **轻量且独立**&a…...
React Native进阶(六十):webview实现屏蔽所嵌套web页面异常弹窗
文章目录 一、前言二、解决方案三、注意事项四、拓展阅读 一、前言 在React Native项目集成web页面时,webview嵌套方式是常用方式。如果所嵌套的web页面由于某种不可控因素导致出现错误弹窗信息,webview作为web嵌套方式应该对其行为可控。 React Nativ…...
IS-IS原理与配置
一、IS-IS概述 IS-IS(Intermediate System to Intermediate System,中间系统到中间系统)是ISO(International Organization for Standardization,国际标准化组织)为它的CLNP(ConnectionLessNet…...
响应式CMS架构优化SEO与用户体验
内容概要 在数字化内容生态中,响应式CMS架构已成为平衡搜索引擎可见性与终端用户体验的核心载体。该系统通过多终端适配技术,确保PC、移动端及平板等设备的内容渲染一致性,直接降低页面跳出率并延长用户停留时长。与此同时,智能S…...
mysql 主从配置
核心概念: 主服务器(Master):负责处理客户端的写操作,并将数据更改记录到二进制日志(binlog)中。从服务器(Slave):负责读取主服务器的二进制日志,…...
Windows下编译安装Qt5.15.0指南
1. 系统要求 操作系统: Windows 7 或更高版本(推荐 Windows 10/11)。 磁盘空间: 至少 20-30 GB(编译过程会生成大量中间文件)。 内存: 建议 8GB 或更高。 编译器: 需要安装 Visual Studio(推荐 MSVC 2017 或 MSVC 2…...
001-JMeter的安装与配置
1.前期准备 下载好JMeter : https://jmeter.apache.org/download_jmeter.cgi 下载好JDK : :Java Downloads | Oracle 中国 下载图中圈蓝的JMeter和JDK就行,让它边下载,我们边往下看 2.为什么要下载并安装JDK ? JMeter 是基于 Java 开发的工具&#…...
kube-score K8S Yaml静态代码分析工具详解
kube-score 是一款专注于 Kubernetes 配置文件的静态代码分析工具,旨在通过自动化检查帮助用户识别资源配置中的潜在问题,并遵循最佳实践以提升集群的安全性、稳定性和性能。以下是其核心功能、使用方法和应用场景的详细解析: 一、核心功能与…...
【Go】Go语言结构体笔记
整体介绍 虽然 Go 语言不是传统意义上的面向对象语言,但它提供了结构体(struct)来组织数据,并且可以为结构体绑定方法,从而达到面向对象的部分效果。 关键知识点包括: 结构体定义与实例化 定义结构体时使用…...
Oracle数据库性能优化全攻略:十大关键方向深度解析与实践指南
文章目录 一、SQL查询优化二、索引优化三、内存管理四、I/O优化五、分区表与分区索引六、并行处理七、统计信息管理八、锁与并发控制九、数据库参数调优十、应用设计优化结论 在当今数据驱动的时代,数据库的性能优化成为了确保企业应用高效运行的关键。Oracle作为业…...
Modern C++面试题及参考答案
目录 解释右值引用的定义及其与左值引用的核心区别 std::move 的实现原理是什么?为什么它本身不执行移动操作? 移动构造函数与拷贝构造函数的调用场景有何不同? 实现一个支持移动语义的类需要遵循哪些原则? 完美转发(Perfect Forwarding)的实现原理及 std::forward 的…...
【单片机通信技术应用——学习笔记三】液晶屏显示技术,取模软件的应用
一、液晶显示技术简介 1.RGB信号线 RGB是一种色彩模式,是工业界的一种颜色标准,是通过红(R)、绿(G)、蓝(B)三个颜色通道的变化,以及它们相互之间的叠加来得到各式各样的…...
Git push后撤销提交
一、介绍 当某次更改完工程后,push了本地仓库到云端,但是发现有地方改错了,想撤销这次推送,或者某次提交就更改了很小一部分,想和本地这次修改的合并为一次推送,省的在云端显示特别多次提交,显得…...
React多层级对象改变值--immer
reduxjs/toolkit底层就是immer,,,所以在使用redux的时候,直接赋值,就会响应式的数据 如果不使用reduxjs/toolkit,可以自己使用immer来实现 安装immer npm install immer引入produce函数,,prod…...
17153. 班级活动(蓝桥杯-python)
代码写法 import os import sys# 请在此输入您的代码n int(input()) a list(map(int,input().split()))da {} flag1 0 flag2 0for i in a:da[i] da.get(i,0)1for i,j in da.items():if j 1:flag1 1if j > 2:flag2 j-2if flag2 > flag1:print(flag2) else:print(…...
html5-qrcode前端打开摄像头扫描二维码功能
实现的效果如图所示,全屏打开并且扫描到二维码后弹窗提醒,主要就是使用html5-qrcode这个依赖库,html5-qrcode开源地址:GitHub - mebjas/html5-qrcode: A cross platform HTML5 QR code reader. See end to end implementation at:…...
【后端】【Django】【ORM】SearchFilter 详解
SearchFilter 详解 SearchFilter 是 Django REST Framework(DRF)提供的一个过滤器,用于在 ModelViewSet 视图集中支持搜索功能。它允许用户通过 URL 查询参数(默认 search)对多个字段进行模糊匹配。 一、基本用法 &a…...
python 实现一个简单的window 任务管理器
import tkinter as tk from tkinter import ttk import psutil# 运行此代码前,请确保已经安装了 psutil 库,可以使用 pip install psutil 进行安装。 # 由于获取进程信息可能会受到权限限制,某些进程的信息可能无法获取,代码中已经…...
使用密码连接Redis服务的两种方式
说明:本文介绍连接需要密码的Redis服务的两种方式 方式一 连接时,携带密码,如下: redis-cli -a [密码]如下: 有两个问题: 密码直接放在命令里,可通过 history 找到,不安全&#x…...
基于C语言实现的观察者模式 以温度监控系统为例
场景为 温度监控系统:当温度传感器检测到温度变化时,自动通知所有注册的显示器(如LCD、手机App)更新显示。 场景描述 主题(Subject):温度传感器,负责检测温度变化并通知观察者。 观察者(Observer):显示器(LCD显示器、手机App),订阅温度数据并在温度变化时更新显…...
英伟达黄仁勋2025GTC演讲深度解析:液冷GPU、AI工厂、机器人AI…...
目录 一、技术产品与架构升级:从芯片到算力工厂1. 新一代GPU与计算架构2. AI工厂与算力操作系统 二、AI技术演进:从生成式到物理AI1. AI发展的三大阶段2. 推理算力需求爆炸式增长 三、生态合作与行业落地1. CUDA生态与开源工具2. 跨行业合作案例 四、未来…...
学习Flutter:搭建第一个 Flutter 应用
引言 曾几何时,我们还在为 Android 和 iOS 各写一套 UI 而头疼,做一个需求像是两家公司在竞争。但 Flutter 的出现,改变了这一切。它让跨平台开发变得可能,让一个程序员的代码可以同时运行在多个设备上,省时省力&…...
lua实现面向对象(封装/继承/多态)
lua实现面向对象封装/继承/多态 lua实现面向对象(封装/继承/多态) lua实现面向对象(封装/继承/多态) print("***********面向对象**********") print("*************封装************") --表就是表现类的一种形式 --实现了new方法:本质上是创建一个空表&a…...
WX小程序
下载 package com.sky.utils;import com.alibaba.fastjson.JSONObject; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.Cl…...
【模拟面试】计算机考研复试集训(第十一天)
文章目录 前言一、专业面试1、什么是面向对象编程?2、软件工程的主要模型有哪些?3、Cache和寄存器的区别4、卷积层有哪些参数,它们代表什么?5、你有读博的打算吗?6、你的师兄/姐临近毕业,仍做不出成果&…...
【深度技术揭秘】 Android SystemUI锁屏界面动态布局重构:横竖屏智能适配指南
1. 问题背景与需求拆解 在Android 13系统定制中,发现平板横屏锁屏界面存在两大视觉问题: 时钟控件尺寸过大,与竖屏样式不统一 解锁图标位置异常,横向居中而非顶部居中(如图示) 需实现: 横竖屏…...
AI比人脑更强,因为被植入思维模型【20】卡尼曼双系统理论
定义 卡尼曼双系统理论思维模型是由诺贝尔经济学奖得主丹尼尔卡尼曼提出的,该理论认为人类的思维系统可以分为两个相互关联但又具有不同特点的子系统,即系统1(快思考)和系统2(慢思考)。系统1是基于直觉、经…...
修改服务器windows远程桌面默认端口号
修改服务器windows远程桌面默认端口号 在Windows服务器上修改远程桌面协议(RDP)的默认端口(3389)可以增强服务器的安全性,减少被恶意扫描和攻击的风险。以下是修改远程端口的详细步骤: 按 Win R 打开运行…...
3.22模拟面试
前端模拟面试(1 年经验) 面试时长:40-60 分钟 面试难度:初中级 技术栈:Vue 3、TypeScript、微前端(qiankun)、Webpack/Rspack、Ant Design、组件库迁移 一、基础知识 HTML & CSS 介绍一下…...
MySQL高频八股——索引
大家好,我是钢板兽! 今天来更新MySQL高频八股的最后一篇文章,包括很多内容:索引分类、最左匹配原则、范围查询使联合索引失效、索引下推、给联合查询加索引、索引失效情况。 在MySQL的第一篇八股文章,我写了MySQL的索…...
二分查找(java)
文章目录 1. 基本原理2. 步骤3.练习 1. 基本原理 二分查找(Binary Search)是一种基于分治思想的高效搜索算法,核心逻辑是通过不断缩小搜索区间来定位目标值。其前提是数据必须为有序数组,时间复杂度为 O(log n)。 2. 步骤 1.…...
2025_0321_生活记录
刚刚写完待会儿早上要汇报的文档,看了一眼时间,现在已经是凌晨2点多了。一直说要早睡,但是一直都没做到。。。算了,不苛求自己了。 昨天是春分,春分秋分,昼夜平分。不知不觉就到春天了,但房间里…...
【LangChain入门 6 Chain组件】单链和多链
一、单链 1.1 LCEL的语法 | 为关键字,使用 | 作为链接符号 from langchain_core.output_parsers import StrOutputParser from langchain_ollama import ChatOllama llm ChatOllama( model"deepseek-r1:7b") parser StrOutputParser() # 加了这段后&…...
决策树基础
决策树 定义 从根节点开始,也就是拥有全部的数据,找一个维度对根节点开始划分, 划分后希望数据整体的信息熵是最小的, 针对划分出来的两个节点,我们继续重复刚才的划分方式寻找信息熵最小的维度和阈值。 递归这个…...
MATLAB+Arduino控制小车直行+转向
1 硬件 两轮车arduino板子 硬件连接 注意:电机连线。 这个小车的电机电流小,可以用arduino板子直接驱动,如果是大电流的,需要你自带电池,供电用用你的电池(如移动电源),控制信号&…...
【uni-app】引用公共组件
目录 一、建立公共组件 1.1新建vue文件 1.2编写公共文件代码 1.3使用 注意事项 一、建立公共组件 1.1新建vue文件 在公共组件文件目录下新建所需要的功能文件 1.2编写公共文件代码 按需求写对应功能的代码 1.3使用 在需要使用的文件下引用公共组件 注意事项 想要使用s…...
六西格玛遇上Python:统计学的高效实践场
在当今数据驱动的时代,数据分析和可视化工具成为了各行业优化流程、提升质量的关键手段。六西格玛(Six Sigma)作为一种以数据为基础、追求完美质量的管理理念,其实施依赖于一系列基础工具的灵活运用。而Python,凭借其强…...
虚幻基础:UI
文章目录 控件蓝图可以装载其他控件蓝图可以安装其他蓝图接口 填充:相对于父组件填充水平框尺寸—填充—0.5:改变填充的尺寸填充—0.5:改变与父组件的距离 锚点:相对于父组件的控件坐标系原点,屏幕比例改变时ÿ…...