[boost network library from bronze to king] Part 6: socket asynchronous read (receive) write (send) in asio network programming

1 Introduction

This article introduces the asynchronous read and write operations and precautions of boost asio . In order to ensure that the knowledge is easy for readers to absorb, only the code fragments used by the api are introduced. Write a complete client and server program in the next section.

So we define a session class, which represents the management class that the server handles client connections

#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__

The session class defines a socket member variable, which is responsible for handling the connection reading and writing of the peer** (ip+port), and encapsulates the Connect** function:

#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;
}

Here is just a brief meaning. The following core introduces the use of asynchronous read and write APIs .

2. Asynchronously write void AsyncWriteSomeToSocketErr(const std::string& buffer)

Before the write operation, we first encapsulate a Buffer structure. It is used to manage the data to be sent and received. This structure includes the first address of the data field, the total length of the data, and the processed length (read length or written length)

Two constructors are written, two parameters are responsible for constructing the write node, and one parameter is responsible for constructing the read node.

#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_;
};

Next, add an asynchronous write and send data operation and a node responsible for sending write data to the 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__

insert image description here

The AsyncWriteSomeToSocketErr function is our encapsulated write operation. AsyncWriteSomeToSocketErr is a callback function for asynchronous write operations. Why are there three parameters? We can take a look at the asio source code:

  template <typename ConstBufferSequence,
      BOOST_ASIO_COMPLETION_TOKEN_FOR(void (boost::system::error_code,
        std::size_t)) WriteToken
          BOOST_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) token
        BOOST_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 is an asynchronous write function. This asynchronous write function has two parameters. The first parameter is the buffer of the constant reference type of ConstBufferSequence , which is to construct the buffer structure.

The second parameter is the WriteToken type, and WriteToken is defined above. It is a function object type, the return value is void , and the parameters are error_code and size_t . Therefore, in order to call the async_write_some function, we must also pass in a function that conforms to the WriteToken definition, that is The AsyncWriteSomeToSocketErr function we declare , the first two parameters are the parameters specified by WriteToken , and the third parameter is the smart pointer of Buffer , so that the life cycle of the Buffer data we send is guaranteed to be extended through the smart pointer .

Let's take a look at the specific implementation of the AsyncWriteSomeToSocketErr function:

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,参数占位符2
		std::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_));
	}
}

The function of this code is to realize the function of sending data asynchronously, mainly including two functions: AsyncWriteSomeToSocketErr and AsyncWriteSomeCallBackErr .

  • The function of the AsyncWriteSomeToSocketErr function is to put data into the send queue and trigger an asynchronous write operation. Specific steps are as follows:

    • First, use std::make_shared to create a Buffer object, which is used to store the data to be sent.
    • Then, use the socket_->async_write_some function to trigger an asynchronous write operation to write data to the socket. Here, you bind the callback function AsyncWriteSomeCallBackErr .
  • The AsyncWriteSomeCallBackErr function is the callback function after the asynchronous write operation is completed. Its main function is to process the result of the write operation, check if an error occurred, and continue to send the remaining data. Specific steps are as follows:

    • First, check the err parameter, if its value is not 0 , it means that there is an error in sending, then output the error message and return.
    • Then there is no error, check whether the number of bytes transferred bytes_transferred plus the number of bytes sent in the buffer_ object buffer_->GetCurLen() is less than the total data length buffer_->GetTotalLen() . If it is less than the total length, it means that there are remaining data to be sent.
    • If there is remaining data to be sent, update the number of bytes sent in the buffer_ object buffer_->SetCurLen(buffer_->GetCurLen() + bytes_transferred) , and then continue to trigger asynchronous write operations to send the remaining data. Here socket_->async_write_some is called again and the same callback function is bound to check and process again after the write operation is completed.
    • Overall, this code implements the logic of sending data asynchronously, ensuring the integrity and sending order of data. By using a callback function, you can handle logic after each write operation, including checking for errors, updating the number of bytes sent, and triggering the next write operation.

In the AsyncWriteSomeToSocketErr function, it is judged that if the number of bytes sent has not reached the total number of bytes to be sent, then update the length that the node has sent, and then calculate the remaining length to be sent. If there is data that has not been sent, call the async_write_some function again sent asynchronously.

But this function cannot be put into practical application, because the number of bytes sent returned by async_write_some callback function may not be the full length. For example, the total size of the TCP sending buffer is 8 bytes, but there are 3 bytes that have not been sent (the last time it was not sent), so the remaining space is 5 bytes.
insert image description here
At this time, we call async_write_some to send hello world! The actual length of sending is 5, that is, only hello is sent , and the rest of world ! Continue sending through our callback.

