网络编程(1)——同步读写api

news2024/9/28 1:53:49

一、day1

学习了服务器和客户端socket的建立、监听以及连接。

(1)socket的监听和连接

服务端
1)socket——创建socket对象。

2)bind——绑定本机ip+port。

3)listen——监听来电,若在监听到来电,则建立起连接。

4)accept——再创建一个socket对象给其收发消息。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息。

5)read、write——就是收发消息了。

对于客户端是这样的
客户端
1)socket——创建socket对象。

2)connect——根据服务端ip+port,发起连接请求。

3)write、read——建立连接后,就可发收消息了。

boost库网络编程的基本流程(阻塞):

1)终端节点创建

终端节点代表一个网络通信的端点,由 IP 地址和端口号组成。该函数创建一个 TCP 客户端的终端节点。如果我们是客户端,我们可以通过对端的ip和端口构造一个endpoint,用这个endpoint和其通信。

客户端端点的建立:

// 在客户端创建一个TCP/IP的终端(endpoint),将IP地址和端口号组合成一个用于通信的终端
int client_end_point() {
	std::string raw_ip_address = "127.4.8.1"; // 对端地址
	unsigned short port_num = 3333; // 对端端口号
	boost::system::error_code ec; // 错误码
	// 使用 boost::asio 库的 from_string 方法,将字符串形式的 IP 地址转换为 asio 支持的 IP 地址格式。
	// 如果转换失败,错误信息将存储在 ec 中。
	boost::asio::ip::address ip_address = boost::asio::ip::address::from_string(raw_ip_address, ec);
	if (ec.value() != 0) { // 转换失败
		std::cout << "Failed to parse the IP address. Error code = " << ec.value() << " .Message is " << ec.message();
		return ec.value();
	}

	// 创建一个 TCP 终端 ep,将 ip_address 和 port_num 绑定在一起。
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);

	return 0;
}

服务器端端点的建立:

// 在服务器端创建一个TCP/IP的终端(endpoint),使用IPv6地址的通配符::绑定本地的所有地址
int server_end_point() {
	unsigned short port_num = 3333; // 定义服务器端使用的端口号 3333
	/*
	 定义一个ip_address,使用boost::asio库的address_v6::any()方法创建一个IPv6地址的通配符,
	 这个地址可以绑定到本地的任何地址上。通配符地址 (:: in IPv6) 允许服务器绑定到所有的可用地址。
	 换句话说,服务器可以监听来自任何接口(有线连接、无线连接、虚拟接口等)的传入连接。
	 服务器不需要指定一个具体的 IP 地址来监听,而是监听所有可用的 IP 地址。
	*/
	boost::asio::ip::address ip_address = boost::asio::ip::address_v6::any();
	// 创建一个 TCP/IP终端ep,将ip_address和port_num绑定在一起, 服务器将在所有的IPv6地址上监听端口3333的连接
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
	return 0;
}

2)建立socket

客户端socket_v4的建立:

上下文iocontext->选择协议->生成socke->打开socket

// 客户端socket_v4的建立
int create_tcp_socket() {
	boost::asio::io_context ioc; // 声明用于管理异步操作的I/O上下文
	// 声明socket,使用前面定义的io_context(ioc)来管理其I/O操作
	boost::asio::ip::tcp::socket sock(ioc);  

	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4(); // 声明ipv4的协议
	boost::system::error_code ec; // 错误码
	sock.open(protocol, ec); // 判断是否创建成功
	if (ec.value() != 0) { // ec不为0时创建失败
		std::cout << "Failed to parse the IP address. Error code = " 
			<< ec.value() << " .Message is " << ec.message();
		return ec.value();
	}

	return 0;
}

上述socket只是通信的socket,需在服务端建立acceptor的socket,用于接收新的连接:

