同步与异步读写(持续更新)

  1. 同步阻塞:马上专心做一件事情
  2. 同步非阻塞:一边做一件事情,一边做另一件事情(一心二用)
  3. 异步阻塞:把问题推到以后专心处理
  4. 异步非阻塞:把问题推到以后时不时处理一下

同步读写

write_some方式

  • boost::asio提供了几种同步写的api,write_some可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。
void write_to_socket(boost::asio::ip::tcp::socket& sock)
{
	std::string buf = "Hello World!";
	std::size_t total_bytes_written = 0;
	//循环发送
	//write_some 返回每次写入字节数
	while (total_bytes_written != buf.length()) {
	//total_bytes_written是已经发送的字节数。
		total_bytes_written += sock.write_some(boost::asio::buffer(buf.c_str() + total_bytes_written,
			buf.length() - total_bytes_written));
	}
}
  • 流程
int send_data_by_write_some()
{
	std::string raw_ip_address = "192.169.1.124";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		write_to_socket(sock);
	}
	catch (boost::system::system_error& e) {
		std::cout << "失败,ip.address.Error_code = " << e.code() << "信息" << e.what();
		return e.code().value();

	}
	return 0;
}

send方式

  • write_some使用起来比较麻烦,需要多次调用,asio提供了send函数。send函数会一次性将buffer中的内容发送给对端,如果有部分字节因为发送缓冲区满无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成。
int send_data_by_write_some()
{
	std::string raw_ip_address = "192.169.1.124";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		//write_to_socket(sock);


		std::string buf = "Hello World!";
		int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			std::cout << "send failed" << std::endl;
			return 0;
		}


	}
	catch (boost::system::system_error& e) {
		std::cout << "失败,ip.address.Error_code = " << e.code() << "信息" << e.what();
		return e.code().value();

	}
	return 0;
}

write方式

  • 类似send方法,asio还提供了一个write函数,可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成。
int write_data_by_write_some()
{
	std::string raw_ip_address = "192.169.1.124";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		//write_to_socket(sock);
		std::string buf = "Hello World!";

		int send_length = boost::asio::write(sock,boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			std::cout << "send failed" << std::endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "失败,ip.address.Error_code = " << e.code() << "信息" << e.what();
		return e.code().value();

	}
	return 0;
}

三者区别

  • send ()和write_some () 都是用于发送数据的函数,但它们有一些区别。 send ()函数是一个异步函数,它会将数据写入缓冲区并立即返回,然后在后台发送数据。 而write_some ()函数是一个同步函数,它会一直阻塞,直到所有数据都被写入缓冲区。

read_some方式

  • 同步读和同步写类似,提供了读取指定字节数的接口read_some
std::string read_from_socket(boost::asio::ip::tcp::socket& sock) {
	//消息长度
	const unsigned char MESSAGE_SIZE = 7;
	char buf[MESSAGE_SIZE];
	std::size_t total_bytes_read = 0;
	while (total_bytes_read != MESSAGE_SIZE) {
		total_bytes_read += sock.read_some(boost::asio::buffer(buf + total_bytes_read, MESSAGE_SIZE - total_bytes_read));
	}
	return std::string(buf, total_bytes_read);
}


int read_data_by_some() {
	std::string raw_tcp_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_tcp_address),port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc,ep.protocol());
		sock.connect(ep);
		read_from_socket(sock);
	}
	catch (boost::system::system_error& e) {
		std::cout << "失败,ip.address.Error_code = " << e.code() << "信息" << e.what();
		return e.code().value();

	}
}

同步读receive

  • 可以一次性同步接收对方发送的数据
int read_data_by_some() {
	std::string raw_tcp_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_tcp_address),port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc,ep.protocol());
		sock.connect(ep);
		//read_from_socket(sock);
		const unsigned char BUF_SIZE = 7;
		char buf_receive[BUF_SIZE];
		int receive_length = sock.receive(boost::asio::buffer(buf_receive,BUF_SIZE));
		if (receive_length <= 0) {
			std::cout << " ERROR" << std::endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "失败,ip.address.Error_code = " << e.code() << "信息" << e.what();
		return e.code().value();

	}
}

read方式

  • 可以一次性同步读取对方发送的数据
int read_data_by_some() {
	std::string raw_tcp_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_tcp_address),port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc,ep.protocol());
		sock.connect(ep);
		//read_from_socket(sock);
		const unsigned char BUF_SIZE = 7;
		char buf_receive[BUF_SIZE];
		int receive_length = boost::asio::read(sock,boost::asio::buffer(buf_receive, BUF_SIZE));
		if (receive_length <= 0) {
			std::cout << " ERROR" << std::endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "失败,ip.address.Error_code = " << e.code() << "信息" << e.what();
		return e.code().value();

	}
}

读取直到指定字符

std::string read_data_by_until(asio::ip::tcp::socket& sock) {
	asio::streambuf buf;
	asio::read_until(sock, buf, '\n');
	std::string message;
	std::istream input_stream(&buf);
	std::getline(input_stream, message);
	return message;
}

案例

