南昌市建设监督网站站长/舆情优化公司
文章目录
- 1、简介
- 2、异步写 void AsyncWriteSomeToSocketErr(const std::string& buffer)
- 3、异步写void AsyncWriteSomeToSocket(const std::string& buffer)
- 4、异步写void AsyncSendToSocket(const std::string& buffer)
- 5、异步读void AsyncReadSomeToSocket(const std::string& buffer)
- 6、异步读void AsyncReceiveToSocket(const std::string& buffer)
- 7、总结
1、简介
本文介绍boost asio的异步读写操作及注意事项,为保证知识便于读者吸收,仅介绍api使用的代码片段。下一节再编写完整的客户端和服务器程序。
所以我们定义一个session类,这个session类表示服务器处理客户端连接的管理类
#pragma once
#ifndef __ASYNC_DEMO_H_2023_8_22__
#define __ASYNC_DEMO_H_2023_8_22__
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;};#endif // !__ASYNC_DEMO_H_2023_8_22__
session类定义了一个socket成员变量,负责处理对端**(ip+端口)的连接读写,封装了Connect**函数:
#include"async_demo.h"Session::Session(std::shared_ptr<boost::asio::ip::tcp::socket> socket):socket_(socket)
{send_buffer_ = nullptr;
}bool Session::Connect(boost::asio::ip::tcp::endpoint& ep) {socket_->connect(ep);return true;
}
这里只是简单意思一下,下面核心介绍异步读写api的使用。
2、异步写 void AsyncWriteSomeToSocketErr(const std::string& buffer)
在写操作前,我们先封装一个Buffer结构。用来管理要发送和接收的数据,该结构包含数据域首地址,数据的总长度,以及已经处理的长度(已读的长度或者已写的长度)
写了两个构造函数,两个参数的负责构造写节点,一个参数的负责构造读节点。
#pragma once
#include<iostream>//trv
const int RECVSIZE = 1024;
class Buffer {
public://发送消息协议//param 协议首地址,协议总长度Buffer(const char* msg,int32_t total_len):msg_(new char[total_len]),total_len_(total_len),cur_len_(0){memcpy(msg_, msg, total_len);}//接收消息协议//param 协议总长度,当前接收协议长度Buffer(int32_t total_len):total_len_(total_len),cur_len_(0){msg_ = new char[total_len];}~Buffer() {delete[] msg_;}char* GetMsg() {return msg_;}int32_t GetTotalLen() {return total_len_;}void SetTotalLen(int32_t total_len) {total_len_ = total_len;}int32_t GetCurLen() {return cur_len_;}void SetCurLen(int32_t cur_len) {cur_len_ = cur_len;}
private://消息协议的首地址char* msg_;//消息协议的总长度int32_t total_len_;//消息协议的当前发送长度 +上已经发送长度 = total_len (已经处理的长度(已读的长度或者已写的长度))int32_t cur_len_;
};
接下来为Session添加异步写发送数据操作和负责发送写数据的节点。
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);//异步写 这个异步写存在问题void AsyncWriteSomeToSocketErr(const std::string& buffer);void AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer);
private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;
};#endif // !__ASYNC_DEMO_H_2023_8_22__
AsyncWriteSomeToSocketErr函数为我们封装的写操作,AsyncWriteSomeToSocketErr为异步写操作回调的函数,为什么会有三个参数呢,我们可以看一下asio源码:
template <typename ConstBufferSequence,BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code,std::size_t)) WriteTokenBOOST_ASIO_DEFAULT_COMPLETION_TOKEN_TYPE(executor_type)>BOOST_ASIO_INITFN_AUTO_RESULT_TYPE_PREFIX(WriteToken,void (boost::system::error_code, std::size_t))async_write_some(const ConstBufferSequence& buffers,BOOST_ASIO_MOVE_ARG(WriteToken) tokenBOOST_ASIO_DEFAULT_COMPLETION_TOKEN(executor_type))BOOST_ASIO_INITFN_AUTO_RESULT_TYPE_SUFFIX((async_initiate<WriteToken,void (boost::system::error_code, std::size_t)>(declval<initiate_async_send>(), token,buffers, socket_base::message_flags(0)))){return async_initiate<WriteToken,void (boost::system::error_code, std::size_t)>(initiate_async_send(this), token,buffers, socket_base::message_flags(0));}
async_write_some是异步写的函数,这个异步写函数有两个参数,第一个参数为ConstBufferSequence常引用类型的buffer,就是构造buffer结构。
第二个参数为WriteToken类型,而WriteToken在上面定义了,是一个函数对象类型,返回值为void,参数为error_code和size_t,所以我们为了调用async_write_some函数也要传入一个符合WriteToken定义的函数,就是我们声明的AsyncWriteSomeToSocketErr函数,前两个参数为WriteToken规定的参数,第三个参数为Buffer的智能指针,这样通过智能指针保证我们发送的Buffer数据生命周期延长。
我们看一下AsyncWriteSomeToSocketErr函数的具体实现:
void Session::AsyncWriteSomeToSocketErr(const std::string& buffer) {//先构造一个发送节点send_buffer_ = std::make_shared<Buffer>(buffer.c_str(), buffer.length());//然后构造async_write_some的参数buffer和回调和函数socket_->async_write_some(boost::asio::buffer(buffer.c_str(), buffer.length()),//绑定成员函数的地址,类的对象,参数占位符1,参数占位符2std::bind(&Session::AsyncWriteSomeCallBackErr, this, std::placeholders::_1, std::placeholders::_2, send_buffer_));
}//TCP缓冲区 收发端不对等 发11字节 TCP缓冲区只有5字节 那么要分两次发送,假设发送hello world ,第一次只发送hello,\
world未发送,那么如果用户再次调用WriteCallBackErr那么底层不保护发送顺序,那么可能收到的结果hello hello world world \
解决这种就是用一个队列把存储的数据存放到队列里面
void Session::AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer_) {if (err.value() != 0) {std::cout << "error occured!error code: " << err.value() << " . message: " << err.what() << std::endl;return;}if (bytes_transferred + buffer_->GetCurLen() < buffer_->GetTotalLen()) {//buffer_->GetCurLen() = buffer_->GetCurLen() + bytes_transferred;buffer_->SetCurLen(buffer_->GetCurLen() + bytes_transferred);socket_->async_write_some(boost::asio::buffer(buffer_->GetMsg() + buffer_->GetCurLen(), buffer_->GetTotalLen() - buffer_->GetCurLen()),std::bind(&Session::AsyncWriteSomeCallBackErr, this, std::placeholders::_1, std::placeholders::_2, buffer_));}
}
这段代码的作用是实现异步发送数据的功能,主要包括两个函数:AsyncWriteSomeToSocketErr 和 AsyncWriteSomeCallBackErr。
-
AsyncWriteSomeToSocketErr 函数的作用是将数据放入发送队列中,并触发异步写操作。具体步骤如下:
- 首先,使用 std::make_shared 创建一个 Buffer 对象,这个对象用于存储要发送的数据。
- 然后,使用 socket_->async_write_some 函数触发异步写操作,将数据写入套接字。在这里,你绑定了回调函数 AsyncWriteSomeCallBackErr。
-
AsyncWriteSomeCallBackErr 函数是异步写操作完成后的回调函数。它的主要作用是处理写操作的结果,检查是否发生错误,以及是否需要继续发送剩余的数据。具体步骤如下:
- 首先,检查 err 参数,如果其值不为 0,表示发送出现错误,就输出错误信息并返回。
- 然后没有错误,检查已传输的字节数 bytes_transferred 加上 buffer_ 对象中已经发送的字节数 buffer_->GetCurLen() 是否小于总的数据长度 buffer_->GetTotalLen()。如果小于总长度,说明还有剩余数据需要发送。
- 如果有剩余数据需要发送,就更新 buffer_ 对象中的已发送字节数 buffer_->SetCurLen(buffer_->GetCurLen() + bytes_transferred),然后继续触发异步写操作,将剩余的数据发送出去。这里再次调用 socket_->async_write_some 并绑定了相同的回调函数,以便在写操作完成后再次检查和处理。
- 总体来说,这段代码实现了异步发送数据的逻辑,确保了数据的完整性和发送顺序。通过使用回调函数,可以在每次写操作完成后处理相应的逻辑,包括检查错误、更新已发送字节数以及触发下一次写操作。
在AsyncWriteSomeToSocketErr函数里判断如果已经发送的字节数没有达到要发送的总字节数,那么久更新节点已经发送的长度,然后计算剩余要发送的长度,如果有数据未发送完,再次调用async_write_some函数异步发送。
但是这个函数并不能投入实际应用,因为async_write_some回调函数返回已发送的字节数可能并不是全部长度。比如TCP发送缓存区总大小为8字节,但是有3字节未发送(上一次未发送完),这样剩余空间为5字节。
此时我们调用async_write_some发送hello world!实际发送的长度就是为5,也就是只发送了hello,剩余world!通过我们的回调继续发送。
而实际开发的场景用户是不清楚底层tcp的多路复用调用情况的,用户想发送数据的时候就调用WriteToSocketErr,或者循环调用WriteToSocketErr,很可能在一次没发送完数据还未调用回调函数时再次调用WriteToSocketErr,因为boost::asio封装的时epoll和iocp等多路复用模型。当写事件就绪后就发数据,发送的数据按照async_write_some调用的顺序发送,所以回调函数内调用的async_write_some可能并没有被及时调用。
比如我们如下代码:
//用户发送数据
AsyncWriteSomeToSocketErr("Hello World!");
//用户无感知下层调用情况又一次发送了数据
AsyncWriteSomeToSocketErr("Hello World!");
那么很可能第一次只发送了Hello,后面的数据没发完,第二次发送了Hello World!之后又发送了World!
所以对端收到的数据很可能是HelloHello World! World!
3、异步写void AsyncWriteSomeToSocket(const std::string& buffer)
那怎么解决这个问题呢,我们可以通过队列保证应用层的发送顺序。我们在Session中定义一个发送队列,然后重新定义正确的异步发送函数和回调处理:
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);//异步写 这个异步写存在问题void AsyncWriteSomeToSocketErr(const std::string& buffer);void AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer);void AsyncWriteSomeToSocket(const std::string& buffer);void AsyncWriteSomeCallBack(const boost::system::error_code& err, std::size_t bytes_transferred);
private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;std::queue<std::shared_ptr<Buffer>> send_queue_;bool send_padding_;
};#endif // !__ASYNC_DEMO_H_2023_8_22__
定义了bool变量send_padding_,该变量为true表示一个节点还未发送完,false代表发送完成。send_padding_ 用来缓存要发送的消息协议节点,是一个队列。
我们实现异步发送功能:
Session::Session(std::shared_ptr<boost::asio::ip::tcp::socket> socket):socket_(socket),send_padding_(false)
{send_buffer_ = nullptr;if (!send_queue_.empty()) {send_queue_.pop();}
}
函数实现:
void Session::AsyncWriteSomeToSocket(const std::string& buffer) {//发送节点插入队列send_queue_.emplace(new Buffer(buffer.c_str(), buffer.length()));//判断是否还有未发完的数据,false,表示没有,true表示还有if (send_padding_) {return;}//异步发送数据socket_->async_write_some(boost::asio::buffer(buffer.c_str(), buffer.length()), std::bind(&Session::AsyncWriteSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));send_padding_ = true;
}void Session::AsyncWriteSomeCallBack(const boost::system::error_code& err, size_t bytes_transferred) {if (err.value() != 0) {std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}//取出队列中队首元素std::shared_ptr<Buffer> send_data = send_queue_.front();send_data->SetCurLen(send_data->GetCurLen() + bytes_transferred);//数据未发送完,继承调用异步函数取出队首元素发送if (send_data->GetCurLen() < send_data->GetTotalLen()) {socket_->async_write_some(boost::asio::buffer(send_data->GetMsg() + send_data->GetCurLen(),send_data->GetTotalLen() - send_data->GetCurLen()),std::bind(&Session::AsyncWriteSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));return;}//如果这个数据发送完了,把数据节点取出来send_queue_.pop();//判断队列里面是否还有下一个数据if (send_queue_.empty()) {send_padding_ = false;return;}//有数据则继续发送if (!send_queue_.empty()) {std::shared_ptr<Buffer> send_data_next = send_queue_.front();//异步发送的地址偏移socket_->async_write_some(boost::asio::buffer(send_data_next->GetMsg() + send_data_next->GetCurLen(),send_data_next->GetTotalLen() - send_data_next->GetCurLen()),std::bind(&Session::AsyncWriteSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));}
}
这段代码的作用是实现异步发送数据并保证发送顺序的逻辑,主要包括两个函数:AsyncWriteSomeToSocket 和 AsyncWriteSomeCallBack。
-
AsyncWriteSomeToSocket 函数的作用是将数据放入发送队列中,并触发异步写操作。具体步骤如下:
- 首先,将一个新的 Buffer 对象(用于存储要发送的数据)插入到 send_queue_ 队列中。
- 接着,检查是否还有未发完的数据,如果有,说明还在等待前一次异步写操作完成,直接返回。
- 如果没有未发完的数据,说明可以触发异步发送操作,使用 socket_->async_write_some 函数将数据写入套接字,并绑定回调函数 AsyncWriteSomeCallBack。
-
AsyncWriteSomeCallBack 函数是异步写操作完成后的回调函数。其主要作用是处理写操作的结果,继续发送队列中的下一个数据。具体步骤如下:
- 首先,检查 err 参数,如果其值不为 0,表示发送出现错误,就输出错误信息并返回。
- 然后,取出队列中队首元素,该元素是一个 Buffer 对象,表示待发送的数据。
- 接着,更新这个数据的已发送字节数 send_data->SetCurLen(send_data->GetCurLen() + bytes_transferred)。
- 然后,检查数据是否已经全部发送完,如果未发送完,则继续触发异步写操作,将剩余的数据发送出去。
- 如果这个数据已经发送完毕,就从队列中移除这个数据节点,并检查队列是否还有下一个数据。
- 如果队列不为空,表示还有数据需要发送,就取出下一个数据节点,更新已发送字节数,并触发下一个异步写操作,以便发送下一个数据。
这段代码的设计确保了数据的发送顺序,即使在异步发送的情况下也可以保持数据的完整性和顺序。如果发送错误,它也会正确地处理错误情况。
async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度,这样我们每次都要在回调函数判断发送数据是否完成,asio提供了一个更简单的发送函数async_send,这个函数在发送的长度未达到我们要求的长度时就不会触发回调,所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送,所以async_send不能和async_write_some混合使用,我们基于async_send封装另外一个发送函数。
4、异步写void AsyncSendToSocket(const std::string& buffer)
函数定义:
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);//异步写 这个异步写存在问题void AsyncWriteSomeToSocketErr(const std::string& buffer);void AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer);void AsyncWriteSomeToSocket(const std::string& buffer);void AsyncWriteSomeCallBack(const boost::system::error_code& err, std::size_t bytes_transferred);//优先取这个void AsyncSendToSocket(const std::string& buffer);void AsyncSendCallBack(boost::system::error_code& err, std::size_t bytes_transferred);
private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;std::queue<std::shared_ptr<Buffer>> send_queue_;bool send_padding_;
};#endif // !__ASYNC_DEMO_H_2023_8_22__
函数实现:
void Session::AsyncSendToSocket(const std::string& buffer) {//把发送消息协议构造成节点插入队列send_queue_.emplace(new Buffer(buffer.c_str(), buffer.length()));//判断是否还有未发完数据if (send_padding_) {return;}//异步发送数据socket_->async_send(boost::asio::buffer(buffer.c_str(), buffer.length()),std::bind(&Session::AsyncSendCallBack, this, std::placeholders::_1, std::placeholders::_2));send_padding_ = true;
}void Session::AsyncSendCallBack(boost::system::error_code& err, std::size_t bytes_transferred) {if (0 != err.value()) {//发送数据失败std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}//因为调用的是async_send()它的设计目标是简化发送数据的过程,\让用户不必关心数据的细节,只需提供要发送的数据和回调函数即可send_queue_.pop();if (send_queue_.empty()) {send_padding_ = false;return;}if (!send_queue_.empty()) {std::shared_ptr<Buffer> send_data_next = send_queue_.front();//异步发送发生地址偏移socket_->async_send(boost::asio::buffer(send_data_next->GetMsg() + send_data_next->GetCurLen(),send_data_next->GetTotalLen() - send_data_next->GetCurLen()),std::bind(&Session::AsyncSendCallBack, this,std::placeholders::_1, std::placeholders::_2));}
}
这段代码的目的是实现异步发送数据,并在发送完成后调用回调函数进行处理。这与你之前提到的代码逻辑类似,但使用了 async_send 函数代替了 async_write_some,并且没有需要手动维护已发送字节数。
-
具体的逻辑如下:
-
AsyncSendToSocket 函数用于将数据包装成一个 Buffer 对象并插入发送队列 send_queue_ 中。
-
接着,检查是否已经有数据正在等待发送(send_padding_ 是否为 true),如果是,则说明还在等待前一次异步发送完成,直接返回。
-
如果没有等待发送的数据,就调用 socket_->async_send 函数进行异步发送。这个函数会将数据发送到套接字,并在发送完成后调用回调函数 AsyncSendCallBack。
-
在 AsyncSendCallBack 回调函数中,首先检查错误码 err,如果不为 0,表示发送出现错误,输出错误信息并返回。
-
如果发送成功,就从发送队列中弹出已发送的数据 (send_queue_.pop()),并检查队列是否为空。如果队列为空,说明没有待发送的数据,将 send_padding_ 设置为 false 表示没有数据需要发送。
-
如果队列不为空,表示还有待发送的数据,就取出队列的头部元素,即下一个要发送的数据,然后调用 socket_->async_send 再次异步发送数据。这个过程会重复,直到队列中的数据全部发送完毕。
-
总体而言,这段代码实现了异步发送数据的功能,保证了发送的顺序,同时也能正确处理发送过程中的错误。不同之处在于,它使用了 async_send 函数,该函数封装了发送的细节,使得发送数据更加方便。
5、异步读void AsyncReadSomeToSocket(const std::string& buffer)
接下来介绍异步读操作,异步读操作和异步的写操作类似同样又async_read_some和async_receive函数,前者触发的回调函数获取的读数据的长度可能会小于要求读取的总长度,后者触发的回调函数读取的数据长度等于读取的总长度。
先基于async_read_some封装一个读取的函数AsyncReadSomeToSocket,同样在Session类的声明中添加一些变量:
函数定义:
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);//异步写 这个异步写存在问题void AsyncWriteSomeToSocketErr(const std::string& buffer);void AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer);void AsyncWriteSomeToSocket(const std::string& buffer);void AsyncWriteSomeCallBack(const boost::system::error_code& err, std::size_t bytes_transferred);//优先取这个void AsyncSendToSocket(const std::string& buffer);void AsyncSendCallBack(boost::system::error_code& err, std::size_t bytes_transferred);//异步读 优先取这个void AsyncReadSomeToSocket(const std::string& buffer);void AsyncReadSomeCallBack(boost::system::error_code& err, std::size_t bytes_transferred);private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;std::queue<std::shared_ptr<Buffer>> send_queue_;std::shared_ptr<Buffer> recv_buffer_;bool send_padding_;bool recv_padding_;
};#endif // !__ASYNC_DEMO_H_2023_8_22__
函数实现:
void Session::AsyncReadSomeToSocket(const std::string& buffer) {//判断是否正在读数据,这里第一次读数据if (recv_padding_) {return;}recv_buffer_ = std::make_shared<Buffer>(buffer.c_str(), buffer.length());//异步读取数据socket_->async_read_some(boost::asio::buffer(recv_buffer_->GetMsg() + recv_buffer_->GetCurLen(),recv_buffer_->GetTotalLen() - recv_buffer_->GetCurLen()),std::bind(&Session::AsyncReadSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));recv_padding_ = true;
}void Session::AsyncReadSomeCallBack(boost::system::error_code& err, std::size_t bytes_transferred) {if (0 != err.value()) {std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}//判断读取的字节数,没有读取完继续读取recv_buffer_->SetCurLen(recv_buffer_->GetCurLen() + bytes_transferred);if (recv_buffer_->GetCurLen() < recv_buffer_->GetTotalLen()) {socket_->async_read_some(boost::asio::buffer(recv_buffer_->GetMsg() + recv_buffer_->GetCurLen(),recv_buffer_->GetTotalLen() - recv_buffer_->GetCurLen()),std::bind(&Session::AsyncReadSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));return;}//将数据投递到队列里交给逻辑线程处理,此处略去//如果读完了则将标记置为falserecv_padding_ = false;recv_buffer_ = nullptr;
}
这段代码的主要功能是异步读取数据,并在读取完成后调用回调函数 AsyncReadSomeCallBack 处理数据。以下是代码逻辑的详细解释:
-
AsyncReadSomeToSocket 函数用于异步读取数据。在这个函数中,首先检查 recv_padding_ 是否为 true。如果为 true,表示正在读取数据,直接返回,避免重复读取。
-
如果 recv_padding_ 为 false,说明可以开始读取数据。这时,创建一个 Buffer 对象 recv_buffer_,并初始化为要读取的数据。
-
接着,调用 socket_->async_read_some 函数进行异步读取数据。这个函数会在读取完成后调用回调函数 AsyncReadSomeCallBack。
-
在 AsyncReadSomeCallBack 回调函数中,首先检查错误码 err,如果不为 0,表示读取出现错误,输出错误信息并返回。
-
如果读取成功,将已读取的字节数添加到 recv_buffer_ 的当前长度 CurLen 中。然后,检查是否已经读取完所有数据,即 CurLen 是否小于 TotalLen。
-
如果未读取完,继续调用 socket_->async_read_some 函数继续异步读取剩余的数据,直到读取完所有数据。
-
如果读取完了,将 recv_padding_ 置为 false,表示没有正在读取的数据。最后,清空 recv_buffer_ 对象,以便下次读取新的数据。
这段代码实现了异步读取数据的逻辑,确保数据被正确读取并处理。如果数据没有完全读取,它会继续异步读取剩余的部分,直到读取完整个数据。如果有新的数据需要读取,可以再次调用 AsyncReadSomeToSocket。
6、异步读void AsyncReceiveToSocket(const std::string& buffer)
我们基于async_receive再封装一个接收数据的函数:
函数声明:
#pragma once
#ifndef __ASYNC_DEMO_H_2023_8_22__
#define __ASYNC_DEMO_H_2023_8_22__
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);//异步写 这个异步写存在问题void AsyncWriteSomeToSocketErr(const std::string& buffer);void AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer);void AsyncWriteSomeToSocket(const std::string& buffer);void AsyncWriteSomeCallBack(const boost::system::error_code& err, std::size_t bytes_transferred);//优先取这个void AsyncSendToSocket(const std::string& buffer);void AsyncSendCallBack(boost::system::error_code& err, std::size_t bytes_transferred);//异步读 优先取这个void AsyncReadSomeToSocket(const std::string& buffer);void AsyncReadSomeCallBack(boost::system::error_code& err, std::size_t bytes_transferred);void AsyncReceiveToSocket(const std::string& buffer);void AsyncReceiveCallBack(boost::system::error_code& err, std::size_t bytes_transferred);private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;std::queue<std::shared_ptr<Buffer>> send_queue_;std::shared_ptr<Buffer> recv_buffer_;bool send_padding_;bool recv_padding_;
};#endif // !__ASYNC_DEMO_H_2023_8_22__
函数实现:
void Session::AsyncReceiveToSocket(const std::string& buffer) {//判断是否有数据正在读取if (recv_padding_) {return;}recv_buffer_ = std::make_shared<Buffer>(buffer.c_str(), buffer.length());socket_->async_receive(boost::asio::buffer(recv_buffer_->GetMsg() + recv_buffer_->GetCurLen(),recv_buffer_->GetTotalLen() - recv_buffer_->GetCurLen()),std::bind(&Session::AsyncReceiveCallBack, this, std::placeholders::_1, std::placeholders::_2));recv_padding_ = true;
}void Session::AsyncReceiveCallBack(boost::system::error_code& err, std::size_t bytes_transferred) {if (0 != err.value()) {std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}recv_buffer_->SetCurLen(recv_buffer_->GetCurLen() + bytes_transferred);recv_padding_ = false;recv_buffer_ = nullptr;
}
这段代码看起来非常类似于前面提到的异步读取数据的代码。它实现了异步接收数据的逻辑,以下是代码的详细解释:
-
AsyncReceiveToSocket 函数用于异步接收数据。首先,它检查 recv_padding_ 是否为 true,如果为 true,表示已经有数据在读取,直接返回,以避免重复接收。
-
如果 recv_padding_ 为 false,说明可以开始接收数据。这时,创建一个 Buffer 对象 recv_buffer_,并初始化为要接收的数据。
接着,调用 socket_->async_receive 函数进行异步接收数据。这个函数会在接收完成后调用回调函数 AsyncReceiveCallBack。
在 AsyncReceiveCallBack 回调函数中,首先检查错误码 err,如果不为 0,表示接收出现错误,输出错误信息并返回。
如果接收成功,将已接收的字节数添加到 recv_buffer_ 的当前长度 CurLen 中。然后,将 recv_padding_ 置为 false,表示没有正在接收的数据。
最后,清空 recv_buffer_ 对象,以便下次接收新的数据。
这段代码实现了异步接收数据的逻辑,确保数据被正确接收并处理。如果数据没有完全接收,它会继续异步接收剩余的部分,直到接收完整个数据。如果有新的数据需要接收,可以再次调用 AsyncReceiveToSocket。
同样async_read_some和async_receive不能混合使用,否则会出现逻辑问题。
7、总结
总体代码声明:
#pragma once
#ifndef __ASYNC_DEMO_H_2023_8_22__
#define __ASYNC_DEMO_H_2023_8_22__
#include<iostream>
#include<boost/asio.hpp>
#include<memory>
#include<string>
#include<queue>
#include"buffer.h"class Session {
public:Session(std::shared_ptr <boost::asio::ip::tcp::socket> socket);bool Connect(boost::asio::ip::tcp::endpoint& ep);//异步写 这个异步写存在问题void AsyncWriteSomeToSocketErr(const std::string& buffer);void AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer);void AsyncWriteSomeToSocket(const std::string& buffer);void AsyncWriteSomeCallBack(const boost::system::error_code& err, std::size_t bytes_transferred);//优先取这个void AsyncSendToSocket(const std::string& buffer);void AsyncSendCallBack(boost::system::error_code& err, std::size_t bytes_transferred);//异步读 优先取这个void AsyncReadSomeToSocket(const std::string& buffer);void AsyncReadSomeCallBack(boost::system::error_code& err, std::size_t bytes_transferred);void AsyncReceiveToSocket(const std::string& buffer);void AsyncReceiveCallBack(boost::system::error_code& err, std::size_t bytes_transferred);private:std::shared_ptr<boost::asio::ip::tcp::socket> socket_;std::shared_ptr<Buffer> send_buffer_;std::queue<std::shared_ptr<Buffer>> send_queue_;std::shared_ptr<Buffer> recv_buffer_;bool send_padding_;bool recv_padding_;
};#endif // !__ASYNC_DEMO_H_2023_8_22__
总体代码定义:
#include"async_demo.h"Session::Session(std::shared_ptr<boost::asio::ip::tcp::socket> socket):socket_(socket),send_padding_(false),recv_padding_(false)
{send_buffer_ = nullptr;recv_buffer_ = nullptr;if (!send_queue_.empty()) {send_queue_.pop();}
}bool Session::Connect(boost::asio::ip::tcp::endpoint& ep) {socket_->connect(ep);return true;
}void Session::AsyncWriteSomeToSocketErr(const std::string& buffer) {//先构造一个发送节点send_buffer_ = std::make_shared<Buffer>(buffer.c_str(), buffer.length());//然后构造async_write_some的参数buffer和回调和函数socket_->async_write_some(boost::asio::buffer(buffer.c_str(), buffer.length()),//绑定成员函数的地址,类的对象,参数占位符1,参数占位符2std::bind(&Session::AsyncWriteSomeCallBackErr, this, std::placeholders::_1, std::placeholders::_2, send_buffer_));
}//TCP缓冲区 收发端不对等 发11字节 TCP缓冲区只有5字节 那么要分两次发送,假设发送hello world ,第一次只发送hello,\
world未发送,那么如果用户再次调用WriteCallBackErr那么底层不保护发送顺序,那么可能收到的结果hello hello world world \
解决这种就是用一个队列把存储的数据存放到队列里面
void Session::AsyncWriteSomeCallBackErr(const boost::system::error_code& err, std::size_t bytes_transferred, std::shared_ptr<Buffer> buffer_) {if (err.value() != 0) {std::cout << "error occured!error code: " << err.value() << " . message: " << err.what() << std::endl;return;}if (bytes_transferred + buffer_->GetCurLen() < buffer_->GetTotalLen()) {//buffer_->GetCurLen() = buffer_->GetCurLen() + bytes_transferred;buffer_->SetCurLen(buffer_->GetCurLen() + bytes_transferred);socket_->async_write_some(boost::asio::buffer(buffer_->GetMsg() + buffer_->GetCurLen(), buffer_->GetTotalLen() - buffer_->GetCurLen()),std::bind(&Session::AsyncWriteSomeCallBackErr, this, std::placeholders::_1, std::placeholders::_2, buffer_));}
}void Session::AsyncWriteSomeToSocket(const std::string& buffer) {//发送节点插入队列send_queue_.emplace(new Buffer(buffer.c_str(), buffer.length()));//判断是否还有未发完的数据,false,表示没有,true表示还有if (send_padding_) {return;}//异步发送数据socket_->async_write_some(boost::asio::buffer(buffer.c_str(), buffer.length()), std::bind(&Session::AsyncWriteSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));send_padding_ = true;
}void Session::AsyncWriteSomeCallBack(const boost::system::error_code& err, size_t bytes_transferred) {if (err.value() != 0) {std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}//取出队列中队首元素std::shared_ptr<Buffer> send_data = send_queue_.front();send_data->SetCurLen(send_data->GetCurLen() + bytes_transferred);//数据未发送完,继承调用异步函数取出队首元素发送if (send_data->GetCurLen() < send_data->GetTotalLen()) {socket_->async_write_some(boost::asio::buffer(send_data->GetMsg() + send_data->GetCurLen(),send_data->GetTotalLen() - send_data->GetCurLen()),std::bind(&Session::AsyncWriteSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));return;}//如果这个数据发送完了,把数据节点取出来send_queue_.pop();//判断队列里面是否还有下一个数据if (send_queue_.empty()) {send_padding_ = false;return;}//有数据则继续发送if (!send_queue_.empty()) {std::shared_ptr<Buffer> send_data_next = send_queue_.front();//异步发送的地址偏移socket_->async_write_some(boost::asio::buffer(send_data_next->GetMsg() + send_data_next->GetCurLen(),send_data_next->GetTotalLen() - send_data_next->GetCurLen()),std::bind(&Session::AsyncWriteSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));}
}void Session::AsyncSendToSocket(const std::string& buffer) {//把发送消息协议构造成节点插入队列send_queue_.emplace(new Buffer(buffer.c_str(), buffer.length()));//判断是否还有未发完数据if (send_padding_) {return;}//异步发送数据socket_->async_send(boost::asio::buffer(buffer.c_str(), buffer.length()),std::bind(&Session::AsyncSendCallBack, this, std::placeholders::_1, std::placeholders::_2));send_padding_ = true;
}void Session::AsyncSendCallBack(boost::system::error_code& err, std::size_t bytes_transferred) {if (0 != err.value()) {//发送数据失败std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}//因为调用的是async_send()它的设计目标是简化发送数据的过程,\让用户不必关心数据的细节,只需提供要发送的数据和回调函数即可send_queue_.pop();if (send_queue_.empty()) {send_padding_ = false;return;}if (!send_queue_.empty()) {std::shared_ptr<Buffer> send_data_next = send_queue_.front();//异步发送发生地址偏移socket_->async_send(boost::asio::buffer(send_data_next->GetMsg() + send_data_next->GetCurLen(),send_data_next->GetTotalLen() - send_data_next->GetCurLen()),std::bind(&Session::AsyncSendCallBack, this,std::placeholders::_1, std::placeholders::_2));}
}void Session::AsyncReadSomeToSocket(const std::string& buffer) {//判断是否正在读数据,这里第一次读数据if (recv_padding_) {return;}recv_buffer_ = std::make_shared<Buffer>(buffer.c_str(), buffer.length());//异步读取数据socket_->async_read_some(boost::asio::buffer(recv_buffer_->GetMsg() + recv_buffer_->GetCurLen(),recv_buffer_->GetTotalLen() - recv_buffer_->GetCurLen()),std::bind(&Session::AsyncReadSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));recv_padding_ = true;
}void Session::AsyncReadSomeCallBack(boost::system::error_code& err, std::size_t bytes_transferred) {if (0 != err.value()) {std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}//判断读取的字节数,没有读取完继续读取recv_buffer_->SetCurLen(recv_buffer_->GetCurLen() + bytes_transferred);if (recv_buffer_->GetCurLen() < recv_buffer_->GetTotalLen()) {socket_->async_read_some(boost::asio::buffer(recv_buffer_->GetMsg() + recv_buffer_->GetCurLen(),recv_buffer_->GetTotalLen() - recv_buffer_->GetCurLen()),std::bind(&Session::AsyncReadSomeCallBack, this, std::placeholders::_1, std::placeholders::_2));return;}//将数据投递到队列里交给逻辑线程处理,此处略去//如果读完了则将标记置为falserecv_padding_ = false;recv_buffer_ = nullptr;
}void Session::AsyncReceiveToSocket(const std::string& buffer) {//判断是否有数据正在读取if (recv_padding_) {return;}recv_buffer_ = std::make_shared<Buffer>(buffer.c_str(), buffer.length());socket_->async_receive(boost::asio::buffer(recv_buffer_->GetMsg() + recv_buffer_->GetCurLen(),recv_buffer_->GetTotalLen() - recv_buffer_->GetCurLen()),std::bind(&Session::AsyncReceiveCallBack, this, std::placeholders::_1, std::placeholders::_2));recv_padding_ = true;
}void Session::AsyncReceiveCallBack(boost::system::error_code& err, std::size_t bytes_transferred) {if (0 != err.value()) {std::cout << "error occured!error code: " << err.value() << " .message: " << err.what() << std::endl;return;}recv_buffer_->SetCurLen(recv_buffer_->GetCurLen() + bytes_transferred);recv_padding_ = false;recv_buffer_ = nullptr;
}
本文介绍了boost asio异步读写的操作,仅仅是代码片段和api的封装便于大家理解,下一篇利用这些异步api写一个异步的服务器展示收发效果。
相关文章:

【boost网络库从青铜到王者】第六篇:asio网络编程中的socket异步读(接收)写(发送)
文章目录 1、简介2、异步写 void AsyncWriteSomeToSocketErr(const std::string& buffer)3、异步写void AsyncWriteSomeToSocket(const std::string& buffer)4、异步写void AsyncSendToSocket(const std::string& buffer)5、异步读void AsyncReadSomeToSocket(cons…...

django sqlite3操作和manage.py功能介绍
参考链接:https://www.cnblogs.com/csd97/p/8432715.html manage.py 常用命令_python manage.py_追逐&梦想的博客-CSDN博客 python django操作sqlite3_django sqlite_浪子仙迹的博客-CSDN博客...

【SQL语句】SQL编写规范
简介 本文编写原因主要来于XC迁移过程中修改SQL语句时,发现大部分修改均源自于项目SQL编写不规范,以此文档做以总结。 注:此文档覆盖不甚全面,大体只围绕迁移遇到的修改而展开。 正文 1、【字段引号】 列名、表名如无特殊情况…...

后端项目开发:工具类封装(序列化、反射)
1.整合Jackson 根据《阿里巴巴开发规范》,包名使用单数,类名可以使用复数。 所以generic-common创建util包和utils工具类 很多时候我们需要将接收到的json数据转换为对象,或者将对象转为json存储。这时候我们需要编写用于json转换的工具类。…...

软件测试技术分享丨遇到bug怎么分析?
为什么定位问题如此重要? 可以明确一个问题是不是真的“bug” 很多时候,我们找到了问题的原因,结果发现这根本不是bug。原因明确,误报就会降低 多个系统交互,可以明确指出是哪个系统的缺陷,防止“踢皮球…...

LeetCode无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “…...

17.2.2 【Linux】通过systemctl观察系统上所有的服务
使用 systemctl list-unit-files 会将系统上所有的服务通通列出来~而不像 list-units 仅以 unit 分类作大致的说明。 至于 STATE 状态就是前两个小节谈到的开机是否会载入的那个状态项目。主要有 enabled / disabled / mask / static 等等。 假设我不想要知道这么多…...

Redis扩容机制与一致性哈希算法解析
在分布式系统设计中,Redis是一个备受欢迎的内存数据库,而一致性哈希算法则是分布式系统中常用的数据分片和负载均衡技术。本文将深入探讨Redis的扩容机制以及一致性哈希算法的原理,同时提供示例代码以帮助读者更好地理解这两个重要概念。 推…...

BDA初级分析——可视化基础
一、可视化的作用 数据可视化——利用各种图形方式更加直观地呈现数据的过程 可视化的作用 1、更快地理解数据,找出数据的规律和异常 2、讲出数据背后的故事,辅助做出业务决策 3、给非专业人士提供数据探索的能力 数据分析问题如何通过可视化呈现&am…...

边缘计算节点BEC典型实践:如何快速上手PC-Farm服务器?
百度智能云边缘计算节点BEC(Baidu Edge Computing)基于运营商边缘节点和网络构建,一站式提供靠近终端用户的弹性计算资源。边缘计算节点在海外覆盖五大洲,在国内覆盖全国七大区、三大运营商。BEC通过就近计算和处理,大…...

python自动把内容发表到wordpress完整示例及错误解答
要实现 Python 自动将内容发布到 WordPress,可以使用 Python 的 wordpress_xmlrpc 库,该库提供了使用 WordPress XML-RPC API 进行内容发布和管理的功能。 需要安装一下第三方库:wordpress_xmlrpc! pip install python_wordpress_xmlrpc 下面是一个简单的示例代码,可以实…...

【javaweb】学习日记Day6 - Mysql 数据库 DDL DML DQL
之前学习过的SQL语句笔记总结戳这里→【数据库原理与应用 - 第六章】T-SQL 在SQL Server的使用_Roye_ack的博客-CSDN博客 目录 一、概述 1、如何安装及配置路径Mysql? 2、SQL分类 二、DDL 数据定义 1、数据库操作 2、IDEA内置数据库使用 (1&…...

如何利用SFTP如何实现更安全的远程文件传输 ——【内网穿透】
🎬 鸽芷咕:个人主页 🔥 个人专栏: 《高效编程技巧》《cpolar》 ⛺️生活的理想,就是为了理想的生活! 文章目录 1. 安装openSSH1.1 安装SSH1.2 启动ssh 2. 安装cpolar2.1 配置termux服务 3. 远程SFTP连接配置3.1 查看生成的随机公…...

枚举和反射
枚举 枚举 枚举是一种特殊的类,它可以有自己的属性、方法和构造方法。 两种枚举的方法 自定义枚举 a.将构造器私有化,防止外部直接new b.去掉set方法,防止属性被修改 c.在内部直接创建固定的对象 通过类名直接去访问 关键字枚举 用…...

MinIO【部署 01】MinIO安装及SpringBoot集成简单测试
MinIO安装及SpringBoot集成测试 1.下载安装1.1 Install the MinIO Server1.2 Launch the MinIO Server1.3 Connect Your Browser to the MinIO Server 2.SpringBoot集成2.1 依赖及配置2.2 代码2.3 测试结果 1.下载安装 下载 https://min.io/download#/linux; 安装文…...

问道管理:证券代码是什么?有什么用?
交流炒股经历时,有些股民一时忘了股票发行公司的全称,会直接报一串数字来代替,这串数字的内容是证券代码,那么,证券代码是什么?它又起什么作用?关于这些,为大家准备了以下参考内容。…...

中文医学知识语言模型:BenTsao
介绍 BenTsao:[原名:华驼(HuaTuo)]: 基于中文医学知识的大语言模型指令微调 本项目开源了经过中文医学指令精调/指令微调(Instruction-tuning) 的大语言模型集,包括LLaMA、Alpaca-Chinese、Bloom、活字模型等。 我们基于医学知识图谱以及医…...

Java基础十四(字符串)
1. 判断邮箱 输入一个电子邮箱,判断是否是正确电子邮箱地址。 正确的邮箱地址: 必须包含 字符,不能是开头或结尾必须以 .com结尾和.com之间必须有其他字符 public class Mailbox {/*** 判断邮箱* param str* return boolean*/public stat…...

vue3 基础知识 (动态组件 keep-alive 等) 04
嘿,happy 文章目录 一、动态组件二、keep-alive 一、动态组件 动态组件是使用 component 组件,通过一个特殊的属性 is 来实现 一定是注册好的组件我们需要将属性和监听事件放到 component 上来使用 <template><div><button v-for"t…...

【C# Programming】编程入门:数组、操作符、控制流
目录 一、数组 1、数组的声明 1.1 一维数组声明: 1.2 多维数组声明: 2、数组的实例化和赋值 2.1 数组在声明时通过在花括号中使用以逗号分隔的数据项对数组赋值, 例如: 2.2 如果在声明后赋值,则需…...

线上问诊:业务数据采集
系列文章目录 线上问诊:业务数据采集 文章目录 系列文章目录前言一、环境准备1.Hadoop2.Zookeeper3.Kafka4.Flume5.Mysql6.Maxwell 二、业务数据采集1.数据模拟2.采集通道 总结 前言 暑假躺了两个月,也没咋写博客,准备在开学前再做个项目找…...

2023 CCPC 华为云计算挑战赛 hdu7399 博弈,启动!(图上博弈/枚举+逆向有向图sg函数)
题目 给定t(t<200)组样例, 每次给定一个n(n<300)个左边的点m(m<300)个右边的点的二分图,图无重边 所有边总量不超过5e5 初始时棋子可以被放置在任意一个点上, 若被放置在左边,则Alice先走;被放置在右边&a…...

Unity之 Vector3 的详细介绍以及方法的介绍
文章目录 总的介绍小试牛刀相关的描述的参数看个小例子 总的介绍 当涉及到Unity中的Vector3类时,以下是一些常用的方法和操作: magnitude 方法:返回向量的长度。 float length vector.magnitude;sqrMagnitude 方法:返回向量的平…...

Postgresql部署及简单操作
目录 1、介绍 2、什么是PostgreSQL 3、PostgreSQL 的特点 4、数据库定为 5、环境准备 6、编译安装 6.1 安装依赖包 6.2 下载安装包 6.3 创建用户 6.4 创建 postgresql数据目录并授权 6.5 上传压缩包并解压 6.6 编译postgresql源码 6.7 配置环境变量 6.8 初始化数…...

rabbitmq集群搭建
升级步骤 1.升级包上传 1.1上传erlang、rabbitmq安装包 创建对应升级目录 将安装包otp_src_22.1.7.tar.gz上传到新创建的目录下 将安装包rabbitmq-server-generic-unix-3.8.9.tar.xz上传到新创建的目录下 1.2 执行解压命令tar -zxvf otp_src_22.1.7.tar.gz xz -d rabbitmq-s…...

C++ 二叉搜索树的概念特性
1. 二叉搜索树 1.1 二叉搜索树概念 二叉搜索树又称二叉排序树,它或者是一棵空树 ,或者是具有以下性质的二叉树 : 若它的左子树不为空,则左子树上所有节点的值都小于根节点的值 若它的右子树不为空,则右子树上所有节点的值都大…...

7、Spring_AOP
一、Spring AOP 简介 1.概述 对于spring来说,有三大组件,IOC,ID,AOP aop概述:AOP(Aspect Oriented Programming)面向切面编程。 作用:不改变原有代码设计的基础上实现功能增强 例子 传统打印日志 使用…...

QChart:数据可视化(用图像形式显示数据内容)
1、数据可视化的图形有:柱状/线状/条形/面积/饼/点图、仪表盘、走势图,弦图、金字塔、预测曲线图、关系图、数学公式图、行政地图、GIS地图等。 2、在QT Creator的主页面,点击 欢迎》示例》右侧输入框 输入Chart,即可查看到QChar…...

【python】Leetcode(primer-set)
文章目录 78. 子集(集合的所有子集)90. 子集 II(集合的所有子集) 更多 leetcode 题解可参考:【Programming】 78. 子集(集合的所有子集) 给定一组不含重复元素的整数数组 nums,返回…...

【LVS集群】
目录 一、集群概述 1.负载均衡技术类型 2.负载均衡实现方式 二、LVS结构 1.三层结构 2.架构对象 三、LVS工作模式 四、LVS负载均衡算法 1.静态负载均衡 2.动态负载均衡 五、ipvsadm命令详解 1. -A 2. -D 3. -L 4. -a 5. -d 6. -l 7. -t 8. -s 9. -r 10. -…...