// 创建一个服务器用于接受客户端连接的TCP监听器
int create_acceptor_socket() {
	boost::asio::io_context ios;
	boost::asio::ip::tcp::acceptor acceptor(ios);  // 新版本截止到此已经成功,老版本还需下面的操作

	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4(); // 声明ipv4的协议
	boost::system::error_code ec; // 错误码
	acceptor.open(protocol, ec);
	if (ec.value() != 0) { // ec不为0时创建失败
		std::cout << "Failed to parse the IP address. Error code = "
			<< ec.value() << " .Message is " << ec.message();
		return ec.value();
	}

	return 0;

	// 生成一个acceptor,指定tcpv4协议,并接收所有发向3333端口的信息(新版本)
	// 创建 acceptor 并绑定它到一个特定的端点(IP 地址和端口)
	// boost::asio::ip::tcp::acceptor a(ios, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 3333));
}

3)绑定socket

对于acceptor类型的socket,服务器要将其绑定到指定的端点,所有连接这个端点的连接都可以被接收到

// 创建一个服务器TCP连接接收器(acceptor),将其绑定到一个指定的端点(IP 地址和端口)
int bind_acceptor_socket() {
	unsigned short port_num = 3333;
	boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num);
	boost::asio::io_context ios;
	boost::asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
	boost::system::error_code ec; // 将接收器绑定到之前定义的端点 ep(包括通配符 IP 地址和端口 3333)
	acceptor.bind(ep, ec);
	if (ec.value() != 0) { // ec不为0时创建失败
		std::cout << "Failed to parse the IP address. Error code = "
			<< ec.value() << " .Message is " << ec.message();
		return ec.value();
	}
	return 0;
}

4)连接指定的端点

作为客户端可以连接服务器指定的端点

// 创建TCP客户端socket,并尝试连接到指定的服务器端点(服务器的 IP 地址和端口)
int connect_to_end() {
	std::string raw_ip_address = "192.168.1.124"; // 服务器ip
	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 ios;
		// socket对象连接到指定的服务器端点
		boost::asio::ip::tcp::socket sock(ios, ep.protocol());
		// 这里客户端是连接到指定端点,但在服务器中是绑定
		sock.connect(ep);
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}

	return 0;
}

// 通过域名解析将主机名(服务器域名)转换为 IP 地址,然后尝试使用 TCP 套接字与服务器建立连接
int dns_connect_to_end() {
	std::string host = "llfc.club"; // 服务器域名
	std::string port_num = "3333"; // 服务器端口号
	boost::asio::io_context ios; // 上下文服务
	/*
	boost::asio::ip::tcp::resolver::query 用于指定 DNS 解析的查询参数。
	resolver_query 是一个查询对象,包含了主机名(host)和端口号(port_num)。
	boost::asio::ip::tcp::resolver::query::numeric_service 指定端口号是一个数字,
	而不是服务名称(例如,"http"、"ftp"等
	*/
	boost::asio::ip::tcp::resolver::query resolver_query(host, port_num, 
		boost::asio::ip::tcp::resolver::query::numeric_service); // 创建域名查询对象
	/*
	boost::asio::ip::tcp::resolver 是一个 DNS 解析器类,用于将域名解析为 IP 地址。
	resolver 是一个解析器对象,使用 io_context(ios)进行初始化。
	该解析器将用于执行 DNS 查询并获取服务器的 IP 地址。
	*/
	boost::asio::ip::tcp::resolver resolver(ios); // 创建解析器
	try {
		// 使用resolver.resolve(resolver_query)解析域名,返回一个迭代器it,
		// 指向与主机名和端口号匹配的端点列表
		boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(resolver_query);
		boost::asio::ip::tcp::socket sock(ios);
		// 使用解析到的端点列表连接到服务器。它会自动尝试列表中的每一个端点,
		// 直到成功连接或所有端点都尝试完毕
		boost::asio::connect(sock, it);
	}
	catch(boost::system::system_error& e){
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

5)服务器接收连接

当有客户端连接时,服务器需要接收连接

/ 创建一个 TCP 服务器端的接收器(acceptor),绑定到指定的 IP 地址和端口,并等待客户端连接。
// 一旦有新的客户端连接请求到达,服务器将接受这个连接
int accept_new_connection() {
	const int BACKLOG_SIZE = 30; // 监听队列的大小,服务器来不急监听时将链接放入队列
	unsigned short port_num = 3333;
	boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num); // 端点
	boost::asio::io_context ios;
	try {
		boost::asio::ip::tcp::acceptor acceptor(ios, ep.protocol());
		acceptor.bind(ep);
		acceptor.listen(BACKLOG_SIZE);
		// 模拟创建了一个 TCP socket 对象,用于表示与客户端的连接
		boost::asio::ip::tcp::socket sock(ios);
		// 阻塞式地等待并接受一个新的连接请求,并将新的连接绑定到 sock。一旦有新的客户端连接请求,
		// acceptor 会接受它,并将新连接绑定到 sock
		acceptor.accept(sock);
	}
	catch(boost::system::system_error& e){
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
	return 0;
}

第一日总结:

1.网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。利用三元组(ip地址,协议,端口)就可以标识网络的进程,网络中的进程通信可以利用这个标志与其它进程进行交互。

2.什么是socket? socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。

3.acceptor和socket有什么区别,分别有什么作用?

1)acceptor

