boost asio同步编程(附源码api)

首先注明,这里我写的都是关于tcp的通信。

通信大致流程

创建端点

创建tcp端点的api是boost::asio::ip::tcp::endpoint;

当然创建udp端点的api则是boost::asio::ip::udp::endpoint;

是一个表示 TCP/UDP 端点的类,在 Boost.Asio 库中用于网络编程。它通常用于指定要连接的远程主机和端口,或者表示在本地主机上监听的端点

其中endpoint的构造函数使用得较多是参数为网络类型的ip地址端口号。源码构造如下。

这里的网络类型的ip地址需要解释以下。平常我们说得ip地址都是点分十进制的,而我们在网络通信中实际需要的ip地址则是需要进行网络序列化的ip地址。因此我们需要将点分十进制的ip地址转化成为网络序列的ip地址。这里需要使用的转化函数api则是boost::asio::ip::address::from_string。

对于这个转化ip地址序列的函数源码如下

其中第二个参数是一个boost::system::error_code类型的变量。

介绍完这些api之后,下边提供一个完整的创建客户端和服务端endpoint的demo。

void client_EndPoint() {
	string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	boost::system::error_code ec;
	boost::asio::ip::address ip_address
		= boost::asio::ip::address::from_string(raw_ip_address, ec);
	if (ec.value()) {
		cerr << "transfered failed" << endl;
		cout << "Error code: " << ec.value() <<
			"ec Message is" << ec.message() << endl;
		return ec.value();
	}
	//生成端点。该端点由ip地址和端口号生成
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
}
/*boost::asio::ip::address_v4::any()是一个Boost.Asio库中的函数,用于创建一个表示
IPv4任意地址(通配地址)的对象。这个函数返回一个特殊的address_v4对象,它代表了网络
中的任何IPv4地址。这个函数通常用于初始化socket绑定到一个通配地址,这表示socket应该
接受进入的连接,不论这些连接来自于哪个IP地址。*/
int server_EndPoint() {
	//服务器端口号
	unsigned short port_num = 3333;
	//服务器地址
	boost::asio::ip::address ip_address = boost::asio::ip::address_v4::any();
	//生成一个tcp用来通信的端点
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
	return 0;
}

创建套接字

asio空间中,我们首先不可避免的就是类io_service或io_context。

注意,io_context这个类是用来替代io_service的,所以建议以后都直接使用io_context即可

这个类非常重要,它相当于我们程序与系统之间I/O操作的中介,我们所有的接受或发送数据操作,都是通过将需求提交给这个类,然后这个类再交给计算机来执行的。

客户端创建socket

int create_tcp_socket() {
	//创建上下文
	boost::asio::io_context ioc;
	//创建ipv4的协议
	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4();
	//创建socket
	boost::asio::ip::tcp::socket sock(ioc);
	//打开socket
	boost::system::error_code ec;
	sock.open(protocol, ec);

	return 0;
}

这里构造的socket并没有被开启,即只有在被open,在被连接或者接收才能发送数据。构造源码如下

这里的sock.open()函数是 Boost.Asio 库中的一个函数,用于打开一个 TCP 套接字。源码如下,

服务端创建acceptor

下边是一个服务端的创建欢迎套接字demo

int create_acceptor_socket() {
	/*旧写法*/
	创建上下文
	//boost::asio::io_context ioc;
	生成acceptor服务
	//boost::asio::ip::tcp::acceptor acceptor(ioc);
	创建ipv4的协议
	//boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4();
	打开socket
	//boost::system::error_code ec;
	//acceptor.open(protocol, ec);
	//if (ec.value()) {
	//	cerr << "open failed" << "Error Code : " << ec.value()
	//		<< "Error Message : " << ec.message() << endl;
	//}

	/*新写法*/
	//创建上下文
	boost::asio::io_context ioc;
	//生成acceptor服务,(指定地址和端口,实现一个默认绑定)
	boost::asio::ip::tcp::acceptor acceptor(ioc, boost::asio::ip
		::tcp::endpoint(boost::asio::ip::tcp::v4(), 3333));
	return 0;
}

第一种构造acceptor的源码如下。这样构造出来的acceptor并没有启动acceptor去监听新的连接。在acceptor能够监听新的连接之前,必须先调用open函数,启动acceptor去监听新的连接。