智能指针

服务端

  • void session(socket_ptr sock)是一个子线程函数。
  • void server_a(boost::asio::io_context &io_context, unsigned short port)提供主线程服务
#include<boost/asio.hpp>
#include<iostream>
#include<set>
#include<memory>
using namespace boost::asio::ip;
using namespace std;
const int MAX_LENGTH = 1024;
typedef std::shared_ptr<tcp::socket> socket_ptr;
std::set<std::shared_ptr<std::thread>> thread_set;
using namespace std;


void session(socket_ptr sock) {
	try {
		for (;;) {
			char data[MAX_LENGTH];
			memset(data, '\0', MAX_LENGTH);
			boost::system::error_code error;
			//size_t length = boost::asio::read(sock,boost::asio::buffer(data,MAX_LENGTH),error);
			//数据接收
			size_t length = sock->read_some( boost::asio::buffer(data, MAX_LENGTH), error);
			if (error == boost::asio::error::eof) {
				std::cout << "connection closed by peer" << std::endl;
				break;
			}
			else if(error) {
				throw boost::system::system_error(error);
			}
			std::cout << "receive" << sock->remote_endpoint().address().to_string() << std::endl;
			std::cout << "receive message is " << data << std::endl;
			//回传
			boost::asio::write(*sock, boost::asio::buffer(data, length));
		}
	}
	catch (exception &e) {
		std::cerr << "Exception in thread:" << e.what() << endl;
	}
}


void server_a(boost::asio::io_context &io_context, unsigned short port) {
	//新版服务端socket生成包含绑定
	tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));
	for (;;) {
		socket_ptr socket(new tcp::socket(io_context));
		a.accept(*socket);

		auto t = std::make_shared < std::thread>(session, socket);
		//利用引用计数规则防止线程未运行完就被清理
		thread_set.insert(t);
	}
}

int main() {
	try {
		boost::asio::io_context ioc;
		server_a(ioc, 10086);
		//防止在子线程未全部实现完时主线程退出
		for (auto& t : thread_set) {
			t->join();
		}
	}
	catch (std::exception e) {
		std::cerr << "Exception:" << e.what() << endl;
	}
}

异步读写

异步写

async_write_some异步写函数

    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))
  • async_write_some是异步写的函数,这个异步写函数有两个参数:
    1. 第一个参数为ConstBufferSequence常引用类型的buffers,
    2. 第二个参数为WriteToken类型,而WriteToken在上面定义了,是一个函数对象类型,返回值为void,参数为error_code和size_t,
没有队列的发送函数(数据顺序错乱)
  • 所以我们为了调用async_write_some函数也要传入一个符合WriteToken定义的函数,就是我们声明的WriteCallBackErr函数,前两个参数为WriteToken规定的参数,第三个参数为MsgNode的智能指针,这样通过智能指针保证我们发送的Node生命周期延长。
//没有队列时的发送函数()
void WriteCallBackErr(const boost::system::error_code& ec,
	std::size_t bytes_transferred, 
	std::shared_ptr<MsgNode> msg_node);
void WriteToSocketErr(const std::string buf);
  • 因为WriteCallBackErr函数为三个参数且为成员函数,而async_write_some需要的回调函数为两个参数,所以我们通过bind将三个参数转换为两个参数的普通函数。
void Session::WriteCallBackErr(const boost::system::error_code& ec, std::size_t bytes_transferred,
	std::shared_ptr<MsgNode> msg_node)
{
	if ((bytes_transferred + msg_node->_cur_len) < msg_node->_total_len) {
		//如果没有发送完
		//更新已发送长度
		_send_node->_cur_len += bytes_transferred;
		//更新发送起始位置和长度
		this->_socket->async_write_some(asio::buffer(_send_node->_msg+_send_node->_cur_len,_send_node->_total_len - _send_node->_cur_len),
			std::bind(&Session::WriteCallBackErr,this,std::placeholders::_1, std::placeholders::_2, _send_node)
		);

	}
}

void Session::WriteToSocketErr(const std::string buf)
{
	_send_node = make_shared<MsgNode>(buf.c_str(), buf.length());
	//异步发送数据
	this->_socket->async_write_some(asio::buffer(_send_node->_msg, _send_node->_total_len),
		std::bind(&Session::WriteCallBackErr, this, std::placeholders::_1,std::placeholders::_2,_send_node)
		);
}
  • 在WriteCallBackErr函数里判断如果已经发送的字节数没有达到要发送的总字节数,那么久更新节点已经发送的长度,然后计算剩余要发送的长度,如果有数据未发送完,再次调用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可能并没有被及时调用。

比如我们如下代码:

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

那么很可能第一次只发送了Hello,后面的数据没发完,第二次发送了Hello World!之后又发送了World!
所以对端收到的数据很可能是”HelloHello World! World!”

解决没有队列数据出错问题

我们可以通过队列保证应用层的发送顺序。我们在Session中定义一个发送队列,然后重新定义正确的异步发送函数和回调处理

void WriteCallBack(const boost::system::error_code& ec,std::size_t bytes_transferred);
void WriteToSocket(const std::string buf);