作用acceptor 是一个服务器端的对象,用于接收来自客户端的连接请求。

功能:

  • 创建acceptor 对象通常在服务器端创建,它的作用是等待并接收客户端的连接请求。
  • 绑定: 通过 acceptor.bind(endpoint) 将其绑定到特定的 IP 地址和端口号,指定服务器要监听的地址。
  • 监听: 通过 acceptor.listen(backlog_size) 启动监听,backlog_size 是一个整数,表示连接请求队列的大小。
  • 接受连接: 通过 acceptor.accept(socket) 接受一个连接请求,并将连接绑定到一个新的 socket 对象上,socket 用于后续的数据传输。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息
boost::asio::ip::tcp::acceptor acceptor(io_context, endpoint);
acceptor.bind(endpoint);
acceptor.listen(backlog_size);
boost::asio::ip::tcp::socket socket(io_context);
acceptor.accept(socket);

2)socket

作用socket 是用于实际数据传输的对象。它代表了一个网络连接的端点。

功能:

  • 创建socket 对象在客户端和服务器端都可以创建。客户端用来发起连接,服务器端用来处理接收到的连接。
  • 连接: 在客户端,socket 通过 socket.connect(endpoint) 连接到服务器端的 acceptor
  • 接收和发送数据: 一旦建立连接,socket 可以用来发送和接收数据,使用方法如 socket.send() 和 socket.receive()
  • 关闭连接: 通过 socket.close() 关闭连接。
boost::asio::ip::tcp::socket socket(io_context);
socket.connect(endpoint);
boost::asio::write(socket, boost::asio::buffer("Hello, World!"));

二、day2

1)buffer

任何网络库都有提供buffer的数据结构,所谓buffer就是接收和发送数据时缓存数据的结构。

boost::asio提供了asio::mutable_buffer 和 asio::const_buffer这两个结构,他们是一段连续的空间,首字节存储了后续数据的长度。asio::mutable_buffer用于写服务,asio::const_buffer用于读服务。但是这两个结构都没有被asio的api直接使用。对于api的buffer参数,asio提出了MutableBufferSequence和ConstBufferSequence概念,他们是由多个asio::mutable_buffer和asio::const_buffer组成的。也就是说boost::asio为了节省空间,将一部分连续的空间组合起来,作为参数交给api使用。

我们可以理解为MutableBufferSequence的数据结构为std::vector<asio::mutable_buffer>

buffer的结构

每个vector存储的都是mutable_buffer的地址,每个mutable_buffer的第一个字节表示数据的长度,后面跟着数据内容。

这么复杂的结构交给用户使用并不合适,所以asio提出了buffer()函数,该函数接收多种形式的字节流,该函数返回asio::mutable_buffers_1 o或者asio::const_buffers_1结构的对象。如果传递给buffer()的参数是一个只读类型,则函数返回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()函数生成我们要用的缓存存储数据。比如boost的发送接口send要求的参数为ConstBufferSequence类型

