写在前面
最近做项目看到了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队列当中接收上来新连接