二、C++ boost::asio 的使用

上一节介绍了visual studio搭建boost环境,现在我们来使用它来进行网络编程。

1、网络编程基本流程

服务端

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中的asio进行网络编程时,头文件为

#include <boost/asio.hpp>

2、终端节点的创建

        关于终端结点的创建,可以有几种方式:

        第一种:客户端可以使用这种方式,较为麻烦

	// std::string raw_ip_address:字符串变量,表示IP地址
	std::string raw_ip_address = "127.4.8.1";
	// unsigned short port_num:无符号短整型变量,表示端口号
	unsigned short port_num = 3333;
	// boost::system::error_code ec:错误码对象,用于存储可能出现的错误信息
	boost::system::error_code ec;
	// 将字符串形式的IP地址 raw_ip_address 转换为 boost::asio::ip::address 类型的对象 ip_address
	// 并将可能出现的错误信息存储在 ec 中
	boost::asio::ip::address ip_address = boost::asio::ip::address::from_string(raw_ip_address, ec);
	// 如果 ec.value() 不等于0
	if (ec.value() != 0) {
		// 输出错误码和错误信息,并返回错误码
		std::cout << "ip地址解析错误,错误码:" << ec.value() << ",错误信息:" << ec.message() << std::endl;
		return ec.value();
	}
	// 创建一个 boost::asio::ip::tcp::endpoint 对象 ep,它表示一个网络端点
	// 由IP地址 ip_address 和端口号 port_num 组成
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);

         第二种:针对服务端的方法,不需要指定ip,获取自己的ip即可

	unsigned short port_num = 3333;
	// 可以绑定到所有可用的IPv6接口。这种设置通常用于服务器端,以便接收来自任何网络接口的连接
	boost::asio::ip::address ip_address = boost::asio::ip::address_v6::any();
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);

         第三种:简化方法,这种使用起来较为方便,代码简洁

boost::asio::ip::tcp::endpoint remote_ep(boost::asio::ip::address::from_string("127.0.0.1"), 10086);

3、创建socket

        创建socket分为4步,创建上下文iocontext,选择协议,绑定上下文信息,打开socket。

	// boost::asio::io_context ioc:I/O 上下文对象。
	// 它管理所有异步 I/O 操作的执行,所有的网络操作都依赖于它来处理事件循环
	boost::asio::io_context ioc;
	// boost::asio::ip::tcp 对象,指定使用 TCP 协议
	// 如果希望使用 IPv6,可以使用 boost::asio::ip::tcp::v6()
	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4();
	// boost::asio::ip::tcp::socket 对象,表示一个 TCP 套接字。
	// 它被绑定到先前创建的 I/O 上下文 ioc 上
	boost::asio::ip::tcp::socket sock(ioc);
	boost::system::error_code ec;
	sock.open(protocol, ec);
	if (ec.value() != 0) {
		std::cout 
			<< "错误,错误码:" << ec.value()
			<< ",错误信息:" << ec.message() 
			<< std::endl;
		return ec.value();
	}

        上述socket只是通信的socket,如果是服务端,我们还需要生成一个acceptor的socket,用来接收新的连接。

	boost::asio::io_context ioc;
	boost::asio::ip::tcp::acceptor a(ioc, 
		boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3333));

4、绑定acceptor

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

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 ioc;
	// 创建一个 TCP 接收器对象,指定使用 ioc 作为 I/O 上下文,并使用 ep 的协议(IPv4)
	boost::asio::ip::tcp::acceptor accptor(ioc, ep.protocol());
	boost::system::error_code ec;
	// 将接收器绑定到指定的端点 ep,如果出现错误,将错误信息存储在 ec 中
	accptor.bind(ep, ec);
	if (ec.value() != 0) {
		std::cout
			<< "错误,错误码:" << ec.value()
			<< ",错误信息:" << ec.message()
			<< std::endl;
		return ec.value();
	}

	return 0;
}

5、连接指定的端点

        连接至服务器指定的端点