template<typename ConstBufferSequence>
std::size_t send(const ConstBufferSequence & buffers);

如果将“Hello World”转换成该类型:

// ConstBufferSequence类型构造函数1
void use_const_buffer() {
	std::string buf = "Hello World!";
	// 首先将buf转换为c风格字符串并获取首地址,然后将长度传至asio_buf函数,构造const_buffer
	boost::asio::const_buffer asio_buf(buf.c_str(), buf.length());
        // 构造ConstBufferSequence类型
	std::vector< boost::asio::const_buffer> buffers_sequence;
	// 将构造的const_buffer放入const_buffer容器中
	buffers_sequence.push_back(asio_buf);
}

 实际中我们使用并没有这么复杂,简化上述函数,用buffer函数将其转化为send需要的参数类型,output_buf可以直接传递给send接口使用,充当ConstBufferSequence类型:

// ConstBufferSequence类型构造函数2
void use_buffer_str() {
        // buffer函数返回asio::mutable_buffers_1 o或者asio::const_buffers_1结构的对象
	boost::asio::const_buffers_1 optput_buf = boost::asio::buffer("hello world");
}

 

也可以将数组转换为send需要的类型(ConstBufferSequence):

// 使用动态分配的字符数组作为缓冲区,并使用Boost.Asio库将其包装成一个可用于异步 
// I / O 操作的缓冲区对象
void use_buffer_array() {
	// 使用无符号整数类型size_t作为缓冲区大小
	const size_t BUF_SIZE_BYTES = 20;
	// std::unique_ptr<char[]> 是一个智能指针,用于管理动态分配的数组的生命周期。它在不再需要时自动释放内存,
	// 避免了手动 delete[] 的需要
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	// boost::asio::buffer 函数将一个原始指针(在此例中是一个指向字符数组的指针)和其大小(以字节为单位)包装
	// 成一个可用于异步 I/O 操作的 boost::asio::mutable_buffer 对象
	// buf.get() 返回指向智能指针 buf 所管理的字符数组的原始指针,并将其转换为void*类型
	auto input_buf = boost::asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);
}

对于流式操作,我们可以用streambuf,将输入输出流和streambuf绑定,可以实现流式输入和输出:

void use_stream_buffer() {
    asio::streambuf buf;
    std::ostream output(&buf);
    // Writing the message to the stream-based buffer.
    output << "Message1\nMessage2";
    // Now we want to read all data from a streambuf
    // until '\n' delimiter.
    // Instantiate an input stream which uses our 
    // stream buffer.
    std::istream input(&buf);
    // We'll read data into this string.
    std::string message1;
    std::getline(input, message1);
    // Now message1 string contains 'Message1'.
}

2)同步写write_some

boost::asio提供了几种同步写的api,write_some可以每次向指定的空间(socket)写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。举个栗子,用户buffer发送缓冲区长度为5,TCP发送缓冲区长度为12,虽然下面的write_some一开始希望发送buf.length() - total_bytes_written = 12 - 0=12个长度,但是用户buffer只有5个,所以只能先发送5个,剩下的7个循环继续发送:

// 写函数
void write_to_socket(boost::asio::ip::tcp::socket& sock) {
	std::string buf = "Hello World!";
	std::size_t total_bytes_wtitten = 0; // 已发送的字节数
	// 循环发送
	// write_some 返回每次写入的字节数, 该函数的输入类型是ConstBufferSequence
	/*
	持续写入:使用 while 循环持续尝试写入数据,直到所有数据都被写入完毕。
	部分写入:sock.write_some 尝试将部分数据写入到 sock 中,这个方法不是一次性写入所有数据,
			  而是可能写入一部分,因此需要用 total_bytes_written 变量来跟踪已写入的字节数。
	循环写入:每次写入后,更新 total_bytes_written,使写入指针向前移动,直到所有数据被写入。
	*/
	while (total_bytes_wtitten != buf.length()) {
		// 举个栗子,用户buffer发送缓冲区长度为5,TCP发送缓冲区长度为12,虽然下面的write_some一开始希望
		// 发送buf.length() - total_bytes_written = 12 - 0=12个长度,但是用户buffer只有5个,所以只能
		// 先发送5个,剩下的7个循环继续发送
		total_bytes_wtitten += sock.write_some(boost::asio::buffer(buf.c_str() + total_bytes_wtitten,
			buf.length() - total_bytes_wtitten));
	}
}

