网络编程(5)——模拟伪闭包实现连接的安全回收
六、day6
今天学习如何利用C11模拟伪闭包实现连接的安全回收,之前的异步服务器为echo模式,但存在安全隐患,在极端情况下客户端关闭可能会导致触发写和读回调函数,二者都进入错误处理逻辑,进而造成二次析构。今天学习如何通过C11智能指针构造伪闭包状态(将智能指针传给函数对象,如果函数对象不消失,该指针也不会消失),延长session的生命周期,不会发生二次析构的风险。
1)智能指针管理Server
通过智能指针的方式管理Session类,将acceptor接收的链接保存在Session类型的智能指针里。由于智能指针会在引用计数为0时自动析构,所以为了防止其被自动回收,也方便Server管理Session,因为我们后期会做一些重连踢人等业务逻辑,我们在Server类中添加成员变量,该变量为一个map类型,key为Session的uid,value为该Session的智能指针。
class Server
{
private:void start_accept(); // 启动一个acceptor// 当acceptor接收到连接后启动该函数void handle_accept(std::shared_ptr<Session> new_session, const boost::system::error_code& error);boost::asio::io_context& _ioc;tcp::acceptor _acceptor;std::map<std::string, std::shared_ptr<Session>> _sessions;
public:Server(boost::asio::io_context& ioc, short port);void ClearSession(std::string uuid);
};
通过Server中的_sessions这个map管理链接,可以增加Session智能指针的引用计数,只有当Session从这个map中移除后,Session才会被释放。所以在接收连接的逻辑里将Session放入map。
但这里产生了一个问题,为什么new_session在'}'结束后仍不结束,而是bind后计数加一?这个问题在文章后面回答。
void Server::start_accept() {// make_shared分配并构造一个 std::shared_ptr,_ioc, this是传给Session的参数std::shared_ptr<Session> new_session = std::make_shared<Session>(_ioc, this);// 开始一个异步接受操作,当new_session的socket与客户端连接成功时,调用回调函数handle_accept// 为什么new_session在右括号结束后仍不结束,而是bind后计数加一?_acceptor.async_accept(new_session->Socket(), std::bind(&Server::handle_accept, this, new_session,std::placeholders::_1));
}// 当handle_accept触发时,也就是start_accept的回调函数被触发,当该回调函数结束后从队列中移除后,new_session的引用计数减一
void Server::handle_accept(std::shared_ptr<Session> new_session, const boost::system::error_code& error) {// 如果没有错误(error 为 false),调用 new_session->Start() 来启动与旧客户端的会话if (!error) {new_session->Start();_sessions.insert(std::make_pair(new_session->GetUuid(), new_session));}else // 无论当前连接是否成功,都重新调用 start_accept(),以便服务器能够继续接受下一个新客户端的连接请求。// 服务器始终保持在监听状态,随时准备接受新连接start_accept();
}
StartAccept函数中虽然new_session是一个局部变量,但是通过bind操作,将new_session作为数值传递给bind函数,而bind函数返回的函数对象内部引用了该new_session所以引用计数增加1,这样保证了new_session不会被释放。在HandleAccept函数里调用session的start函数监听对端收发数据,并将session放入map中,保证session不被自动释放。
此外,需要封装一个释放函数,将session从map中移除,当其引用计数为0则自动释放
void Server::ClearSession(std::string uuid) {_sessions.erase(uuid);
}
2)Session的uuid
session的uuid可以通过boost提供的生成唯一id的函数获得,也可以自己实现雪花算法
void Session::HandleWrite(const boost::system::error_code& error) {if (!error) {std::lock_guard<std::mutex> lock(_send_lock);_send_que.pop();if (!_send_que.empty()) {auto &msgnode = _send_que.front();boost::asio::async_write(_socket, boost::asio::buffer(msgnode->_data, msgnode->_max_len),std::bind(&CSession::HandleWrite, this, std::placeholders::_1));}}else {std::cout << "handle write failed, error is " << error.what() << endl;_server->ClearSession(_uuid);}
}
void Session::HandleRead(const boost::system::error_code& error, size_t bytes_transferred){if (!error) {cout << "read data is " << _data << endl;//发送数据Send(_data, bytes_transferred);memset(_data, 0, MAX_LENGTH);_socket.async_read_some(boost::asio::buffer(_data, MAX_LENGTH), std::bind(&CSession::HandleRead, this, std::placeholders::_1, std::placeholders::_2));}else {std::cout << "handle read failed, error is " << error.what() << endl;_server->ClearSession(_uuid);}
}
但以上操作仍有造成二次析构的隐患:
正常情况下上述服务器运行不会出现问题,但是当我们像day5一样模拟,在服务器要发送数据前打个断点,此时关闭客户端,在服务器就会先触发写回调函数的错误处理,再触发读回调函数的错误处理,这样session就会两次从map中移除,因为map中key唯一,所以第二次map判断没有session的key就不做移除操作了。
但是这么做还是会有崩溃问题,因为第一次在session写回调函数中移除session,session的引用计数就为0了,调用了session的析构函数,这样在触发session读回调函数时此时session的内存已经被回收了自然会出现崩溃的问题。解决这个问题可以利用智能指针引用计数和bind的特性,实现一个伪闭包的机制延长session的生命周期。
3)构造伪闭包
思路:
- 利用智能指针被复制或使用引用计数加一的原理保证内存不被回收
- bind操作可以将值绑定在一个函数对象上生成新的函数对象,如果将智能指针作为参数绑定给函数对象,那么智能指针就以值的方式被新函数对象使用,那么智能指针的生命周期将和新生成的函数对象一致,从而达到延长生命的效果。
void headle_read(const boost::system::error_code& error, size_t bytes_transferred,std::shared_ptr<Session> _self_shared);void haddle_write(const boost::system::error_code& error, std::shared_ptr<Session> _self_shared);
以haddle_write举例,在bind时传递_self_shared指针增加其引用计数,这样_self_shared的生命周期就和async_write的第二个参数(也就是asio要求的回调函数对象headle_read)生命周期一致了。
void Session::haddle_write(const boost::system::error_code& error, std::shared_ptr<Session> _self_shared) {if (!error) {memset(_data, 0, max_length);_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::headle_read, this, std::placeholders::_1, std::placeholders::_2, _self_shared));}else {cout << "write error" << error.value() << endl;_server->ClearSession(_uuid);}
}
同理,headle_read内部也实现了类似的绑定
void Session::headle_read(const boost::system::error_code& error, size_t bytes_transferred,std::shared_ptr<Session> _self_shared) {if (!error) {cout << "server receive data is " << _data << endl;boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::haddle_write, this, std::placeholders::_1, _self_shared));}else {cout << "read error" << endl;_server->ClearSession(_uuid);}
}
除此之外,在第一次绑定读写回调函数的时候要传入智能指针的值,但是要注意传入的方式,不能用两个智能指针管理同一块内存,如下用法是错误的
void Session::Start() {memset(_data, 0, max_length); // 缓冲区清零// 从套接字中读取数据,并绑定回调函数headle_read_socket.async_read_some(boost::asio::buffer(_data, max_length),// 这里可以将shared_ptr<Session>(this)给bind绑定吗?// 不可以,会造成多个智能指针绑定同一块内存的问题std::bind(&Session::headle_read, this, std::placeholders::_1, std::placeholders::_2,shared_ptr<CSession>(this)));
}
shared_ptr<CSession>(this)生成的新智能指针和this之前绑定的智能指针new_session并不共享引用计数,所以要通过shared_from_this()函数返回智能指针,该智能指针和其他管理这块内存的智能指针共享引用计数。
void Session::Start() {memset(_data, 0, max_length); // 缓冲区清零// 从套接字中读取数据,并绑定回调函数headle_read_socket.async_read_some(boost::asio::buffer(_data, max_length),// 这里可以将shared_ptr<Session>(this)给bind绑定吗?// 不可以,会造成多个智能指针绑定同一块内存的问题std::bind(&Session::headle_read, this, std::placeholders::_1, std::placeholders::_2,shared_from_this()));
}
shared_from_this()函数并不是session的成员函数,要使用这个函数需要继承std::enable_shared_from_this<Session>
class Session:public std::enable_shared_from_this<Session>
{
private:tcp::socket _socket; // 处理客户端读写的套接字enum { max_length = 1024 };char _data[max_length]; // headle回调函数void headle_read(const boost::system::error_code& error, size_t bytes_transferred,std::shared_ptr<Session> _self_shared);void haddle_write(const boost::system::error_code& error, std::shared_ptr<Session> _self_shared);std::string _uuid;Server* _server;
public:Session(boost::asio::io_context& ioc, Server* server) : _socket(ioc), _server(server){// random_generator是函数对象,加()就是函数,再加一个()就是调用该函数boost::uuids::uuid a_uuid = boost::uuids::random_generator()();_uuid = boost::uuids::to_string(a_uuid);}tcp::socket& Socket() { return _socket; }const std::string& GetUuid() const { return _uuid; }void Start();};
再次测试,链接可以安全释放,并不存在二次释放的问题。可以在析构函数内打印析构的信息,发现只析构一次
1. 为什么new_session在'}'结束后仍不结束,而是bind后计数加一?
new_session通过bind绑定时,new_session的计数就会加一,所以在bind后,new_session的生命周期和新构造函数的生命周期相同,因为新生成的函数对象引用了new_session(new_session通过值传递的方式被复制构造函数使用)。所以只要新构造的bind回调函数没有被调用、移除,new_session的生命周期就始终存在,所以new_session不会随着'}'的结束而释放。
bind中的shared_from_this()通过自己生成一个与外界智能指针共享相同引用计数的指针,保证Cession不被释放或者异常释放,因为有可能回调函数还没有调用时,引用Session的应用计数便已经为0,此时如果回调函数被调用,就会发现Session已经被释放。所以为了防止Session被释放,需要生成一个智能指针使得Session的引用计数加一,并传给回调函数,这样能保证回调函数在没有调用之前,一直占用一个Session的引用计数,Session就不会被释放,就达成了一个延时的闭包效果。
总结:使用 std::bind 来绑定 new_session 作为回调参数时,引用计数也会加一,因为 std::bind 内部会将对象拷贝到新生成的函数对象中。这意味着,直到回调函数执行完毕且不再被引用时,new_session 的生命周期才会结束,所以在 } 结束后 new_session 不会被立即销毁。
2. 为什么session执行start函数启动异步操作后会立即将session插入至map种,而不是等异步操作执行完返回后才将session插入map?
new_session->Start();
_sessions.insert(std::make_pair(new_session->GetUuid(), new_session));
代码执行顺序:
1)调用 new_session->Start():
当 Start() 被调用时,它会启动一些异步操作(如异步读取),但是这些异步操作并不会阻塞当前的执行流。它们只是注册了一个回调函数,并告诉操作系统在相关事件发生(如数据到达、连接完成等)时触发回调。所以,new_session->Start() 启动异步读取操作async_read_some后,控制权立即返回到 handle_accept() 中,而不会等待异步操作完成。
2)插入 _sessions:
new_session->Start() 启动异步操作后,下一条语句 _sessions.insert(...) 会立即执行。由于 insert 操作是同步的,服务器将 new_session 立即插入到 _sessions 容器中,这确保 new_session 在异步操作执行期间不会被销毁。
综上,异步操作是非堵塞的,调用 new_session->Start() 后,程序并不会等待异步操作(如 async_read_some)完成。这些异步操作会在某个事件发生后(如客户端发送数据)触发注册的回调函数,但它们本身不会阻塞代码执行。
3.智能指针new_session的计数增减过程?
1)在 Server::start_accept() 中创建 new_session ,引用计数:1
std::shared_ptr<Session> new_session = std::make_shared<Session>(_ioc, this);
2)绑定到异步接受操作(同理,异步读、异步写都会绑定new_session,只有当异步读写结束之后,new_session的引用计数才会减一),引用计数:2
_acceptor.async_accept(new_session->Socket(), std::bind(&Server::handle_accept, this, new_session, std::placeholders::_1));
因为 std::bind 创建了一个闭包,它持有对 new_session 的副本,并将其存储起来,直到异步操作完成并调用回调函数 handle_accept
3)在 Server::handle_accept() 中
void Server::handle_accept(std::shared_ptr<Session> new_session, const boost::system::error_code& error) {if (!error) {new_session->Start(); // 启动会话_sessions.insert(std::make_pair(new_session->GetUuid(), new_session)); // 插入到_sessions中}start_accept(); // 继续等待新的客户端连接
}
- 当 handle_accept() 被调用时,new_session 被传递给该函数,作为一个局部的 std::shared_ptr。此时引用计数增加 1,变为 3。
- 调用 new_session->Start() 后,服务器将 new_session 插入到 _sessions 容器中,引用计数增加 1,变为 4。
- 此时,new_session 的引用计数如下:
- 异步操作 async_accept 中持有一个副本。
- 传递给 handle_accept 时有一个副本。
- 存储在 _sessions 容器中有一个副本。
4)调用 Session::Start() 并启动异步读取操作
void Session::Start() {_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::headle_read, this, std::placeholders::_1, std::placeholders::_2, shared_from_this()));
}
- 在 Start() 函数中,调用 async_read_some() 时,将 shared_from_this() 传递给回调函数 std::bind,创建了另一个 std::shared_ptr<Session>,引用计数增加 1,变为 5。
- shared_from_this() 确保了回调函数中使用的 Session 对象不会在异步操作完成前被销毁。因为在headle_read()和haddle_write()函数中的_self_shared参数是通过 shared_from_this() 获得的 std::shared_ptr<Session>,并通过 std::bind 传递到 headle_read 回调函数中,这里的new_session引用指针并不会增加,因为_self_shared始终都只是同一个,所以在异步操作完成前session都不会被销毁,因为引用指针是通过shared_from_this增加的,在异步完成前,引用不会减一。
5)异步读取完成后调用 headle_read()
void Session::headle_read(const boost::system::error_code& error, size_t bytes_transferred,std::shared_ptr<Session> _self_shared) {if (!error) {boost::asio::async_write(_socket, boost::asio::buffer(_data, bytes_transferred),std::bind(&Session::haddle_write, this, std::placeholders::_1, _self_shared));} else {_server->ClearSession(_uuid);}
}
引用计数:仍然是 5
在 headle_read 函数中,_self_shared 是 shared_from_this() 传递过来的 std::shared_ptr<Session>,此时 new_session 的引用计数保持不变。如果读取操作成功,继续绑定 async_write 操作,并传递 std::shared_ptr<Session> 给回调函数 haddle_write。如果发生错误,调用 _server->ClearSession(_uuid),从 _sessions 中删除 new_session,引用计数减少 1,变为 4。
6)异步写入完成后调用 haddle_write()
void Session::haddle_write(const boost::system::error_code& error, std::shared_ptr<Session> _self_shared) {if (!error) {_socket.async_read_some(boost::asio::buffer(_data, max_length),std::bind(&Session::headle_read, this, std::placeholders::_1, std::placeholders::_2, _self_shared));} else {_server->ClearSession(_uuid);}
}
引用计数:保持不变
- 写入操作完成后,再次启动新的异步读取操作,依然使用 _self_shared 传递给 async_read_some,保持 new_session 的引用计数不变。
- 如果写入操作失败,调用 _server->ClearSession(_uuid),从 _sessions 中移除 new_session,引用计数减少 1。
7)客户端断开连接或发生错误时
void Server::ClearSession(std::string uuid) {_sessions.erase(uuid);
}
引用计数减少:
- 当客户端断开连接或发生错误时,ClearSession() 被调用,从 _sessions 容器中删除 new_session。这导致引用计数减少 1。
- 此时,如果没有其他异步操作绑定 new_session,并且所有与 new_session 相关的回调函数都已经执行完毕,那么引用计数最终会减少到 0。
- 当所有的异步操作完成,new_session 不再被任何地方持有,引用计数归零,此时 std::shared_ptr 自动销毁 Session 对象,释放其占用的内存。
4.为什么“当 handle_accept() 被调用时,new_session 被传递给该函数,作为一个局部的 std::shared_ptr。此时引用计数增加 1,变为 3”但是“在 headle_read 函数中,_self_shared 是 shared_from_this() 传递过来的 std::shared_ptr<Session>,此时 new_session 的引用计数保持不变。”new_session 同样被传递给该函数,但为什么这里的计数不加一?
第一种情况:handle_accept 中传递 new_session
void Server::handle_accept(std::shared_ptr<Session> new_session, const boost::system::error_code& error) {if (!error) {new_session->Start(); // 启动会话_sessions.insert(std::make_pair(new_session->GetUuid(), new_session)); // 插入到_sessions中}start_accept(); // 继续等待新的客户端连接
}
- 传递过程:handle_accept 函数接收的是 std::shared_ptr<Session>,也就是说,在调用 handle_accept 时,会将 new_session 作为参数按值传递。
- 为什么计数增加?:在按值传递时,new_session 被拷贝了一份,这份拷贝的 std::shared_ptr 在函数内部存在,它持有同一个 Session 对象。这种拷贝操作会增加引用计数,因此当 handle_accept 被调用时,new_session 的引用计数增加 1。
- 总结:因为 new_session 是按值传递的,std::shared_ptr 的拷贝操作导致引用计数增加。
第二种情况:headle_read 中传递 _self_shared
void Session::headle_read(const boost::system::error_code& error, size_t bytes_transferred,std::shared_ptr<Session> _self_shared) {if (!error) {}
}
- 传递过程:_self_shared 是通过 shared_from_this() 获得的 std::shared_ptr<Session>,并通过 std::bind 传递到 headle_read 回调函数中。
- 为什么计数不增加?:这里的关键在于 _self_shared 并不是按值传递一个新的 std::shared_ptr,而是通过 std::bind 传递的已经存在的 std::shared_ptr(即 shared_from_this() 返回的那个 shared_ptr)。
- 传递的是同一个 std::shared_ptr 对象:由于 std::bind 是按引用或按值传递该 shared_ptr 对象(取决于如何绑定),回调函数接收到的是一个已经存在的 std::shared_ptr,而不是新拷贝的 std::shared_ptr。因此,这个传递操作并不会创建新的 shared_ptr 对象,因此不会增加引用计数。_self_shared 是通过 参数传递 进入 headle_read 函数的,而不是像 new_session 那样通过 std::bind 进行传递(按值传递)。因此,在这个函数的参数传递过程中,不再进一步增加引用计数。换句话说,_self_shared 的引用计数已经通过之前的 shared_from_this() 加一,所以这里不再加一。
- 总结:在 headle_read 中,传递的是 shared_from_this() 已经创建的 std::shared_ptr 的引用,传递过程中没有创建新的 shared_ptr 对象,因此引用计数保持不变
相关文章:
网络编程(5)——模拟伪闭包实现连接的安全回收
六、day6 今天学习如何利用C11模拟伪闭包实现连接的安全回收,之前的异步服务器为echo模式,但存在安全隐患,在极端情况下客户端关闭可能会导致触发写和读回调函数,二者都进入错误处理逻辑,进而造成二次析构。今天学习如…...
C#绘制动态曲线
前言 用于实时显示数据动态曲线,比如:SOC。 //用于绘制动态曲线,可置于定时函数中,定时更新数据曲线 void DrawSocGraph() {double f (double)MainForm.readData[12]; //display datachart1.Series[0].Points.Add(f);if (ch…...
用Python实现运筹学——Day 10: 线性规划的计算机求解
一、学习内容 1. 使用 Python 的 scipy.optimize.linprog 进行线性规划求解 scipy.optimize.linprog 是 Python 中用于求解线性规划问题的函数。它实现了单纯形法、内点法等算法,能够处理求解最大化或最小化问题,同时满足线性约束条件。 线性规划问题的…...
[C++]使用C++部署yolov11目标检测的tensorrt模型支持图片视频推理windows测试通过
官方框架: https://github.com/ultralytics/ultralytics yolov8官方最近推出yolov11框架,标志着目标检测又多了一个检测利器,于是尝试在windows下部署yolov11的tensorrt模型,并最终成功。 重要说明:安装环境视为最基…...
霍夫曼树及其与B树和决策树的异同
霍夫曼树是一种用于数据压缩的二叉树结构,通常应用于霍夫曼编码算法中。它的主要作用是通过对符号进行高效编码,减少数据的存储空间。霍夫曼树在压缩领域扮演着重要角色,与B树、决策树等数据结构都有一些相似之处,但又在应用场景和…...
CompletableFuture常用方法
一、获得结果和触发计算 1.获取结果 (1)public T get() public class CompletableFutureAPIDemo{public static void main(String[] args) throws ExecutionException, InterruptedException{CompletableFuture<String> completableFuture Com…...
本地化测试对游戏漏洞修复的影响
本地化测试在游戏开发的质量保证过程中起着至关重要的作用,尤其是在修复bug方面。当游戏为全球市场做准备时,它们通常会被翻译和改编成各种语言和文化背景。这种本地化带来了新的挑战,例如潜在的语言错误、文化误解,甚至是不同地区…...
使用rust实现rtsp码流截图
中文互联网上的rust示例程序源码还是太稀少,找资料很是麻烦,下面是自己用业余时间开发实现的一个对批量rtsp码流源进行关键帧截图并存盘的rust demo源码记录。 要编译这个源码需要先安装vcpkg,然后用vcpkg install ffmpeg安装最新版本的ffmpe…...
Cpp::STL—string类的模拟实现(12)
文章目录 前言一、string类各函数接口总览二、默认构造函数string(const char* str "");string(const string& str);传统拷贝写法现代拷贝写法 string& operator(const string& str);传统赋值构造现代赋值构造 ~string(); 三、迭代器相关函数begin &…...
一文搞懂SentencePiece的使用
目录 1. 什么是 SentencePiece?2. SentencePiece 基础概念2.1 SentencePiece 的工作原理2.2 SentencePiece 的优点 3. SentencePiece 的使用3.1 安装 SentencePiece3.2 训练模型与加载模型3.3 encode(高频)3.4 decode(高频&#x…...
一个简单的摄像头应用程序1
这个Python脚本实现了一个基于OpenCV的简单摄像头应用,我们在原有的基础上增加了录制视频等功能,用户可以通过该应用进行拍照、录制视频,并查看已拍摄的照片。以下是该脚本的主要功能和一些使用时需要注意的事项: 功能 拍照: 用户可以通过点击界面上的“拍照”按钮或按…...
通过PHP获取商品详情
在电子商务的浪潮中,数据的重要性不言而喻。商品详情信息对于电商运营者来说尤为宝贵。PHP,作为一种广泛应用的服务器端脚本语言,为我们提供了获取商品详情的便捷途径。 了解API接口文档 开放平台提供了详细的API接口文档。你需要熟悉商品详…...
【Android】获取备案所需的公钥以及签名MD5值
目录 重要前提 获取签名MD5值 获取公钥 重要前提 生成jks文件以及gradle配置应用该文件。具体步骤请参考我这篇文章:【Android】配置Gradle打包apk的环境_generate signed bundle or apk-CSDN博客 你只需要从头看到该文章的配置build.gradle(app&…...
看480p、720p、1080p、2k、4k、视频一般需要多大带宽呢?
看视频都喜欢看高清,那么一般来说看电影不卡顿需要多大带宽呢? 以4K为例,这里引用一位网友的回答:“视频分辨率4092*2160,每个像素用红蓝绿三个256色(8bit)的数据表示,视频帧数为60fps,那么一秒钟画面的数据量是:4096*2160*3*8*60≈11.9Gbps。此外声音大概是视频数据量…...
解决IDEA中@Autowired红色报错的实用指南:原因与解决方案
前言: 在使用Spring Boot开发时,Autowired注解是实现依赖注入的常用方式。然而,许多开发者在IDEA中使用Autowired时,可能会遇到红色报错,导致代码的可读性降低。本文将探讨导致这种现象的原因,并提供几种解…...
408知识点自检(一)
一、细节题 虚电路是面向连接的吗?虚电路线路上会不会有其他虚电路通过?虚电路适合什么类型的数据交换?虚电路的可靠性靠其他协议还是自己?固态硬盘的优势体现在什么存取方式?中断向量地址是谁的地址?多播…...
负载均衡--相关面试题(六)
在负载均衡的面试中,可能会遇到一系列涉及概念、原理、实践应用以及技术细节的问题。以下是一些常见的负载均衡面试题及其详细解答: 一、什么是负载均衡? 回答:负载均衡是一种将网络请求或数据传输工作分配给多个服务器或网络资源…...
【Unity踩坑】Unity更新Google Play结算库
一、问题描述: 在Google Play上提交了app bundle后,提示如下错误。 我使用的是Unity 2022.01.20f1,看来用的Play结算库版本是4.0 查了一下文档,Google Play结算库的维护周期是两年。现在需要更新到至少6.0。 二、更新过程 1. 下…...
Redis:hash类型
Redis:hash类型 hash命令设置与读取HSETHGETHMGET 哈希操作HEXISTSHDELHKEYSHVALSHGETALLHLENHSETNXHINCRBYHINCRBYFLOAT 内部编码ziplisthashtable 目前主流的编程语言中,几乎都提供了哈希表相关的容器,Redis自然也会支持对应的内容…...
力扣9.30
1749. 任意子数组和的绝对值的最大值 给你一个整数数组 nums 。一个子数组 [numsl, numsl1, ..., numsr-1, numsr] 的 和的绝对值 为 abs(numsl numsl1 ... numsr-1 numsr) 。 请你找出 nums 中 和的绝对值 最大的任意子数组(可能为空),…...
kafka下载配置
下载安装 参开kafka社区 zookeeperkafka消息队列群集部署https://apache.csdn.net/66c958fb10164416336632c3.html 下载 kafka_2.12-3.2.0安装包快速下载地址分享 官网下载链接地址: 官网下载地址:https://kafka.apache.org/downloads 官网呢下载慢…...
nlp任务之预测中间词-huggingface
目录 1.加载编码器 1.1编码试算 2.加载数据集 3.数据集处理 3.1 map映射:只对数据集中的sentence数据进行编码 3.2用filter()过滤 单词太少的句子过滤掉 3.3截断句子 4.创建数据加载器Dataloader 5. 下游任务模型 6.测试预测代码 7.训练代码 8.保…...
《程序猿之Redis缓存实战 · Redis 与数据库一致性》
📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…...
【无标题】observer: error while loading shared libraries: libmariadb.so.3处理办法
文章目录 1.记录新装的oceanbase,使用observer帮助时,出现lib文件无法找到的处理过程 ./observer --help ./observer: error while loading shared libraries: libmariadb.so.3: cannot open shared object file: No such file or directory2.做一个strace跟踪&…...
极客兔兔Gee-Cache Day1
极客兔兔7Days GeeCache - Day1 interface{}:任意类型 缓存击穿:一个高并发的请求查询一个缓存中不存在的数据项,因此这个请求穿透缓存直接到达后端数据库或数据源来获取数据。如果这种请求非常频繁,就会导致后端系统的负载突然…...
[MAUI]数据绑定和MVVM:MVVM的属性验证
一、MVVM的属性验证案例 Toolkit.Mvvm框架中的ObservableValidator类,提供了属性验证功能,可以使用我们熟悉的验证特性对属性的值进行验证,并将错误属性提取和反馈给UI层。以下案例实现对UI层的姓名和年龄两个输入框,进行表单提交验证。实现效果如下所示 View<ContentP…...
2024年水利水电安全员考试题库及答案
一、判断题 1.采用水下钻孔爆破方案时,侧面应采用预裂爆破,并严格控制单响药量以保护附近建(构)筑物的安全。 答案:正确 2.围堰爆破拆除工程的实施应成立爆破指挥机构,并应按设计确定的安全距离设置警戒。…...
【快速删除 node_modules 】rimraf
目录 1. 什么是node_modules 2. 卸载一个npm包 3. 删除 node_modules 为什么这么慢 4. rimraf 5. 为什么rimraf 这么快 作为前端开发,无论我们关注不关注,每天都能接触到node_modules。通常产生于一个npm install命令,之后就不会多加关注…...
毕业设计选题:基于ssm+vue+uniapp的教学辅助小程序
开发语言:Java框架:ssmuniappJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:M…...
13-指针和动态内存-内存泄漏
一、视频笔记: C语言通过malloc,来获取堆上的内存。 动态调用内存: malloc 和 free ;new 和 delete 都行。 内存泄漏指的是我们动态申请了内存,但是即是是使用完了之后(从来都不去释放它)。只…...
出台网站集约化建设通知/谷歌关键词推广怎么做
WCF IDisposable接口应用功能详解WCF客户端具体搭建方法解析WCF扩展点在自定义运行时使用两个基本WCF应用技巧总结引用WCF服务正确实现方法介绍 WCF实例上下文是对服务实例的封装,是WCF管理服务实例生命周期的依托。我们先撇开WCF,来简单介绍一下在托管的…...
重庆网站建设找承越/百度seo怎么做网站内容优化
并发 进程和线程的基本概念 并发: 两个或者多个任务(独立任务)同时发生(进行);一个程序同时执行多个任务; 在以往计算机中,单核CPU(中央处理器):在…...
做网站怎样实现网上支付/怎么做网页
01、PaaS的现状 对于像京东这样的商业服务数字化公司,因为业务应用不能直接建在云上,所以需要PaaS。 现在问题来了:PaaS应该怎样建立? 不同的目标,建法也不一样。有的是作为一个技术开发平台,以后再做项…...
商城网站离不开支付系统/西安网站推广排名
主机名与ip地址进行映射 /etc/hosts加入要映射的主机名和ip地址 192.168.1.100 主机名 转载于:https://www.cnblogs.com/lkldeblog/p/10956182.html...
公司网站宣传/搜索引擎优化的技巧有哪些
当我们写vue项目的时候,往往要先进行项目打包,然后再上传到我们的服务器,这样每次重复,感觉特别麻烦,这个时候我们就可以通过glup来帮我们解决问题 首先我们在项目新建一个gulpfile.js文件 然后安装两个东西 npm in…...
vultr建wordpress/抖音引流推广一个30元
创建设备对象 创建设备对象最简单的方式是使用 DirectSoundCreate8 函数。函数的第一个参数指定了与对象关联的设备的GUID。你能够通过枚举设备获取这个GUID,也可以传递以下GUID中的一个来指定一个默认设备。GUID 定义描述DSDEVID_DefaultPlayback默认系统音频设备。…...