- 同步阻塞:马上专心做一件事情
- 同步非阻塞:一边做一件事情,一边做另一件事情(一心二用)
- 异步阻塞:把问题推到以后专心处理
- 异步非阻塞:把问题推到以后时不时处理一下
同步读写
写
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;
}
案例
智能指针
- https://geek-docs.com/cpp/cpp-tutorial/smart-pointer.html
- https://www.cnblogs.com/wxquare/p/4759020.html
- https://blog.csdn.net/cpp_learner/article/details/118912592
服务端
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是异步写的函数,这个异步写函数有两个参数:
- 第一个参数为ConstBufferSequence常引用类型的buffers,
- 第二个参数为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;
}