// 客户端发送函数
int send_data_by_write_some() {
	std::string raw_ip_address = "127.0.0.1";
	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_service ios
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
                // 发送信息至ep端点
		write_to_socket(sock);
	}
	catch(boost::system::system_error& e){
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

3)同步写send

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

int send_data_by_send() {
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	std::string buf = "Hello World!";
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		//boost::asio::io_service ios
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		// 不使用write_to_socket(sock)函数,使用sock的send函数
		// 将buf长度为length的内容发送至socket,返回size_t类型,如果成功发送,
		// bytes_sent 可能等于 buf.length(),即所有数据都成功发送;如果返回值小于 
		// buf.length(),表示只有部分数据被发送;如果发送失败,send 将抛出一个 
		// boost::system::system_error 异常。
		// send 是一个同步操作,所以它会阻塞直到所有数据被成功发送或发生错误,
		// 这意味着在 send 操作完成之前,程序将不会继续执行后面的代码。
		int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length())); 
		// send_length < 0:出现系统错误
		// send_length == 0:对端关闭
		// send_length = buf.length() > 0:发送成功
		if (send_length <= 0) // 发送失败
			return 0;
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

4)同步写write

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

int send_data_by_write() {
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	std::string buf = "Hello World!";
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		//boost::asio::io_service ios
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		// 和sock.send()函数类似
		int send_length = boost::asio::write(sock, boost::asio::buffer(buf.c_str(), buf.length()));
		// send_length < 0:出现系统错误
		// send_length == 0:对端关闭
		// send_length = buf.length() > 0:发送成功
		if (send_length <= 0) // 发送失败
			return 0;
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

5)同步读read_some

同步读和同步写类似,提供了读取指定字节数的接口read_some,但read_some可能会被多次调用,比较麻烦。

// 使用 Boost.Asio 库从 TCP 套接字 sock 读取固定大小(MESSAGE_SIZE)的数据,
// 并返回一个 std::string 对象,表示读取到的数据
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_byter_read = 0;
	while (total_byter_read != MESSAGE_SIZE) {
		total_byter_read += sock.read_some(boost::asio::buffer(buf + total_byter_read, MESSAGE_SIZE - total_byter_read));
	}

	return std::string(buf, total_byter_read);
}

// 从一个指定的 TCP 服务器(在 127.0.0.1:3333 上)建立连接,并调用 read_from_socket 
// 函数来同步读取数据
int read_data_by_read_some() {
	std::string raw_ip_address = "127.0.0.1";
	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);
		// 调用同步读函数,读取来自连接套接字的数据
		read_from_socket(sock); // 同步读
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

6)同步读receive

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

int read_data_by_receive() {
	std::string raw_ip_address = "127.0.0.1";
	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);
		const unsigned char BUFF_SIZE = 7;
		char buffer_receive[BUFF_SIZE];
		// 接收数据,并获取接收到的字节数
		int receive_length = sock.receive(boost::asio::buffer(buffer_receive, BUFF_SIZE));
		if (receive_length <= 0) {
			std::cout << "receive failed " << std::endl;
		}
			
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

7)同步读read

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

int read_data_by_read() {
	std::string raw_ip_address = "127.0.0.1";
	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);
		const unsigned char BUFF_SIZE = 7;
		char buffer_receive[BUFF_SIZE];
		int receive_length = boost::asio::read(sock, boost::asio::buffer(buffer_receive, BUFF_SIZE));
		if (receive_length <= 0) {
			std::cout << "receive failed " << std::endl;
		}

	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();

		return e.code().value();
	}
}

