写在前面
最近做项目看到了Boost库来实现网络编程的技术,对比出之前使用的muduo网络库来说,这个库使用起来更加的通用化,可以在Linux平台和windows平台都使用,有跨平台性,再加上Boost库本身也可以看成就是一个C++的标准库,因此这里就开始学习这个吧
因为有前面的编程基础,在网络编程这里学习的成本也不算很高,主要是熟悉一下基本的逻辑和接口,之后重点在于异步的设计和实现,主要是想学习这里的异步和muduo库当中的异步之间的调用逻辑关系,希望能有所收获
套接字创建
这一节主要熟悉的是这几个函数,基本的调用逻辑还是挺简单的,服务端创建监听套接字,然后bind,listen,客户端进行connect,整体逻辑就这样,所以这一篇的任务还是比较简单的
#pragma once extern int client_end_point(); extern int server_end_point(); extern int create_tcp_socket(); extern int create_acceptor_socket(); extern int bind_acceptor_socket(); extern int connect_to_end(); extern int dns_connect_to_end(); extern int accept_new_connection();
看名字也能猜出个大概,这里主要就进行实现的时候再讲解吧
客户端通信点
// 客户端的通信端点 int client_end_point() { // 设置对端的内容 string raw_ip_address = ""; unsigned short port_num = 3333; // 设置错误码 boost::system::error_code ec; // 对于ip地址进行解析成所需要的参数 asio::ip::address ip_address = asio::ip::address::from_string(raw_ip_address, ec); if (ec.value()) { cout << "fail to parse ip address, error code is " << ec.value() << " Message is" << ec.message(); } // 给asio绑定ip地址和端口号,客户端可以通过这个进行链接 asio::ip::tcp::endpoint ep(ip_address, port_num); return 0; }
这个函数主要是客户端和服务端进行通信的一个节点的建立过程,基本的逻辑其实很简单,就是指定一个ip和端口,然后进行绑定的过程,不过在Boost库这里还多了一个对于ip地址进行转换的过程,ip地址是需要转换成Boost库所自己实现的类型,因此要调用这样的函数
asio::ip::address ip_address = asio::ip::address::from_string(raw_ip_address, ec);
之后就会得到Boost函数所接受的ip地址接口,然后再加上一个端口,就可以进行绑定了,在这里进行设计的时候,看到一个比较有意思的地方是对于错误的处理,设计的接口实现是一个输入型参数,把一个errorcode来传递到函数当中,如果调用失败了就给这个错误码进行设置,然后在外部就可以对于这个错误码进行一个识别的效果,这也算是Linux中的各种函数接口当中比较常用的一种设置错误码的方式,同时这样也能带出更多的输出信息
服务端通信端点
// 服务端的通信端点 int server_end_point() { // 绑定任意地址,绑定到3333端口 unsigned short port_num = 3333; asio::ip::address ip_address = asio::ip::address_v6::any(); asio::ip::tcp::endpoint ep(ip_address, port_num); return 0; }
这里主要实现的是服务端,服务端主要是进行一个创建监听套接字的作用,来进行后续的事件处理等操作
创建监听套接字
// 创建TCPSocket int create_tcp_socket() { // 上下文是asio服务通信的核心内容,告诉asio,这个Socket是属于哪个服务的 asio::io_context ioc; // 定义协议 asio::ip::tcp protocol = asio::ip::tcp::v4(); asio::ip::tcp::socket sock(ioc); boost::system::error_code ec; // 打开socket sock.open(protocol, ec); if (ec.value()) { cout << "fail to open socket, error code is " << ec.value() << " Message is" << ec.message(); return ec.value(); } return 0; }
Accept连接
旧版本
// Accept新连接 int create_acceptor_socket() { // 进行连接的接收 asio::io_context ioc; // 旧版本的写法 asio::ip::tcp::acceptor acceptor(ioc); asio::ip::tcp protocol = asio::ip::tcp::v4(); boost::system::error_code ec; acceptor.open(protocol, ec); if (ec.value()) { cout << "fail to open socket, error code is " << ec.value() << " Message is" << ec.message(); return ec.value(); } bind() return 0; }
新版本
// Accept新连接 int create_acceptor_socket() { // 进行连接的接收 asio::io_context ioc; // 新版本的写法,直接指定,然后进行连接即可,默认实现了绑定的操作 asio::ip::tcp::acceptor a(ioc, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 3333)); return 0; }
对比出来,新版本直接默认进行了绑定到指定端口,而旧版本还需要后续进行一个手动的绑定,下面就是对于绑定端口的模块函数
绑定操作
// 进行绑定的操作 int bind_acceptor_socket() { // 接收来自各个位置想要和port_num进行通信的端口 unsigned short port_num = 3333; asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num); // 注册一个服务事件,进行对应的绑定 asio::io_context ioc; asio::ip::tcp::acceptor acceptor(ioc, ep.protocol()); boost::system::error_code ec; acceptor.bind(ep, ec); if (ec.value()) { cout << "fail to bind socket, error code is " << ec.value() << " Message is" << ec.message(); return ec.value(); } return 0; }
如上就是对于一个套接字的绑定操作,其实也是比较简单的,就是一个进行ip地址的转换,然后转换成一个Boost库自己定义的设置的一个字符串的类型,然后再进行绑定
// 进行绑定的操作 int bind_acceptor_socket() { // 接收来自各个位置想要和port_num进行通信的端口 unsigned short port_num = 3333; asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num); // 注册一个服务事件,进行对应的绑定 asio::io_context ioc; asio::ip::tcp::acceptor acceptor(ioc, ep.protocol()); boost::system::error_code ec; acceptor.bind(ep, ec); if (ec.value()) { cout << "fail to bind socket, error code is " << ec.value() << " Message is" << ec.message(); return ec.value(); } return 0; }
服务器连接
// 进行连接到服务器 int connect_to_end() { string raw_ip_address = ""; unsigned short port_num = 3333; try { // 创建一个端点,绑定ip和端口 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); // 进行连接 asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); } catch (system::system_error& e) { cout << "error code = " << e.code() << "Message: " << e.what() << endl; return e.code().value(); } return 0; }
dns解析服务
// 通过解析域名来进行连接 int dns_connect_to_end() { string host = "baidu.com"; string port_num = "3333"; asio::io_context ioc; // 创建一个dns解析器,第三个参数是强制给的,记住就行 asio::ip::tcp::resolver::query resolver_query(host, port_num, asio::ip::tcp::resolver::query::numeric_service); asio::ip::tcp::resolver resolver(ioc); try { // 根据域名解析器进行解析,解析出一个带有所有域名的迭代器 asio::ip::tcp::resolver::iterator it = resolver.resolve(resolver_query); asio::ip::tcp::socket sock(ioc); asio::connect(sock, it); } catch(system::system_error& e) { cout << "error code = " << e.code() << "Message: " << e.what() << endl; return e.code().value(); } }
这个用的基本不多,一般都是用脚本直接找到ip地址进行传输,这个就当做了解使用吧
接收新的连接
// 建立新的连接 int accept_new_connection() { // 服务器缓冲区队列的大小 const int BACKLOG_SIZE = 30; unsigned short port_num = 3333; // 创建一个端点,接收所有请求 asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num); asio::io_context ioc; try { // 生成一个接收器 asio::ip::tcp::acceptor acceptor(ioc, ep.protocol()); acceptor.bind(ep); acceptor.listen(BACKLOG_SIZE); asio::ip::tcp::socket sock(ioc); acceptor.accept(sock); } catch (system::system_error& e) { cout << "error code = " << e.code() << "Message: " << e.what() << endl; return e.code().value(); } }
监听套接字从Accept队列当中接收上来新连接
Buffer的基本用法
如下展示的是Buffer中的一些基本的用法
// const_buffer的基本用法 void use_count_buffer() { string buf = "hello world"; asio::const_buffer asio_buf(buf.c_str(), buf.size()); vector<asio::const_buffer> buffers_sequence; buffers_sequence.push_back(asio_buf); } void use_buffer_str() { asio::const_buffers_1 output_buf = asio::buffer("hello world"); } void use_buffer_array() { const size_t BUF_SIZE = 20; unique_ptr<char[]> buf(new char[BUF_SIZE]); auto input_buf = asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE); }
发送数据
下面介绍的是发送接受数据的几种方式,这里展示的主要是同步和循环接收的方式,异步放在后面说
// 将数据写到Socket当中 void write_to_socket(asio::ip::tcp::socket& sock) { string buf = "hello world"; size_t total_bytes_written = 0; // 循环发送 // write_som返回的是每次发送的字节数 while (total_bytes_written != buf.size()) { // 每次发送的是起始偏移量+已经发送的数据,发送的长度是总的长度减去已经发送的长度 total_bytes_written += sock.write_some(asio::buffer(buf.c_str() + total_bytes_written, buf.size() - total_bytes_written)); } } // 使用write_some写数据 int send_data_by_write_some() { string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333; try { // 连接到对端 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); // 发送数据 write_to_socket(sock); } catch (system::system_error& e) { cout << "send_data_by_write_some error, error code: " << e.code() << "Message: " << e.what() << endl; } return 0; } // 使用send写数据 int send_data_by_send() { string buf = "hello world"; string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333; try { // 连接到对端 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); // 发送结束了再告诉上层 int send_lenth = sock.send(asio::buffer(buf.c_str(), buf.size())); if (send_lenth <= 0) { cerr << "send fail" << endl; return 0; } } catch (system::system_error& e) { cout << "send_data_by_write_some error, error code: " << e.code() << "Message: " << e.what() << endl; } return 0; } // 使用asio::write写数据 int send_data_by_asio_write() { string buf = "hello world"; string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333; try { // 连接到对端 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); // 发送结束了再告诉上层,和send一样 int send_lenth = asio::write(sock, asio::buffer(buf.c_str(), buf.size())); if (send_lenth <= 0) { cerr << "send fail" << endl; return 0; } } catch (system::system_error& e) { cout << "send_data_by_write_some error, error code: " << e.code() << "Message: " << e.what() << endl; } return 0; }
读取数据
// 使用read_some读取数据 string read_from_socket(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(asio::buffer(buf + total_bytes_read, MESSAGE_SIZE - total_bytes_read)); } return string(buf, total_bytes_read); } // 使用Socket读取数据 int read_data_by_read_some() { string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333; try { // 构造端点 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); read_from_socket(sock); } catch (system::system_error& e) { cout << "read_data_by_read_some error, error code: " << e.code() << "Message: " << e.what() << endl; } } // 使用receive接受数据 int read_data_by_receive() { string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333; try { // 构造端点 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); // 接收数据 const unsigned char BUFF_SIZE = 7; char buffer_receive[BUFF_SIZE]; int receive_lenth = sock.receive(asio::buffer(buffer_receive, BUFF_SIZE)); if (receive_lenth <= 0) { cerr << "receive fail" << endl; } } catch (system::system_error& e) { cout << "read_data_by_read_some error, error code: " << e.code() << "Message: " << e.what() << endl; } } // 使用asio::read接受数据 int read_data_by_asio_read() { string raw_ip_address = "127.0.0.1"; unsigned short port_num = 3333; try { // 构造端点 asio::ip::tcp::endpoint ep(asio::ip::address::from_string(raw_ip_address), port_num); asio::io_context ioc; asio::ip::tcp::socket sock(ioc, ep.protocol()); sock.connect(ep); // 接收数据 const unsigned char BUFF_SIZE = 7; char buffer_receive[BUFF_SIZE]; int receive_lenth = asio::read(sock, asio::buffer(buffer_receive, BUFF_SIZE)); if (receive_lenth <= 0) { cerr << "receive fail" << endl; } } catch (system::system_error& e) { cout << "read_data_by_read_some error, error code: " << e.code() << "Message: " << e.what() << endl; } }