In the actual development scenario, the user is not clear about the multiplexing call of the underlying tcp. When the user wants to send data, he calls WriteToSocketErr, or calls WriteToSocketErr cyclically. Call WriteToSocketErr, because boost::asio encapsulates multiplexing models such as epoll and iocp. When the write event is ready, the data is sent, and the sent data is sent in the order of async_write_some calls, so the async_write_some called in the callback function may not be called in time.

For example, we have the following code:

//用户发送数据
AsyncWriteSomeToSocketErr("Hello World!");
//用户无感知下层调用情况又一次发送了数据
AsyncWriteSomeToSocketErr("Hello World!");

Then it is very likely that only Hello was sent for the first time , and the subsequent data was not sent, and Hello World was sent for the second time! Then World was sent!

So the data received by the peer is likely to be HelloHello World! World!

3. Asynchronously write void AsyncWriteSomeToSocket(const std::string& buffer)

So how to solve this problem, we can guarantee the sending order of the application layer through the queue. We define a sending queue in Session , and then redefine the correct asynchronous sending function and callback processing:

#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__

The bool variable send_padding_ is defined . If the variable is true , it means that a node has not finished sending, and if false , it means that the sending is completed. send_padding_ is used to cache the message protocol node to be sent, which is a queue.

We implement the asynchronous send function:

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();
	}
}

Function implementation:

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));
	}
}

The function of this code is to implement the logic of sending data asynchronously and ensuring the order of sending, mainly including two functions: AsyncWriteSomeToSocket and AsyncWriteSomeCallBack .

  • The function of the AsyncWriteSomeToSocket function is to put data into the sending queue and trigger an asynchronous write operation. Specific steps are as follows:

    • First, a new Buffer object (used to store the data to be sent) is inserted into the send_queue_queue .
    • Next, check whether there is any unsent data. If so, it means that it is still waiting for the completion of the previous asynchronous write operation, and returns directly.
    • If there is no unsent data, it means that the asynchronous sending operation can be triggered, use the socket_->async_write_some function to write the data to the socket, and bind the callback function AsyncWriteSomeCallBack .
  • The AsyncWriteSomeCallBack function is a callback function after the asynchronous write operation is completed. Its main function is to process the result of the write operation and continue to send the next data in the queue. Specific steps are as follows:

    • First, check the err parameter, if its value is not 0 , it means that there is an error in sending, then output the error message and return.
    • Then, take out the first element in the queue, which is a Buffer object, representing the data to be sent.
    • Next, update the sent bytes of this data send_data->SetCurLen(send_data->GetCurLen() + bytes_transferred) .
    • Then, check whether all the data has been sent, if not, continue to trigger the asynchronous write operation, and send the remaining data.
    • If the data has been sent, remove the data node from the queue and check whether there is another data in the queue.
    • If the queue is not empty, it means that there is still data to send, then take out the next data node, update the number of bytes sent, and trigger the next asynchronous write operation to send the next data.

This code is designed to ensure that data is sent in order, maintaining data integrity and order even when sent asynchronously. It also properly handles the error condition if an error is sent.

The async_write_some function cannot guarantee that the length sent every time the callback function is triggered is the total length, so we have to judge whether the sending data is completed in the callback function every time. asio provides a simpler sending function async_send , which is the length of the sending When the length we require is not reached, the callback will not be triggered, so when the callback function is triggered, either the sending error occurs or the sending is completed. The internal implementation principle is to help us continuously call async_write_some until the sending is completed, so async_send cannot be combined with async_write_some For mixed use, we encapsulate another sending function based on async_send.

4. Write void AsyncSendToSocket(const std::string& buffer) asynchronously

Function definition:

#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__

Function implementation:

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));
	}
}

The purpose of this code is to send data asynchronously, and call the callback function for processing after the sending is completed. This is similar to the logic of the code you mentioned before, but the async_send function is used instead of async_write_some , and there is no need to manually maintain the number of sent bytes.

  • The specific logic is as follows:

    • The AsyncSendToSocket function is used to wrap the data into a Buffer object and insert it into the sending queue send_queue_ .

    • Next, check whether there is already data waiting to be sent ( whether send_padding_ is true ), if yes, it means that it is still waiting for the completion of the previous asynchronous sending, and returns directly.

    • If there is no data waiting to be sent, call the socket_->async_send function to send asynchronously. This function will send the data to the socket and call the callback function AsyncSendCallBack after sending is complete .

    • In the AsyncSendCallBack callback function, first check the error code err , if it is not 0 , it means that there is an error in sending, output the error message and return.

    • If the sending is successful, pop the sent data from the sending queue (send_queue_.pop()) , and check whether the queue is empty. If the queue is empty, it means that there is no data to be sent, and setting send_padding_ to false means that there is no data to send.

    • If the queue is not empty, it means that there is still data to be sent, then take out the head element of the queue, that is, the next data to be sent, and then call socket_->async_send to send the data asynchronously again. This process will repeat until all the data in the queue has been sent.