// 客户端连接服务器
int connect_to_end()
{
	std::string raw_ip_address = "192.168.1.124";
	unsigned short port_num = 3333;
	try {
		// 创建指定ip和端口的端点
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		boost::asio::io_context ioc;
		// 创建一个 TCP 套接字对象,指定使用 ioc 作为 I/O 上下文
		boost::asio::ip::tcp::socket sock(ioc);
		// 连接到指定的端点 ep。这一步是阻塞操作,直到连接成功或失败
		sock.connect(ep);
	}
	catch (boost::system::system_error& e) {
		std::cout
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}

6、服务器接收连接

int accept_new_connection()
{
	// 定义服务器监听队列的大小,即同时可处理的未决连接数。这里设置为30
	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 ioc;
	try {
		// 创建一个 TCP 接收器对象,指定使用 ioc 作为 I/O 上下文,并使用 ep 的协议(IPv4)
		boost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
		// 将接收器绑定到指定的端点 ep,即所有 IPv4 地址和端口 3333
		acceptor.bind(ep);
		// 开始监听传入的连接请求,设置连接等待队列的最大长度为 BACKLOG_SIZE
		acceptor.listen(BACKLOG_SIZE);
		// 创建一个用于通信的 TCP 套接字对象 sock
		boost::asio::ip::tcp::socket sock(ioc);
		// 阻塞等待并接受一个传入的连接,将新连接的套接字对象赋给 sock
		acceptor.accept(sock);
	}
	catch (boost::system::system_error& e) {
		std::cout
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}

7、buffer

        buffer就是接收和发送数据时缓存数据的结构,boost::asio提供的数据结构较为复杂,但是提供了相对简单的api接口。

        将hello转化为该类型

void use_const_buffer()
{
	std::string buf = "hello";
	// boost::asio::const_buffer是一个 Boost.Asio 提供的常量缓冲区类,用于表示一块只读的内存区域
	// buf.c_str():返回指向 buf 首字符数据的指针
	// buf.length():返回字符串的长度,即数据的字节数
	boost::asio::const_buffer asio_buf(buf.c_str(), buf.length());
	// std::vector 容器可以包含多个缓冲区,用于一次性传输多个数据块
	std::vector<boost::asio::const_buffer> buffers_sequence;
	buffers_sequence.push_back(asio_buf);
	// boost::asio.send(buffers_sequence);
}

        上面的方法比较麻烦,可以直接用buffer函数转化为send需要的参数类型

void use_buffer_str()
{
    // boost::asio::const_buffers_1 是一个单一的常量缓冲区类型。它用于包装一个不可修改的数据缓冲区
	boost::asio::const_buffers_1 output_buf = boost::asio::buffer("hello");
}

        对于数组,可以这样写

void use_buffer_array()
{
	const std::size_t BUF_SIZE_BYTES = 20;
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	auto input_buf = boost::asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);
}

8、同步写

8.1 同步写write_some

        write_some可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。它的特点是非阻塞。

void write_to_socket(boost::asio::ip::tcp::socket& sock)
{
	std::string buf = "hello";
	std::size_t total_bytes_weitten = 0;
	// 循环发送
	// write_some 返回每次写入的字节数
	while (total_bytes_weitten != buf.length()) {
		total_bytes_weitten += sock.write_some(boost::asio::buffer(buf.c_str() + total_bytes_weitten,
			buf.length() - total_bytes_weitten));
	}
}
8.2 同步写send

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

int send_data_by_send() {
	std::string raw_ip_address = "192.168.3.11";
	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()); // ep.protocol()传不传都可以
		sock.connect(ep);
		std::string buf = "hello";
		// 阻塞在这,直到发完为止,比write_some用起来方便
		int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			// 发送失败
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}
8.3 同步写write

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

int send_data_by_write() {
	std::string raw_ip_address = "192.168.3.11";
	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()); // ep.protocol()传不传都可以
		sock.connect(ep);
		std::string buf = "hello";
		// 也是阻塞在这,直到发完为止,也比write_some用起来方便
		int send_length = boost::asio::write(sock, boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			// 发送失败
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}

9、同步读

        和同步写类似,也提供了三种方法

9.1 同步读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_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
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}
9.2 同步读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 << "接收失败" << std::endl;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}
9.4 同步读read

        这种方式是阻塞的,直到读到指定长度才会取消阻塞,receive虽然也是阻塞的,但是使用receive时至少读到一个字节的数据就会取消阻塞。

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 << "接收失败" << std::endl;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout
			<< "错误,错误码:" << e.code()
			<< ",错误信息:" << e.what()
			<< std::endl;
		return e.code().value();
	}

	return 0;
}
9.5 读取直到指定字符

        这个方法不经常使用,了解即可。

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;
 }
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的使用 boost::asio 库编写的服务器示例: ```c++ #include <iostream> #include <boost/asio.hpp> using namespace boost::asio; using namespace boost::asio::ip; class Session : public std::enable_shared_from_this<Session> { public: Session(tcp::socket socket) : socket_(std::move(socket)) {} void start() { do_read(); } private: void do_read() { auto self(shared_from_this()); socket_.async_read_some(buffer(data_, max_length), [this, self](boost::system::error_code ec, std::size_t length) { if (!ec) { do_write(length); } }); } void do_write(std::size_t length) { auto self(shared_from_this()); async_write(socket_, buffer(data_, length), [this, self](boost::system::error_code ec, std::size_t /*length*/) { if (!ec) { do_read(); } }); } tcp::socket socket_; enum { max_length = 1024 }; char data_[max_length]; }; class Server { public: Server(io_service& ios, short port) : acceptor_(ios, tcp::endpoint(tcp::v4(), port)), socket_(ios) { do_accept(); } private: void do_accept() { acceptor_.async_accept(socket_, [this](boost::system::error_code ec) { if (!ec) { std::make_shared<Session>(std::move(socket_))->start(); } do_accept(); }); } tcp::acceptor acceptor_; tcp::socket socket_; }; int main(int argc, char* argv[]) { try { if (argc != 2) { std::cerr << "Usage: server <port>" << std::endl; return 1; } io_service ios; Server s(ios, std::atoi(argv[1])); ios.run(); } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } ``` 这个服务器使用boost::asio 库提供的异步 I/O 操作实现,它只会在有客户端连接时才会创建一个新的 Session 对象处理该客户端的请求,这样可以避免阻塞主线程。在 Session 对象中,我们使用了 async_read_some 和 async_write 这两个异步操作来处理客户端的读写请求,这样可以保证多个客户端之间互不干扰。最后,在 main 函数中,我们启动了一个 Server 对象,并调用了 io_service 的 run 方法来启动事件循环。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值