buffer结构
在网络库中,buffer 是用于缓存接收和发送数据的结构。在 Boost.Asio 中,提供了两种 buffer 类型:asio::mutable_buffer 和 asio::const_buffer。它们表示的是一段连续的内存空间,其中首字节存储了后续数据的长度。
asio::mutable_buffer 用于写操作(例如发送数据),
asio::const_buffer 用于读操作(例如接收数据)。
尽管这两种 buffer 是内存区域的基础表示,它们并没有直接作为 Boost.Asio API 的参数出现。相反,Boost.Asio 引入了 MutableBufferSequence 和 ConstBufferSequence 的概念,它们是由多个 asio::mutable_buffer 或 asio::const_buffer 组成的缓冲区序列。这种设计允许将多个连续的内存区域组合在一起,作为一个整体传递给 API,从而提高效率,减少空间浪费。
具体而言,MutableBufferSequence 可以理解为一个由 std::vectorasio::mutable_buffer 组成的容器,存储多个可变的缓冲区。通过这种方式,Boost.Asio 允许更灵活地处理多个缓冲区,而不需要为每个缓冲区分配额外的空间。
为了简化用户对复杂缓冲区结构的使用,Boost.Asio 提供了 buffer() 函数。该函数能够接收多种类型的字节流,并返回适配器对象,如 asio::mutable_buffers_1 或 asio::const_buffers_1。
具体来说:
如果传递给 buffer() 函数的是一个只读类型(例如 const 数据),则返回一个 asio::const_buffers_1 类型的对象。
如果传递给 buffer() 函数的是一个可写类型,则返回一个 asio::mutable_buffers_1 类型的对象。
这两种类型,asio::const_buffers_1 和 asio::mutable_buffers_1,本质上是对 asio::mutable_buffer 和 asio::const_buffer 的适配器,它们实现了符合 MutableBufferSequence 和 ConstBufferSequence 概念的接口。因此,这些对象可以作为 Boost.Asio API 函数的参数使用。
简而言之,buffer() 函数为用户提供了一个简洁的方式来创建和管理缓冲区,以便在数据传输过程中高效地存储数据。
以下分别是几种构造buffer的方式
void use_const_buffer()
{
std::string buf = "hello world";
boost::asio::const_buffer asio_buf(buf.c_str(), buf.length());
std::vector<boost::asio::const_buffer> buffers_sequence;
buffers_sequence.push_back(asio_buf);
//send(buffers_sequence)
}
void use_buffer_str()
{
boost::asio::const_buffers_1 output_buf = boost::asio::buffer("hello world");
}
void use_buffer_array()
{
const size_t BUF_SIZE_BYTES = 20;
std::unique_ptr<char[]>buf(new char[BUF_SIZE_BYTES]);
auto input_buf = boost::asio::buffer(static_cast<char*>(buf.get()), BUF_SIZE_BYTES);
}
同步读写api
write_some
void write_to_socket(boost::asio::ip::tcp::socket& sock)
{
std::string buf = "hello world";
size_t total_bytes_writen = 0;
while (total_bytes_writen != buf.size())
{
total_bytes_writen += sock.write_some(boost::asio::buffer(buf.c_str() + total_bytes_writen, buf.size() - total_bytes_writen));
}
}
int send_data_by_write_some()
{
std::string ip = "127.0.0.1";
unsigned short port = 8888;
try
{
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port);
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 << "Error occured! Error code = " << e.code() << ".Message: " << e.what() << std::endl;
return e.code().value();
}
return 0;
}
write_some的返回值是真实写入的数据大小,由于write_some不能将数据一次性的写完,所以会采用循环的方式进行写操作
为什么write_some不能将数据全部读完?
- TCP 是一种流式协议,它并不保证一次调用 write_some 可以发送所有数据,因为发送的数据量可能大于网络发送的容量,或者发送的速度受网络带宽和延迟的限制。TCP 采用了流量控制机制,确保发送方不会过快地发送数据,造成接收方的缓冲区溢出。如果发送缓冲区的空间不足,write_some 会立即返回已成功写入的字节数,而不会阻塞,直到更多空间变得可用。这就要求应用程序要能够在多次调用中写入所有数据,直到数据完全发送完毕。
- 为了提高性能,避免阻塞程序,write_some 设计为尽可能快地将数据写入缓冲区。它不会阻塞等到数据完全发送完毕,而是会将数据部分写入并立即返回。这样可以在 I/O 操作期间不阻塞主线程,让程序能够在等待写入的同时继续进行其他任务。
- 操作系统会对每个套接字(socket)分配缓冲区,而这个缓冲区的大小是有限的。即使你调用了 write_some,操作系统的网络驱动可能只能容纳一部分数据。并且操作系统内部有一个 TCP发送缓冲区(send buffer),当这个缓冲区满时,write_some 就只能写入缓冲区中的一部分数据,剩余的数据需要等到缓冲区有空闲空间才能继续写入
send
由于write_some需要多次调用比较麻烦,asio提供了send函数用来一次性将buffer中的内容发送出去,如果有剩余字节因为TCP发送缓冲区满了未能成功发送,则程序会阻塞等待,直到发送缓冲区可用为止。该过程一直持续到所有内容发送完成
int send_data_by_send()
{
std::string ip = "127.0.0.1";
unsigned short port_num = 8888;
try
{
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port_num);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
std::string buf("hello world");
int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length()));
if (send_length <= 0)
{
return;
}
}
catch (boost::system::system_error& e)
{
std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what() << std::endl;
return e.code().value();
}
return 0;
}
write
write函数和send相似,也是可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成。
int send_data_by_write()
{
std::string ip = "127.0.0.1";
unsigned short port_num = 8888;
try
{
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port_num);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
std::string buf("hello world");
int send_length = boost::asio::write(sock, boost::asio::buffer(buf.c_str(),buf.length()));
if (send_length <= 0)
{
return;
}
}
catch (boost::system::system_error& e)
{
std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what() << std::endl;
return e.code().value();
}
return 0;
}
read_some
和write_some一样,read_some返回的是一次真实读到的数据大小,如果要一次性读完则需要返回调用该接口
int read_data_by_read_some()
{
std::string ip = "127.0.0.1";
unsigned short int port = 8888;
try
{
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
auto str = read_from_socket(sock);
std::cout << str << std::endl;
}
catch (boost::system::system_error& e)
{
std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what() << std::endl;
return e.code().value();
}
return 0;
}
receive
int read_data_by_receive()
{
std::string ip = "127.0.0.1";
unsigned short port = 8888;
try
{
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
const int SIZE = 1024;
char buf[SIZE];
int ret = sock.receive(boost::asio::buffer(buf, SIZE));
if (ret <= 0)
{
return;
}
}
catch (boost::system::system_error& e)
{
std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what() << std::endl;
return e.code().value();
}
return 0;
}
read
int read_data_by_read()
{
std::string ip = "127.0.0.1";
unsigned short port = 8888;
try
{
boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(ip), port);
boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
sock.connect(ep);
const int SIZE = 1024;
char buf[SIZE];
int read_length = boost::asio::read(sock,boost::asio::buffer(buf, SIZE));
}
catch (boost::system::system_error& e)
{
std::cout << "Error occured! Error code = " << e.code() << ".Message: " << e.what() << std::endl;
return e.code().value();
}
return 0;
}