【boost网络库从青铜到王者】第四篇:asio网络编程中的socket同步读(接收)写(发送)

1、asio中的同步发送write_some()

boost::asio中的write_some() 源码。

  template <typename ConstBufferSequence>
  std::size_t write_some(const ConstBufferSequence& buffers)
  {
    
    
    boost::system::error_code ec;
    std::size_t s = this->impl_.get_service().send(
        this->impl_.get_implementation(), buffers, 0, ec);
    boost::asio::detail::throw_error(ec, "write_some");
    return s;
  }

boost::asio提供了几种同步写的apiwrite_some() 可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。write_some()Boost.Asio 库中用于同步写入数据到套接字的函数之一。它可以用来将数据块写入到套接字,并在数据写入一部分之后立即返回。这个函数适用于阻塞式的 I/O 操作,因此在数据全部写入之前,函数可能会阻塞当前线程。

void BoostAsio::WriteSomeData(boost::asio::ip::tcp::socket& socket) {
    
    
	std::string buff("hello world");
	std::size_t total_write_bytes = 0;

	//循环发送
	//write_some返回每次写入的字节数
	//total_bytes_written是已经发送的字节数。
	//每次发送buf.length()- total_bytes_written字节数据
	while (total_write_bytes != buff.length()) {
    
    
		total_write_bytes = total_write_bytes + socket.write_some(boost::asio::buffer(buff.c_str() + total_write_bytes, buff.length() - total_write_bytes));
	}
}

int32_t BoostAsio::SendDataByWriteSome(std::string& raw_ip_address, uint16_t& port_num) {
    
    
	try {
    
    
		//Step 1: create endpoint
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);

		//Step 2: create socket
		boost::asio::io_context context;
		boost::asio::ip::tcp::socket socket(context, ep.protocol());

		//Step 3: socket connect endpoint
		socket.connect(ep);

		//Step 4: write_some data
		WriteSomeData(socket);
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured!Error code: " << e.code() << " .Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

这段代码展示了如何循环使用 write_some() 函数将数据发送到套接字。让我对你的代码进行解释:在这个示例中,函数 WriteToSocket 接受一个已连接的 Boost.Asio TCP 套接字 socket,并使用 write_some() 循环发送数据。

  • 循环的逻辑如下:

    • 创建一个包含字符串 “hello world”std::string 对象 buff
    • 初始化 total_write_bytes 为 0,用于跟踪已发送的字节数。
  • 在循环内部:

    • boost::asio::buffer(buff.c_str() + total_write_bytes, buff.length() - total_write_bytes) 创建一个缓冲区,每次发送剩余的未发送数据。
    • socket.write_some() 返回实际写入的字节数,并将其累加到 total_write_bytes 中。
  • 在 SendDataByWriteSome 函数中:

    • 创建了一个 boost::asio::ip::tcp::endpoint,表示服务器的地址和端口号。
    • 创建了一个 boost::asio::ip::tcp::socket,使用提供的协议创建套接字。
    • 通过 socket.connect(ep) 连接到服务器。
    • 调用 WriteSomeData(socket) 函数来使用 write_some() 循环发送数据。

这个代码示例中的 WriteSomeData() 函数负责循环发送数据,通过不断使用 write_some() 来发送数据块。需要注意的是,write_some() 可能会在每次写入时阻塞线程,这可能会影响性能,特别是在需要大规模数据传输时。在这种情况下,可能需要考虑使用异步的方式来进行写入操作,以避免阻塞线程。

2、asio中的socket中的同步发送send() 可以在一次性同步发送所以数据出去

boost::asio中的socket中的send源码:

  template <typename ConstBufferSequence>
  std::size_t send(const ConstBufferSequence& buffers)
  {
    
    
    boost::system::error_code ec;
    std::size_t s = this->impl_.get_service().send(
        this->impl_.get_implementation(), buffers, 0, ec);
    boost::asio::detail::throw_error(ec, "send");
    return s;
  }

write_some() 使用起来比较麻烦,需要多次调用,asio提供了send() 函数。send()函数会一次性将buffer中的内容发送给对端,如果有部分字节因为发送缓冲区满无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成。

int32_t BoostAsio::SendDataBySend(std::string& raw_ip_address, uint16_t& port_num) {
    
    
	try {
    
    
		//Step 1: create endpoint
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);

		//Step 2: create socket
		boost::asio::io_context context;
		boost::asio::ip::tcp::socket socket(context, ep.protocol());

		//Step 3: socket connect endpoint
		socket.connect(ep);

		//Step 4: write data
		std::string buffer("hello world");
		int32_t bytes=socket.send(boost::asio::buffer(buffer.c_str(), buffer.length()));
		if (0 >= bytes) {
    
    
			std::cout << "Send data failed!" << std::endl;
			return -1;
		}
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured!Error code: " << e.code() << ". Message" << e.what();
		return e.code().value();
	}
	return 0;
}