定义了bool变量_send_pending,该变量为true表示一个节点还未发送完。

  • _send_queue用来缓存要发送的消息节点,是一个队列。我们实现异步发送功能
void Session::WriteCallBack(const boost::system::error_code & ec, std::size_t bytes_transferred)
{
	if (ec.value()!=0) {
		std::cout << "error,code is" << ec.value() << ".Message is" << ec.message() << endl;
		return;
	}
	//取出队首元素
	auto& send_data = _send_queue.front();
	send_data->_cur_len += bytes_transferred;
	if (send_data->_cur_len < send_data->_total_len) {
		//为发送完成
		this->_socket->async_write_some(
			asio::buffer(send_data->_msg+send_data->_cur_len,send_data->_total_len-send_data->_cur_len),
			std::bind(&Session::WriteCallBack, this, std::placeholders::_1, std::placeholders::_2)
		);
		return;
	}
	_send_queue.pop();
	if (_send_queue.empty()) {
		_send_pending = false;
	}
	else {
		auto& send_data = _send_queue.front();
		this->_socket->async_write_some(
			asio::buffer(send_data->_msg + send_data->_cur_len, send_data->_total_len - send_data->_cur_len),
			std::bind(&Session::WriteCallBack, this, std::placeholders::_1, std::placeholders::_2)
		);
	}
}

	void Session::WriteToSocket(const std::string buf)
{
	//队列插入元素
	_send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));
	if (_send_pending) {
		//在队列有元素时上述已经插入队列,在发送函数中已经有一个循环回调来清空发送队列
		return;
	}
	//在队列没有元素时调用异步函数发送
	this->_socket->async_write_some(asio::buffer(buf),
		std::bind(&Session::WriteCallBack,this, std::placeholders::_1, std::placeholders::_2));
	_send_pending = true;
}

async_send发送

  • async_write_some函数不能保证每次回调函数触发时发送的长度为要总长度,这样我们每次都要在回调函数判断发送数据是否完成,asio提供了一个更简单的发送函数async_send,这个函数在发送的长度未达到我们要求的长度时就不会触发回调,所以触发回调函数时要么时发送出错了要么是发送完成了,其内部的实现原理就是帮我们不断的调用async_write_some直到完成发送,所以async_send不能和async_write_some混合使用,
void Session::WriteAllToSocket(const std::string& buf)
{
	_send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));
	if (_send_pending) {
		return;
	}
	//执行该函数表示该条数据已经发送完毕
	this->_socket->async_send(asio::buffer(buf),
		std::bind(&Session::WriteAllCallBack, this, std::placeholders::_1, std::placeholders::_2));
	_send_pending = true;

}

void Session::WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
	if (ec.value()) {
		std::cout << "error,code is" << ec.value() << ".Message is" << ec.message() << endl;
		return;
	}
	_send_queue.pop();
	if (_send_queue.empty()) {
		_send_pending = false;
		return;
	}
	else {
		auto& send_data = _send_queue.front();
		this->_socket->async_send(
			asio::buffer(send_data->_msg + send_data->_cur_len, send_data->_total_len - send_data->_cur_len),
			std::bind(&Session::WriteAllCallBack, this, std::placeholders::_1, std::placeholders::_2));
	}
}

异步读

  • 同样async_read_some和async_receive不能混合使用,否则会出现逻辑问题。

async_read_some函数

  • 回调函数:
void Session::ReadFromSocket()
{
	if (_recv_pending) {
		return;
	}
	_recv_node = std::make_shared<MsgNode>(RECVSIZE);
	_socket->async_read_some(asio::buffer(_recv_node->_msg,_recv_node->_total_len)
		,std::bind(&Session::ReadCallBack,this,std::placeholders::_1, std::placeholders::_2));
	_recv_pending = true;
}

void Session::ReadCallBack(const boost::system::error_code ec, std::size_t bytes_transferred)
{
	_recv_node->_cur_len += bytes_transferred;
	if (_recv_node->_cur_len < _recv_node->_total_len) {
		_socket->async_read_some(asio::buffer(_recv_node->_msg+_recv_node->_cur_len, _recv_node->_total_len-_recv_node->_cur_len)
			, std::bind(&Session::ReadCallBack, this, std::placeholders::_1, std::placeholders::_2));
		return;
	}
	_recv_pending = false;
	_recv_node = nullptr;
}

async_receive

void Session::ReadAllFromSocket()
{
	if (_recv_pending) {
		return;
	}
	_recv_node = std::make_shared<MsgNode>(RECVSIZE);
	_socket->async_receive(asio::buffer(_recv_node->_msg, _recv_node->_total_len)
		, std::bind(&Session::ReadAllCallBack, this, std::placeholders::_1, std::placeholders::_2));
	_recv_pending = true;
}

void Session::ReadAllCallBack(const boost::system::error_code ec, std::size_t bytes_transferred)
{
	_recv_node->_cur_len += bytes_transferred;
	_recv_node = nullptr;
	_recv_pending = false;
}

异步案例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值