第二种构造acceptor的方式,会在构造acceptor的时候自动去启动acceptor在指定的端点监听新的连接

服务器绑定套接字

int bind_acceptor_socket() {
	//创建服务
	boost::asio::io_context ioc;
	unsigned short port_num = 3333;
	//创建端点
	boost::asio::ip::tcp::endpoint ep(boost::asio::
		ip::address_v4::any(), port_num);
	//生成accpetor,并手动绑定
	boost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
	//绑定端点,返回错误码
	boost::system::error_code ec;
	acceptor.bind(ep, ec);
	if (ec.value()) {
		cerr << "open failed" << "Error Code : " << ec.value()
			<< "Error Message : " << ec.message() << endl;
	}
	return 0;
}

boost::asio::ip::address_v4::any() 是一个Boost.Asio库中的函数,用于创建一个表示IPv4任意地址(通配地址)的address_v4对象。这个函数返回一个特殊的address_v4对象,它可以绑定到一个网络接口上,表示接受任意IPv4地址的数据包

这里构造endpoint的方法也会自动去开启open。源码如下

这段代码需要好好理解下。这里的构造的endpoint端点返回的是一个通配地址,即一个通配的绑定端口的端点,它并没有绑定任何一台主机。这里构造的acceptor仅仅是一个只能接收ipv4协议的acceptor的默认绑定(本地ip,但是并不清楚在哪个端口)。所以需要使用acceptor的bind方法绑定指定的端点(拿到里边的端口)。

这里可能会有疑惑,普通套接字和欢迎套接字的bind()和open()的先后顺序。是先bind再open。这里构造endpoint的方法也会自动去开启open。但是它还并没有去bind,所以并不会及时去开启open,而是等到bind之后再去开启,但是这里并不需要我们去操作,而是程序自动完成的。

客户端连接

客户端的连接比较简单,就先提供代码,再做解读。

int client_connect()
{
	//服务器地址
	string raw_ip_address = "192.168.1.124";
	//服务器端口
	unsigned short port_num = 3333;
	try{
		//创建上下文服务
		boost::asio::io_context ioc;
		//创建端点
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address
			::from_string(raw_ip_address), port_num);
		//创建socket
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		//
		sock.connect(ep);

	}catch(boost::system::system_error& e){
			cerr << "open failed" << "Error Code : " << e.code()
					<< "Error Message : " << e.what() << endl;
	}
	return 0;
}

首先构造出服务端的端点endpoint。再构造一个客户端的socket。这里使用构造socket的方法会将构造出来的socket自动开启,即只有被连接或者接收就能发送或接收数据。源码如下

使用客户端的socket的connect方法去连接服务器的端点即可。connet方法的源码如下

下边写一个服务端获取一个新的连接的demo

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 ioc;
	try {
		//生成一个acceptor
		boost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
		//绑定端口
		acceptor.bind(ep);
		//监听
		acceptor.listen(BACKLOG_SIZE);
		//再创建一个socket
		boost::asio::ip::tcp::socket sock(ioc);
		//将acceptor接收新的连接,交给新创建的sock处理
		acceptor.accept(sock);
	}
	catch (boost::system::system_error& e) {
		cerr << "open failed" << "Error Code : " << e.code()
			<< "Error Message : " << e.what() << endl;
	}

	return 0;
}

boost::asio中的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的重要性

boost中的send接口要求参数为ConstBufferSequence类型。所以我们需要常用ConstBufferSequence类型的参数,但是怎么去构造这样类型的参数呢?下边是send函数源码

使用boost::asio中的const_buffer去构造ConstBufferSequence

void use_const_buffer()
{
	string buf("hello boost");
	//构造一个asio中的一个const_buffer对象
	boost::asio::const_buffer asio_buff(buf.c_str(), buf.size());
	vector<boost::asio::const_buffer> bufSequece;
	bufSequece.emplace_back(asio_buff);
}

可以看出这样写太麻烦了。asio中提供了buffer函数转化成为send函数参数需要的类型。

void use_buffer_str() {
    asio::const_buffers_1 output_buf = asio::buffer("hello world");
}

buffer函数该函数接收多种形式的字节流,该函数返回asio::mutable_buffers_1 或者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函数的参数使用。可以参考下边这个demo。

void use_buffer_array()
{
	const size_t BUF_SIZE_BYTES = 20;
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	//static_cast <type-id>( expression )。
	//static_cast把expression转换为type-id类型
	boost::asio::mutable_buffers_1 input_buf =
		boost::asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);

}