8)读取直到指定字符

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

std::string  read_data_by_until(boost::asio::ip::tcp::socket& sock) {
	boost::asio::streambuf buf;
	// Synchronously read data from the socket until
	// '\n' symbol is encountered.  
	boost::asio::read_until(sock, buf, '\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(&buf);
	std::getline(input_stream, message);
	return message;
}

总结:

1.c++中,sock.receive和boost::asio::read有什么区别?

同:sock.receive 和 boost::asio::read 都用于网络编程中的数据接收操作

不同:1)sock.receive 相对比较简单,只是接收数据,并返回接收的数据长度或错误代码。它不具备高级功能,如超时处理、数据分片重组、异步操作等。boost::asio::read 提供了更高层次的功能封装。例如,它可以在接收到特定数量的字节后才返回结果,还可以支持异步操作 (async_read),使得程序能够同时处理多个 I/O 操作。2)sock.receive 本身是一个阻塞调用,如果需要非阻塞行为,需要使用特定的套接字选项来配置。boost::asio::read 支持同步和异步两种方式,通过 boost::asio::io_context 及回调机制,可以轻松实现异步 I/O 操作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2171873.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人脸遮挡检测系统源码分享

人脸遮挡检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

基于微信小程序的特色乡村综合展示平台设计与实现(源码+文档+讲解开发)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

C++入门基础知识89(实例)——实例14【创建各类三角形图案】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///C爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于创建各类三角形图案的相关内容&#xff…

怎样用云手机进行TikTok矩阵运营?

在运营TikTok矩阵时&#xff0c;许多用户常常面临操作复杂、设备过多等问题。如果你也感到操作繁琐&#xff0c;不妨考虑使用云手机。云手机具备丰富的功能&#xff0c;能够帮助电商卖家快速打造高效的TikTok矩阵。接下来&#xff0c;我们将详细解析这些功能如何提升你的运营效…

每日论文6—16ISCAS一种新型低电流失配和变化电流转向电荷泵

《A Novel Current Steering Charge Pump with Low Current Mismatch and Variation》16ISCAS 本文首先介绍了传统的current steering charge pump&#xff0c;如下图&#xff1a; 比起最简单的电荷泵&#xff0c;主要好处是UP和DN开关离输出节点较远&#xff0c;因此一定程度…

vue.js 原生js app端实现图片旋转、放大、缩小、拖拽

效果图&#xff1a; 旋转 放大&#xff1a;手机上可以双指放大缩小 拖拽 代码实现&#xff1a; html <div id"home" class"" v-cloak><!-- 上面三个按钮 图片自己解决 --><div class"headImage" v-if"showBtn">&l…

python控制台是什么意思

Python 控制台是一种执行命令的快速方法&#xff0c;可以访问完整的Python API、查询命令历史记录和自动补全。命令提示符是 Python 3.x 的典型操作&#xff0c;加载解释器&#xff0c;并在提示符 >>> 处接受命令。 Python 控制台是内置的用于探索Blender 的可能性的…

滑动窗口算法专题(2)

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; 优选算法专题 想要了解滑动窗口算法的介绍&#xff0c;可以去看下面的博客&#xff1a;滑动窗口算法的介绍 目录 904. 水果成篮 438. 找到…

JMeter向kafka发送数据

项目测试服务消费Kafka能力&#xff0c;需要消费特定格式的数据&#xff0c;因此要向Kafka中灌入数据&#xff0c;给老铁们分享下jmeter向kafka发送数据方法&#xff1a; 1. 首先在jmeter如下目录中导入一个kafka相关的jar包&#xff0c;如图&#xff1a; 2. 导入之后就可以打…

Spring Task快速入门

Spring Task介绍 Spring Task 是 Spring 框架提供的一种轻量级的定时任务解决方案&#xff0c;可以按照约定时间自动执行某个代码逻辑。它主要用于在 Spring 应用程序中执行定时任务&#xff0c;提供了一种声明式的方式来配置和执行这些任务。Spring Task 支持通过注解和配置文…