这段代码展示了如何使用 Boost.Asio 进行 TCP 数据发送,包括创建套接字、连接到服务器,并使用 send() 函数来发送数据。我会对这段代码进行解释:

  • SendDataBySend() 函数中:
    • 创建了一个 boost::asio::ip::tcp::endpoint,表示服务器的地址和端口号。
    • 创建了一个 boost::asio::ip::tcp::socket,使用提供的协议创建套接字。
    • 通过 socket.connect(ep) 连接到服务器。
    • 使用 send() 函数发送数据。boost::asio::buffer(buffer.c_str(), buffer.length()) 构造了一个缓冲区,用于发送 buffer 中的数据。

需要注意的是,send() 函数会阻塞当前线程直到所有数据被发送出去。在某些情况下,可能需要在发送之前设置套接字的发送选项,例如设置非阻塞模式。 此外,与使用循环的 write_some() 不同,send() 函数在一次调用中将数据一次性发送出去。

无论是使用循环的 write_some() 还是一次性的 send(),你都需要根据具体情况来选择合适的发送方式,以及是否考虑异步的发送操作。

3、asio中的write()发送数据

asio中的write() 源码:

template <typename SyncWriteStream, typename ConstBufferSequence>
inline std::size_t write(SyncWriteStream& s, const ConstBufferSequence& buffers,
    typename constraint<
      is_const_buffer_sequence<ConstBufferSequence>::value
    >::type)
{
    
    
  boost::system::error_code ec;
  std::size_t bytes_transferred = write(s, buffers, transfer_all(), ec);
  boost::asio::detail::throw_error(ec, "write");
  return bytes_transferred;
}

类似send() 方法,asio还提供了一个write() 函数,可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成。

int32_t BoostAsio::SendDataByAsioWrite(std::string& raw_ip_address, uint16_t& port_num) {
    
    
	try {
    
    
		//Step 1: create endpoint
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);

		//Step 2: create socket
		boost::asio::io_context context;
		boost::asio::ip::tcp::socket socket(context, ep.protocol());

		//Step 3: connect endpoint
		socket.connect(ep);

		//Step 4: send data;
		std::string buff("hello world");
		int32_t bytes=boost::asio::write(socket, boost::asio::buffer(buff.c_str(), buff.length()));
		if (0 >= bytes) {
    
    
			std::cout << "Send data failed!" << std::endl;
			return -1;
		}
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured!Error code : " << e.code().value() << ". Message :" << e.what();
		return e.code().value();
	}
	return 0;
}

在这段代码示例演示了如何使用 Boost.Asiowrite() 函数来进行 TCP 数据发送,这个函数可以更方便地将数据发送到套接字。我会解释一下你的代码:

  • SendDataByAsioWrite() 函数中:
    • 创建了一个 boost::asio::ip::tcp::endpoint,表示服务器的地址和端口号。
    • 创建了一个 boost::asio::ip::tcp::socket,使用提供的协议创建套接字。
    • 通过 socket.connect(ep) 连接到服务器。
    • 使用 boost::asio::write() 函数发送数据。boost::asio::buffer(buff.c_str(), buff.length()) 构造了一个缓冲区,用于发送 buff 中的数据。

与之前的例子相比,write() 函数的优势在于它可以一次性地将数据全部发送出去,而不需要手动迭代循环发送。此外,write() 函数可以将数据发送到套接字上,无需显式传递套接字作为参数,因为你已经在创建函数体内部的上下文中定义了套接字 socket。

需要注意的是,无论是 write() 还是其他发送方法,都应该考虑处理可能的错误情况,例如套接字连接失败或数据发送失败。

4、asio中的同步接收read_some()