下边是buffer函数的源码。第一个参数是字符串的首地址,第二个参数是字符串的长度。

同步读写api

同步写

write_some

write_some可以每次向指定的空间写入固定的字节数。这个函数将会阻塞的写入,直到一个或者更多的数据字节成功写入,另或是出现错误。这是源码给出的解释,也能这样的理解,就是write_some可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数

demo如下

void write_some_to_socket(boost::asio::ip::tcp::socket& sock)
{
	string buf = "hello boost";
	size_t total_bytes_written = 0;
	//循环发送
	
	while (total_bytes_written != buf.size()) {
        //注意这里写的位置要加上偏移量,更符合实际
		total_bytes_written += sock.write_some(boost::asio::buffer
		(buf.c_str() + total_bytes_written,
			buf.size() - total_bytes_written));
	}

}

write_some函数源码如下

send

这个函数将会阻塞的写入,直到一个或者更多的数据字节成功写入,另或是出现错误。这也是源码给出的解释。但是send函数会一次性将buffer中的内容发送给对端,如果有部分字节因为发送缓冲区满无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成,这样理解会更好

demo如下

int send_to_socket()
{
	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::ip::tcp::socket sock(ios, ep.protocol());
		sock.connect(ep);
		std::string buf = "Hello World!";
		/*下边的send_length只有三种情况。
		1、大于0,即等于buff的长度
		2、等于0,表示对端关闭
		3、小于0,表示socket出现系统级错误*/
		int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			cout << "send failed" << endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

send函数源码如下

write

可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成

demo如下

int write_to_socket()
{
	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::ip::tcp::socket sock(ios, ep.protocol());
		sock.connect(ep);
		std::string buf = "Hello World!";
		/*下边的send_length只有三种情况。
		1、大于0,即等于buff的长度
		2、等于0,表示对端关闭
		3、小于0,表示socket出现系统级错误*/
		int send_length = boost::asio::write(sock, boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			cout << "send failed" << endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

write函数源码如下

同步读

read_some

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

demo如下

string read_some_fromSocket(boost::asio::ip::tcp::socket& sock)
{
	const unsigned char MESSAGE_SIZE = 7;
	char buf[MESSAGE_SIZE];
	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 string(buf, total_bytes_read);
}

read_some函数源码如下

receive

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

demo如下

int receive_fromSocket(boost::asio::ip::tcp::socket& sock)
{
	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::ip::tcp::socket sock(ios, 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) {
			cout << "receive failed" << endl;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}

	return 0;
}

receive函数源码如下

read

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

demo如下

int read_fromSocket(boost::asio::ip::tcp::socket& sock)
{
	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::ip::tcp::socket sock(ios, 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) {
			cout << "receive failed" << endl;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

read函数源码如下

下边提供一个同步读写的echo服务器和客户端,源码如下

syncSer:codes-C++: C++学习 - Gitee.com

syncCli:codes-C++: C++学习 - Gitee.com

最近更新

  1. TCP协议是安全的吗?

    2024-05-12 07:30:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-12 07:30:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-12 07:30:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-12 07:30:04       20 阅读

热门阅读

  1. 代码随想录算法训练营第36期DAY25

    2024-05-12 07:30:04       8 阅读
  2. 设计模式-09 - 享元模式 flyweight pattern

    2024-05-12 07:30:04       8 阅读
  3. Linux权限(二)

    2024-05-12 07:30:04       11 阅读
  4. 数据结构之队列

    2024-05-12 07:30:04       9 阅读
  5. DBSCAN聚类算法

    2024-05-12 07:30:04       12 阅读
  6. WEB前端复习——HTML

    2024-05-12 07:30:04       11 阅读
  7. UML 方法

    2024-05-12 07:30:04       14 阅读
  8. C语言-STM32:初始定时器(通用定时器)

    2024-05-12 07:30:04       9 阅读
  9. Lua 协程池

    2024-05-12 07:30:04       12 阅读
  10. EureKa详细讲解通俗易懂

    2024-05-12 07:30:04       10 阅读
  11. flask+layui显示监控视频

    2024-05-12 07:30:04       12 阅读
  12. 代码绘梦:Processing艺术编程入门

    2024-05-12 07:30:04       8 阅读