Overall, this code implements the function of sending data asynchronously, guarantees the order of sending, and can also correctly handle errors during sending. The difference is that it uses the async_send function, which encapsulates the details of sending and makes sending data more convenient.

5. Asynchronous read void AsyncReadSomeToSocket(const std::string& buffer)

Next, we will introduce asynchronous read operations. Asynchronous read operations and asynchronous write operations are similar to async_read_some and async_receive functions. The length of the read data obtained by the callback function triggered by the former may be less than the total length required to be read, and the callback function triggered by the latter The length of data read is equal to the total length read.

First encapsulate a read function AsyncReadSomeToSocket based on async_read_some , and also add some variables in the declaration of the Session class:

Function definition:

#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__

Function implementation:

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;
	}
	//将数据投递到队列里交给逻辑线程处理,此处略去
	//如果读完了则将标记置为false
	recv_padding_ = false;
	recv_buffer_ = nullptr;
}

The main function of this code is to read data asynchronously, and call the callback function AsyncReadSomeCallBack to process the data after the reading is completed . The following is a detailed explanation of the code logic:

  • The AsyncReadSomeToSocket function is used to read data asynchronously. In this function, first check if recv_padding_ is true . If it is true , it means that the data is being read and returned directly to avoid repeated reading.

  • If recv_padding_ is false , it means that data can be read. At this time, create a Buffer object recv_buffer_ and initialize it to the data to be read.

  • Next, call the socket_->async_read_some function to read data asynchronously. This function will call the callback function AsyncReadSomeCallBack after the read is complete .

  • In the AsyncReadSomeCallBack callback function, first check the error code err , if it is not 0 , it means that there is an error in reading, output the error message and return.

  • If the read is successful, add the number of bytes read to the current length CurLen of recv_buffer_ . Then, check whether all the data has been read, that is, whether CurLen is less than TotalLen .

  • If it is not finished, continue to call the socket_->async_read_some function to continue to read the remaining data asynchronously until all the data is read.

  • If the reading is finished, set recv_padding_ to false , indicating that there is no data being read. Finally, clear the recv_buffer_ object so that new data can be read next time.

This code implements the logic of reading data asynchronously to ensure that the data is read and processed correctly. If the data is not fully read, it will continue to asynchronously read the remainder until the entire data has been read. If there is new data to read, AsyncReadSomeToSocket can be called again .

6. Asynchronous read void AsyncReceiveToSocket(const std::string& buffer)

We encapsulate a function that receives data based on async_receive :

Function declaration:

#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__

Function implementation:

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;
}

This code looks very similar to the code for reading data asynchronously mentioned earlier. It implements the logic of receiving data asynchronously. The following is a detailed explanation of the code:

  • The AsyncReceiveToSocket function is used to receive data asynchronously. First, it checks whether recv_padding_ is true . If it is true , it means that there is already data being read, and returns directly to avoid repeated reception.

  • If recv_padding_ is false , it means that data can be received. At this time, create a Buffer object recv_buffer_ and initialize it to the data to be received.

Next, call the socket_->async_receive function to receive data asynchronously. This function will call the callback function AsyncReceiveCallBack after receiving is completed .

In the AsyncReceiveCallBack callback function, first check the error code err , if it is not 0 , it means that there is an error in the reception, output the error message and return.

If the reception is successful, add the received bytes to the current length CurLen of recv_buffer_ . Then, set recv_padding_ to false , indicating that there is no data being received.

Finally, clear the recv_buffer_ object to receive new data next time.

This code implements the logic of receiving data asynchronously to ensure that the data is received and processed correctly. If the data is not fully received, it will continue to receive the rest asynchronously until the entire data is received. If there is new data to receive, call AsyncReceiveToSocket again .

Similarly, async_read_some and async_receive cannot be mixed, otherwise there will be logic problems.

7. Summary

Overall code declaration:

#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__

Overall code definition:

#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,参数占位符2
		std::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;
	}
	//将数据投递到队列里交给逻辑线程处理,此处略去
	//如果读完了则将标记置为false
	recv_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;
}

This article introduces the asynchronous read and write operations of boost asio . It is just code snippets and api encapsulation for everyone to understand. The next article uses these asynchronous apis to write an asynchronous server to show the effect of sending and receiving.

Guess you like

Origin blog.csdn.net/qq_44918090/article/details/132480687