read_some的源代码:

  template <typename MutableBufferSequence>
  std::size_t read_some(const MutableBufferSequence& buffers)
  {
    
    
    boost::system::error_code ec;
    std::size_t s = this->impl_.get_service().receive(
        this->impl_.get_implementation(), buffers, 0, ec);
    boost::asio::detail::throw_error(ec, "read_some");
    return s;
  }

同步读和同步写类似,提供了读取指定字节数的接口read_some

std::string BoostAsio::ReadSomeData(boost::asio::ip::tcp::socket& socket) {
    
    
	const unsigned char SIZE = 10;
	char buff[SIZE];
	std::size_t total_read_bytes = 0;
	while (total_read_bytes != SIZE) {
    
    
		total_read_bytes = total_read_bytes + socket.read_some(boost::asio::buffer(buff + total_read_bytes, SIZE - total_read_bytes));
	}
	//C++ 非常量引用的初始值必须是左值
	return std::string(buff, total_read_bytes);
}

int32_t BoostAsio::RecvDataByReadSome(std::string& raw_ip_adress, uint16_t& port_num) {
    
    
	try {
    
    
		//Step 1: create endpoint
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_adress), port_num);

		//Step 2: create socket
		boost::asio::io_context context;
		boost::asio::ip::tcp::socket socket(context, ep.protocol());    //ep.protocol()可不写

		//Step 3: socket connect endpoint
		socket.connect(ep);

		//Step 4: receive data
		std::cout << ReadSomeData(socket) << std::endl;
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured!Error code : " << e.code().value() << ". Message :" << e.what();
		return e.code().value();
	}
	return 0;
}
  • RecvDataByReadSome 函数中:
    • 创建了一个 boost::asio::ip::tcp::endpoint,表示服务器的地址和端口号。
    • 创建了一个 boost::asio::ip::tcp::socket,使用提供的协议创建套接字。
    • 通过 socket.connect(ep) 连接到服务器。
    • 调用 ReadSomeData(socket) 函数循环接收数据,并将接收到的数据输出。

需要注意的是,ReadSomeData() 函数使用了 read_some() 函数来循环接收数据。但是,read_some() 可能会阻塞当前线程,直到至少有一个字节的数据到达。在实际应用中,可能需要考虑使用异步的方式来进行数据接收,以避免阻塞线程。此外,你的代码中固定了接收数据的大小为 SIZE,如果实际接收到的数据不足 SIZE 字节,可能会导致问题。最好的方式是根据实际情况动态地处理接收数据的大小。

5、asio中的socket中的同步接收receive()可以一次性同步接收对方发送的数据

receive() 源代码 :

  template <typename MutableBufferSequence>
  std::size_t receive(const MutableBufferSequence& buffers)
  {
    
    
    boost::system::error_code ec;
    std::size_t s = this->impl_.get_service().receive(
        this->impl_.get_implementation(), buffers, 0, ec);
    boost::asio::detail::throw_error(ec, "receive");
    return s;
  }

可以一次性同步接收对方发送的数据:

