首先注明,这里我写的都是关于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