易燃气体检测系统源码分享

易燃气体检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

【抓包工具】如何下载抓包工具Fiddler

目录 Fiddler简介 Fiddler下载步骤 Fiddler安装步骤 配置Fiddler抓取HTTPS Fiddler简介 Fiddler是一个http协议调试代理工具&#xff0c;它能够记录并检查所有你的电脑和互联网之间的http通讯&#xff0c;设置断点&#xff0c;查看所有的“进出”Fiddler的数据&#xff08…

数据先行 -- Scale AI如何通过AI数据服务成为独角兽

数据引领未来 ©作者|格林&玄同 来源|神州问学 引言 近期&#xff0c;OpenAI发布的o1模型得到了广泛关注&#xff0c;该模型在多个推理能力上超过了人类博士水平。AI是否真的具有思考能力&#xff1f;为了追寻这一答案&#xff0c;技术专家们发出倡议&#xff0c;向人…

【Python】入门学习2:输入函数、输出函数、转义符

一、输入函数 # 普通用法&#xff1a;小括号里写提示语句&#xff0c;用引号包裹input("请输入&#xff1a;") # 控制台会显示“请输入&#xff1a;”&#xff0c;可以在后面输入内容# 变量接收&#xff1a;可以用变量进行接收a input("请输入&#xff1a;&q…

PSO粒子群代码手搓实现—代码详解版python

PSO粒子群算法手搓实现版&#x1f680; 读了博士之后&#xff0c;送算法方向转到了控制方向&#xff0c;然后最近接触到的项目&#xff0c;要用到粒子群算法&#xff0c;然后秉持着我自己一贯的把基础代码自己手写一遍的原则&#xff0c;我自己上网找了一些视频&#xff0c;然后…

通过反思性反馈增强和提升大模型

人工智能咨询培训老师叶梓 转载标明出处 尽管通过人类反馈的强化学习&#xff08;RLHF&#xff09;在使LLMs与人类偏好对齐方面展现出潜力&#xff0c;但这种方法往往只会导致表面的对齐&#xff0c;优先考虑风格上的变化而非提升LLMs在下游任务中的表现。而且偏好的不明确可能…

【漏洞复现】公交IC卡收单管理系统 SQL注入致RCE漏洞复现

》》》产品描述《《《 公交IC卡收单 管理系统Q是城市公共交通领域中不可或缺的一部分&#xff0c;它通过集成先进的集成电路技术(IC卡)实现了乘客便捷的支付方式&#xff0c;并有效提高了公共交通运营效率。系统集成了发卡、充值、消费、数据采集、查询和注销等多个功能模块&am…

走进上海郭培高定会馆:以冠珠华脉、华珍筑就至臻至性的艺术空间

“我热爱高级时装&#xff0c;因为她是一种生命的停驻。我希望我的高级时装成为馆藏级的精品&#xff0c;殿堂级的珍宝&#xff0c;成为传世杰作。” ——郭培 中国唯一一位法国高定公会受邀会员&#xff0c;曾荣登《TIME》时代周刊全球100位最具影响力人物榜单。纽约时报评价…

Error:Decorators are not valid here. 使用Angular中的装饰器

Decorators are not valid here&#xff0c;项目中出现这个提示信息&#xff0c;说明装饰器未知错误、或者在不支持的元素上使用了装饰器。 如下图所示&#xff0c;我在NgModule装饰器后面加了一个导出方法&#xff0c;加完之后控制台提示了如下错误&#xff1a;Error TS1206&a…

基于Spring Boot+Vue的减肥健康管理系统设计和实现【原创】(BMI算法,协同过滤算法、图形化分析)

&#x1f388;系统亮点&#xff1a;图形化分析、BMI算法&#xff0c;协同过滤算法&#xff1b; 一.系统开发工具与环境搭建 1.系统设计开发工具 后端使用Java编程语言的Spring boot框架 项目架构&#xff1a;B/S架构 运行环境&#xff1a;win10/win11、jdk17 前端&#xff1a…