int32_t BoostAsio::RecvDataByReceive(std::string& raw_ip_address, uint16_t& port_num) {
    
    
	try {
    
    
		//Step1: create endpoint
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);

		//Step2: create socket
		boost::asio::io_context context;
		boost::asio::ip::tcp::socket socket(context, ep.protocol());

		//Step3: socket connect endpoint
		socket.connect(ep);

		//Step4: receive data
		const unsigned char SIZE = 10;
		char buff[SIZE];
		int32_t recv_size = socket.receive(boost::asio::buffer(buff, SIZE));
		if (0 >= recv_size) {
    
    
			std::cout << "receive data failed!" << std::endl;
			return -1;
		}
		std::cout << buff << std::endl;
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured!Error code: " << e.code().value() << " . Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

这段代码展示了如何使用 Boost.Asio 进行 TCP 数据接收,包括创建套接字、连接到服务器,并使用 receive() 函数接收数据。我会解释这段代码:

  • RecvDataByReceive 函数中:

    • 创建了一个 boost::asio::ip::tcp::endpoint,表示服务器的地址和端口号。
    • 创建了一个 boost::asio::ip::tcp::socket,使用提供的协议创建套接字。
    • 通过 socket.connect(ep) 连接到服务器。
    • 使用 receive() 函数接收数据。boost::asio::buffer(buff, SIZE) 构造了一个缓冲区,用于接收数据。

需要注意的是,receive() 函数会阻塞当前线程直到至少有一个字节的数据到达。与之前的例子类似,你可能需要考虑使用异步的方式来进行数据接收,以避免阻塞线程。另外,确保你适当地处理接收到的数据大小,以避免接收到的数据超出缓冲区大小。

6、asio中的read()接收数据

**read()**函数源码:

template <typename SyncReadStream, typename MutableBufferSequence>
inline std::size_t read(SyncReadStream& s, const MutableBufferSequence& buffers,
    typename constraint<
      is_mutable_buffer_sequence<MutableBufferSequence>::value
    >::type)
{
    
    
  boost::system::error_code ec;
  std::size_t bytes_transferred = read(s, buffers, transfer_all(), ec);
  boost::asio::detail::throw_error(ec, "read");
  return bytes_transferred;
}

可以一次性同步读取对方发送的数据:

int32_t BoostAsio::RecvDataByRead(std::string& raw_ip_address, uint16_t& port_num) {
    
    
	try {
    
    
		//Step1: create endpoint
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);

		//Step2: create socket
		boost::asio::io_context context;
		boost::asio::ip::tcp::socket socket(context, ep.protocol());

		//Step3: socket connect endpoint
		socket.connect(ep);

		//Step: read data
		const unsigned char SIZE = 10;
		char  buff[SIZE];
		int32_t length = boost::asio::read(socket, boost::asio::buffer(buff, SIZE));
		if (0 >= length) {
    
    
			std::cout << "Read data failed!" << std::endl;
			return -1;
		}
		std::cout << buff << std::endl;
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured!Error code: " << e.code().value() << ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

这段代码示例展示了如何使用 Boost.Asio 进行 TCP 数据接收,包括创建套接字、连接到服务器,并使用 read() 函数接收数据。我会解释这段代码:

  • RecvDataByRead() 函数中:

    • 创建了一个 boost::asio::ip::tcp::endpoint,表示服务器的地址和端口号。
    • 创建了一个 boost::asio::ip::tcp::socket,使用提供的协议创建套接字。
    • 通过 socket.connect(ep) 连接到服务器。
    • 使用 read() 函数接收数据。boost::asio::buffer(buff, SIZE) 构造了一个缓冲区,用于接收数据。

与之前的例子类似,read() 函数会阻塞当前线程直到接收到足够的数据。如果需要避免阻塞线程,你可以考虑使用异步的方式进行数据接收。另外,确保适当地处理接收到的数据大小,以避免数据溢出。

7、 Boost.Asio 网络库中socket.send() 和 boost::asio::write() 以及socket.recevie() 和 boost::asio::read()区别

Boost.Asio 网络库中,socket.send()boost::asio::write()socket.receive()boost::asio::read() 都是用于进行数据发送和接收的函数,但它们之间有一些区别。我会为你解释这些区别:

  • 发送数据:

    • socket.send():

      • socket.send() 是套接字类(如 boost::asio::ip::tcp::socket)的成员函数,用于将数据发送到套接字连接的对端。
      • 返回实际发送的字节数,可能小于请求发送的总字节数。
      • 是同步的,会阻塞当前线程,直到数据发送完成或出现错误。
    • boost::asio::write():

      • boost::asio::write()Boost.Asio 提供的独立函数,用于将数据写入到指定的流(如套接字)中。
      • 返回实际写入的字节数,可能小于请求写入的总字节数。
      • 是同步的,会阻塞当前线程,直到数据写入完成或出现错误。
  • 接收数据:

    • socket.receive():

      • socket.receive() 是套接字类(如 boost::asio::ip::tcp::socket)的成员函数,用于从套接字连接的对端接收数据。
      • 返回实际接收的字节数,可能小于请求接收的总字节数。
      • 是同步的,会阻塞当前线程,直到有足够的数据接收完成或出现错误。
    • boost::asio::read():

      • boost::asio::read()Boost.Asio 提供的独立函数,用于从指定的流(如套接字)中读取数据。
      • 返回实际读取的字节数,可能小于请求读取的总字节数。
      • 是同步的,会阻塞当前线程,直到有足够的数据读取完成或出现错误。
  • 总结:

    • socket.send()boost::asio::write() 都是用于发送数据的函数,前者是套接字成员函数,后者是独立函数。
    • socket.receive()boost::asio::read() 都是用于接收数据的函数,前者是套接字成员函数,后者是独立函数。
    • 所有这些函数都是同步的,会阻塞线程,直到操作完成或出现错误。
    • 使用哪个函数取决于你的需求和代码结构,以及你希望更灵活地管理数据的读取和写入。

8、asio中的read_util()函数

read_util() 源码:

template <typename SyncReadStream, typename Allocator>
inline std::size_t read_until(SyncReadStream& s,
    boost::asio::basic_streambuf<Allocator>& b, char delim)
{
    
    
  return read_until(s, basic_streambuf_ref<Allocator>(b), delim);
}

这段代码是 Boost.Asio 库中 read_until() 函数的一个实现,用于从输入流中读取数据直到遇到指定的分隔符字符。这个函数的目的是读取数据并将其存储到一个 boost::asio::basic_streambuf 缓冲区中,直到遇到指定的分隔符。

  • 解释每个部分:
    • **template <typename SyncReadStream, typename Allocator>:**这是一个函数模板,它有两个模板参数。SyncReadStream 是一个同步读取流的类型(比如套接字),Allocator 是一个用于分配内存的分配器类型。

    • inline std::size_t read_until(SyncReadStream& s, boost::asio::basic_streambuf& b, char delim): 这是函数的签名。它接受三个参数:一个同步读取流 s,一个 boost::asio::basic_streambuf 缓冲区 b,以及一个指定的分隔符字符 delim

    • return read_until(s, basic_streambuf_ref(b), delim): 这是函数体的内容。它调用另一个 read_until 函数,并将传递的参数进行适配。basic_streambuf_ref 是一个用于将 basic_streambuf 转换为适当类型的辅助类。

总体上,这段代码提供了一个便捷的方式,允许你通过指定的分隔符从给定的同步读取流(例如套接字)中读取数据并存储在 boost::asio::basic_streambuf 缓冲区中。这个函数的实现内部会处理适当的转换,以调用实际的 read_until 函数。

我们可以一直读取,直到读取指定字符结束:

std::string BoostAsio::ReadDataByUtil(boost::asio::ip::tcp::socket& socket) {
    
    
	boost::asio::streambuf buff;

	// Synchronously read data from the socket until
	 // '\n' symbol is encountered.  
	boost::asio::read_until(socket, buff, '\n');

	std::string message;

	// Because buffer 'buf' may contain some other data
	// after '\n' symbol, we have to parse the buffer and
	// extract only symbols before the delimiter. 

	std::istream input_stream(&buff);
	std::getline(input_stream, message);

	return message;
}
  • 步骤解释:

    • 首先,你创建了一个 boost::asio::streambuf 对象 buff,用于存储从套接字读取的数据。
    • 然后,使用 boost::asio::read_until 函数从套接字中同步读取数据,直到遇到 \n 符号为止,并将读取的数据存储在 buff 中。
    • 接着,你创建了一个空的 std::string 对象 message,稍后将用于存储从缓冲区中提取的数据。
    • 因为缓冲区 buff 可能包含在 \n 符号后的其他数据,所以你创建了一个 std::istream 输入流对象 input_stream,将缓冲区 buff 传递给它,以便从中读取数据。
    • 最后,你使用 std::getline 函数从输入流中读取一行数据,存储在 message 字符串中,即提取了分隔符 \n 之前的字符。
    • 最终,函数返回读取的消息字符串。

这个函数的目的是从套接字中读取一行数据,以 \n 符号作为分隔符。返回的消息字符串将包含分隔符之前的所有字符。

9、TCP和UDP区别

TCP(Transmission Control Protocol)和UDP(User Datagram Protocol) 是两种常见的传输层协议,用于在计算机网络中传输数据。它们在功能和特性上有很多区别,以下是它们的主要区别:

  • 连接性与无连接性:

    • TCP是一种面向连接的协议。在通信前,必须通过握手建立连接,然后进行数据传输,最后通过释放连接来结束通信。
    • TCP提供可靠的、有序的数据传输,以及流量控制和拥塞控制。
    • UDP是一种无连接的协议。通信时不需要事先建立连接,每个数据包都是独立的。UDP不提供可靠性保证,数据传输可能会丢失、重复或乱序。
  • 可靠性:

    • TCP提供可靠的数据传输。它通过确认、重传丢失的数据、以及按序传输来确保数据的完整性和正确性。
    • UDP不提供可靠性保证。发送的数据包可能会在传输过程中丢失,也可能以不同的顺序到达接收方。
  • 顺序性:

    • TCP提供有序的数据传输。数据在传输过程中会按照发送的顺序到达接收方。
    • UDP不保证数据的顺序,不同的数据包可能以不同的顺序到达接收方。
  • 延迟和效率:

    • TCP通常会引入较高的传输延迟,因为它需要进行握手、重传、流量控制等操作,以确保数据的可靠性和有序性。
    • UDP具有较低的传输延迟,适用于需要实时性的应用,如音频和视频传输。
  • 适用场景:

    • TCP适用于需要可靠传输的应用,如网页浏览、文件下载、电子邮件等。它适用于对数据完整性和顺序性要求较高的场景。
    • UDP适用于实时性要求较高的应用,如音频、视频传输、在线游戏。它适用于能够容忍一些数据丢失的场景,重点在于快速传输。
  • 用途:

    • TCP通常用于那些对数据可靠性和有序性要求较高的应用,需要确保数据完整到达,如文件传输、网页浏览等。
    • UDP通常用于那些对实时性要求较高的应用,需要快速传输数据,如音频、视频、在线游戏等。

10、TCP服务器通信时候发送或者接收数据要一个缓冲区UDP不用缓冲区

TCP 和 UDP 在通信时的数据处理方式的确存在差异,其中一个显著的区别就是 TCP 在发送和接收数据时需要使用缓冲区,而 UDP 则不需要。

  • TCP:

    • TCP 是面向连接的协议,提供可靠的、有序的数据传输。
    • TCP 通信中,数据被视为一个字节流,而不是一个个独立的数据包。这意味着应用程序发送的数据可能会被分成多个 TCP 数据段进行传输。
    • 为了保证数据的可靠性、有序性以及流量控制,TCP 协议会在发送和接收数据时使用缓冲区进行数据的缓存、组合和处理。
  • UDP:

    • UDP 是无连接的协议,提供不可靠的数据传输。
    • UDP 通信中,每个数据包都是独立的,没有连接状态或顺序保证。 应用程序发送的每个数据包都是以数据报的形式进行传输。
    • 由于 UDP 不保证数据的可靠性和有序性,因此它不需要使用缓冲区进行数据的组合和处理。每个数据报在传输过程中都是独立的实体。

因此,TCP 在数据传输过程中需要使用缓冲区来处理数据的分段、重新组合和重传,以确保数据的可靠性和有序性。而 UDP 则不需要这样的缓冲区处理,因为它的设计目标更加注重实时性和轻量性。

11、std::getline用法

std::getlineC++ 标准库提供的一个函数,用于从输入流中读取一行文本并存储到一个字符串中。它的基本语法如下:

#include <iostream>
#include <string>

// ...

std::istream& getline(std::istream& is, std::string& str, char delim);

  • 其中:

    • is 是输入流对象,可以是 std::cin(标准输入)或其他任何输入流。
    • str 是存储读取行的字符串变量。
    • delim 是用于分隔行的字符,当遇到这个字符时,读取会停止。这个字符不会被包含在结果字符串中。

用法示例:

#include <iostream>
#include <string>

int main() {
    
    
    std::string line;
    
    std::cout << "Enter a line of text: ";
    std::getline(std::cin, line); // Read a line from standard input
    
    std::cout << "You entered: " << line << std::endl;
    
    return 0;
}

在这个示例中,程序会等待用户输入一行文本,然后使用 std::getline 从标准输入流中读取这一行,并将其存储在 line 字符串中。之后,程序会输出用户输入的内容。

在你之前提供的代码中,std::getline(input_stream, message) 会从 input_stream 中读取数据,直到遇到换行符(\n)为止,并将读取的数据存储在 message 字符串中。这样可以有效地从 boost::asio::streambuf 中提取一行数据。

12、输入流、输出流

输入流 (input stream) 和输出流 (output stream)C++ 标准库中用于从数据源读取数据和向数据目标写入数据的抽象概念。这些概念可以用于各种类型的数据源和目标,包括文件、字符串、键盘、套接字等。以下是一些常见的输入流和输出流:

  • 常见的输入流(用于从数据源读取数据):

    • std::cin: 标准输入流,从键盘读取数据。
    • std::ifstream: 输入文件流,从文件读取数据。
    • std::istringstream: 字符串输入流,从字符串读取数据。
    • std::wcin: 宽字符标准输入流,从键盘读取宽字符数据。
    • std::wifstream: 宽字符输入文件流,从文件读取宽字符数据。
    • 等等…
  • 常见的输出流(用于向数据目标写入数据):

    • std::cout: 标准输出流,向屏幕输出数据。
    • std::ofstream: 输出文件流,向文件写入数据。
    • std::ostringstream: 字符串输出流,将数据写入字符串。
    • std::wcout: 宽字符标准输出流,向屏幕输出宽字符数据。
    • std::wofstream: 宽字符输出文件流,向文件写入宽字符数据。
    • 等等…

关于为什么套接字 (socket) 被视为输入流,这涉及到套接字的通信模型。在网络通信中,套接字可以作为数据源和数据目标。例如,服务器可以通过套接字将数据发送给客户端,客户端可以通过套接字接收来自服务器的数据。从套接字读取数据的操作类似于从其他输入流(如文件输入流)读取数据。因此,套接字被视为输入流,用于从套接字中读取来自其他端点的数据。

总结起来,输入流和输出流是 C++ 标准库提供的一种抽象,用于处理从各种不同类型的数据源读取数据和向各种不同类型的数据目标写入数据的操作。套接字被视为输入流,因为它可以从远程端点读取数据。

套接字(socket)既可以是输入流,也可以是输出流,甚至可以同时兼具这两个角色,具体取决于你如何使用它。

在网络通信中,套接字是一种双向通信的工具,可以同时用于发送和接收数据。因此,套接字可以被视为既具有输入流特性(用于接收数据),又具有输出流特性(用于发送数据)。这种双向通信的特性使得套接字非常灵活,可以用于实现各种类型的通信模式。

当你从套接字中读取数据时(接收数据),你可以将套接字视为输入流,使用类似于输入流的操作,如 recv() 函数或 Boost.Asio 中的 read() 函数来读取数据。

当你向套接字发送数据时,你可以将套接字视为输出流,使用类似于输出流的操作,如 send() 函数或 Boost.Asio 中的 write() 函数来发送数据。

因此,套接字的角色取决于你是从中读取数据还是向其中写入数据。在网络通信中,套接字在发送和接收数据之间切换,以实现双向通信。

13、std::istream、std::ostream和std::iostream

std::istreamstd::iostream 都是 C++ 标准库中的类,用于处理输入流(input stream)操作,但它们在功能和用法上有一些不同。

  • std::istream:

    • std::istream 是一个抽象基类,用于表示输入流,它提供了一组通用的输入操作。
    • 你不能直接实例化 std::istream,但你可以使用其派生类,如 std::cin(标准输入)、std::ifstream(文件输入流)、 std::istringstream(字符串输入流)等。
    • std::istream 提供了输入运算符 >>getlinegetignore 等成员函数,用于从输入流中读取不同类型的数据。
  • std::iostream:

    • std::iostreamstd::istreamstd::ostream 的结合,它是输入流和输出流的合并。
    • std::iostream 类型可以同时处理输入和输出操作,因此它可以用于从某个数据源读取数据,并将结果写入到某个数据目标。
    • std::iostream 派生自 std::istreamstd::ostream,因此它继承了这两者的功能,允许你在同一个对象上进行输入和输出操作。

示例:

#include <iostream>

int main() {
    
    
    int number;
    
    // Using std::cin from std::istream
    std::cout << "Enter a number: ";
    std::cin >> number; // Read input from standard input
    
    // Using std::cout from std::ostream
    std::cout << "You entered: " << number << std::endl; // Output to standard output
    
    // Using std::iostream for both input and output
    std::iostream io_stream(std::cin.rdbuf(), std::cout.rdbuf());
    io_stream << "Hello, this is std::iostream!" << std::endl;
    
    return 0;
}

总之,std::istreamstd::iostream 都是用于输入流操作的类,但前者主要用于读取数据,后者则结合了输入和输出功能。

std::ostreamC++ 标准库中的一个抽象类,用于表示输出流。它定义了一组通用的输出操作,使得你可以将数据写入不同类型的数据目标,如屏幕、文件、字符串等。std::ostream 类是输出流操作的基类,它提供了各种方法来将数据写入输出流。

std::ostream 类提供了诸如 operator<<write 等成员函数,用于向输出流中写入数据。其中,operator<< 运算符被广泛用于将不同类型的数据写入输出流。这个运算符支持重载,因此你可以通过重载来自定义输出格式。

以下是一个示例,展示如何使用 std::ostream 来输出数据到标准输出(屏幕):

#include <iostream>

int main() {
    
    
    int number = 42;
    double pi = 3.14159;
    
    std::cout << "The number is: " << number << std::endl;
    std::cout << "The value of pi is: " << pi << std::endl;
    
    return 0;
}

在这个示例中,std::cout 是一个实现了 std::ostream 接口的输出流,它用于将数据写入标准输出(屏幕)。通过使用 operator<< 运算符,你可以将数据输出到输出流中。

需要注意的是,std::ostream 是一个抽象类,不能直接实例化。你可以使用其派生类,如 std::cout(标准输出)、std::ofstream(文件输出流)、std::ostringstream(字符串输出流)等,来进行实际的输出操作。

14、boost::asio::streambuf数据构造成std::istream或者std::ostream

  • boost::asio::streambufstd::istream 以及std::ostream都是 C++ 类库中的一部分,它们提供了不同的功能,但可以在某些情况下结合使用。
    • boost::asio::streambufBoost.Asio 库提供的一个类,用于管理缓冲区,特别是在异步网络通信中。它可以用于将接收的数据存储在缓冲区中,然后进一步处理。boost::asio::streambuf 提供了一些用于读取和写入数据的成员函数。

    • std::istreamC++ 标准库提供的一个类,用于处理输入流。它提供了一组通用的输入操作,使得你可以从不同的数据源读取数据,如键盘、文件、字符串等。

    • boost::asio::streambuf 对象中存储的数据可以通过构造一个 std::istream 对象,从而使得你可以使用标准的输入流操作来处理其中的数据。这在需要将网络通信接收的数据以输入流的方式处理时非常有用。你可以通过将 boost::asio::streambuf 的缓冲区传递给 std::istream 构造函数来实现。

#include <iostream>
#include <boost/asio.hpp>

int main() {
    
    
    boost::asio::streambuf buffer;
    std::ostream output_stream(&buffer);
    output_stream << "Hello, Boost.Asio and std::istream!";
    
    std::istream input_stream(&buffer);
    std::string data;
    input_stream >> data;
    
    std::cout << "Read from streambuf: " << data << std::endl;
    
    return 0;
}

在这个示例中,我们首先使用 boost::asio::streambuf 将数据写入缓冲区,然后通过构造一个 std::istream 对象,从缓冲区中读取数据,就像处理标准输入流一样。这允许我们在使用标准输入流操作的同时,与 Boost.Asio 结合处理网络通信的数据。

输出结果:

Read from streambuf: Hello,

代码中似乎存在一个小问题,导致输出结果可能不符合预期。问题出现在对 std::istream 使用 >> 运算符时。

std::istream 的默认行为是将输入按空格分隔为不同的单词,并使用这些单词填充变量。在你的代码中,当你使用 input_stream >> data 时,data 只会获取第一个单词 “Hello,”,而后面的内容 “Boost.Asio and std::istream!” 并没有被读取。

    std::string test;
    std::cin >> test;
    std::cout << test << std::endl;

输出结果:

nihao hi
nihao

在这里插入图片描述
为了读取整个行,你可以使用 std::getline() 函数来读取一行数据。这样,你就可以正确地读取整个输入。

修改后的代码如下:

#include <iostream>
#include <boost/asio.hpp>

int main() {
    
    
    boost::asio::streambuf buffer;
    std::ostream output_stream(&buffer);
    output_stream << "Hello, Boost.Asio and std::istream!";

    std::istream input_stream(&buffer);
    std::string data;
    std::getline(input_stream, data);  // 使用 std::getline() 读取整行数据

    std::cout << "Read from streambuf: " << data << std::endl;

    return 0;
}

输出结果:

Read from streambuf: Hello, Boost.Asio and std::istream!

在这里插入图片描述

    std::string test;
    std::getline(std::cin, test);
    std::cout << test << std::endl;

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_44918090/article/details/132301115