首先注明,这里我写的都是关于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();//创建socketboost::asio::ip::tcp::socket sock(ioc);//打开socketboost::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);//创建socketboost::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 {//生成一个acceptorboost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());//绑定端口acceptor.bind(ep);//监听acceptor.listen(BACKLOG_SIZE);//再创建一个socketboost::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::endpointep(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::endpointep(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::endpointep(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::endpointep(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