一、day1 学习了服务器和客户端 socket 的建立、监听以及连接。
参考:
恋恋风辰官方博客
visual studio配置C++ boost库_哔哩哔哩_bilibili
1. socket的监听和连接 服务端 1)socket——创建socket对象。
2)bind——绑定本机ip+port。
3)listen——监听来电,若在监听到来电,则建立起连接。
4)accept——再创建一个socket对象给其收发消息。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息。
5)read、write——就是收发消息了。
对于客户端是这样的 客户端 1)socket——创建socket对象。
2)connect——根据服务端ip+port,发起连接请求。
3)write、read——建立连接后,就可发收消息了。
boost库网络编程的基本流程(阻塞)如下所示。
1.1 终端节点创建 终端节点代表一个网络通信的端点,由 ip 地址和端口号组成。该函数创建一个 TCP 客户端的终端节点。如果我们是客户端,我们可以通过对端的 ip 和端口构造一个 endpoint,用这个 endpoint和其通信。
客户端端点的建立:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int client_end_point () { std::string raw_ip_address = "127.4.8.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 () != 0 ) { std::cout << "Failed to parse the IP address. Error code = " << ec.value () << " .Message is " << ec.message (); return ec.value (); } boost::asio::ip::tcp::endpoint ep (ip_address, port_num) ; return 0 ; }
服务器端端点的建立:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 int server_end_point () { unsigned short port_num = 3333 ; boost::asio::ip::address ip_address = boost::asio::ip::address_v6::any (); boost::asio::ip::tcp::endpoint ep (ip_address, port_num) ; return 0 ; }
1.2 建立socket 客户端 socket_v4
的建立:
上下文 iocontext
->选择协议->生成socket->打开socket
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int create_tcp_socket () { boost::asio::io_context ioc; boost::asio::ip::tcp::socket sock (ioc) ; boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4 (); boost::system::error_code ec; sock.open (protocol, ec); if (ec.value () != 0 ) { std::cout << "Failed to parse the IP address. Error code = " << ec.value () << " .Message is " << ec.message (); return ec.value (); } return 0 ; }
上述 socket 只是通信的 socket,需在服务端建立 acceptor 的 socket,用于接收新的连接:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 int create_acceptor_socket () { boost::asio::io_context ios; boost::asio::ip::tcp::acceptor acceptor (ios) ; boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4 (); boost::system::error_code ec; acceptor.open (protocol, ec); if (ec.value () != 0 ) { std::cout << "Failed to parse the IP address. Error code = " << ec.value () << " .Message is " << ec.message (); return ec.value (); } return 0 ; }
1.3 绑定socket 对于acceptor类型的socket,服务器要将其绑定到指定的端点,所有连接这个端点的连接都可以被接收到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int bind_acceptor_socket () { unsigned short port_num = 3333 ; boost::asio::ip::tcp::endpoint ep (boost::asio::ip::address_v4::any(), port_num) ; boost::asio::io_context ios; boost::asio::ip::tcp::acceptor acceptor (ios, ep.protocol()) ; boost::system::error_code ec; acceptor.bind (ep, ec); if (ec.value () != 0 ) { std::cout << "Failed to parse the IP address. Error code = " << ec.value () << " .Message is " << ec.message (); return ec.value (); } return 0 ; }
1.4 连接指定的端点 作为客户端可以连接服务器指定的端点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 int connect_to_end () { std::string raw_ip_address = "192.168.1.124" ; 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_context ios; boost::asio::ip::tcp::socket sock (ios, ep.protocol()) ; sock.connect (ep); } catch (boost::system::system_error& e) { std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } return 0 ; } int dns_connect_to_end () { std::string host = "llfc.club" ; std::string port_num = "3333" ; boost::asio::io_context ios; boost::asio::ip::tcp::resolver::query resolver_query (host, port_num, boost::asio::ip::tcp::resolver::query::numeric_service) ; boost::asio::ip::tcp::resolver resolver (ios) ; try { boost::asio::ip::tcp::resolver::iterator it = resolver.resolve (resolver_query); boost::asio::ip::tcp::socket sock (ios) ; boost::asio::connect (sock, it); } catch (boost::system::system_error& e){ std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } return 0 ; }
1.5 服务器接收连接 当有客户端连接时,服务器需要接收连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 / 创建一个 TCP 服务器端的接收器(acceptor),绑定到指定的 IP 地址和端口,并等待客户端连接。 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 ios; try { boost::asio::ip::tcp::acceptor acceptor (ios, ep.protocol()) ; acceptor.bind (ep); acceptor.listen (BACKLOG_SIZE); boost::asio::ip::tcp::socket sock (ios) ; acceptor.accept (sock); } catch (boost::system::system_error& e){ std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } return 0 ; }
第一日总结:
1.网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。利用三元组 (ip地址,协议,端口)就可以标识网络的进程,网络中的进程通信 可以利用这个标志与其它进程进行交互。
2.什么是socket? socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
3.acceptor和socket有什么区别,分别有什么作用?
1)acceptor
作用 : acceptor
是一个服务器端的对象,用于接收来自客户端的连接请求。
功能 :
创建 : acceptor
对象通常在服务器端创建,它的作用是等待并接收客户端的连接请求。
绑定 : 通过 acceptor.bind(endpoint)
将其绑定到特定的 IP 地址和端口号,指定服务器要监听的地址。
监听 : 通过 acceptor.listen(backlog_size)
启动监听,backlog_size
是一个整数,表示连接请求队列 的大小。
接受连接 : 通过 acceptor.accept(socket)
接受一个连接请求,并将连接绑定到一个新的 socket
对象上,socket
用于后续的数据传输。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息
1 2 3 4 5 boost::asio::ip::tcp::acceptor acceptor (io_context, endpoint) ; acceptor.bind (endpoint); acceptor.listen (backlog_size); boost::asio::ip::tcp::socket socket (io_context) ; acceptor.accept (socket);
2)socket
作用 : socket
是用于实际数据传输的对象。它代表了一个网络连接 的端点。
功能 :
创建 : socket
对象在客户端和服务器端都可以创建。客户端用来发起连接,服务器端用来处理接收到的连接。
连接 : 在客户端,socket
通过 socket.connect(endpoint)
连接到服务器端的 acceptor
。
接收和发送数据 : 一旦建立连接,socket
可以用来发送和接收数据 ,使用方法如 socket.send()
和 socket.receive()
。
关闭连接 : 通过 socket.close()
关闭连接。
1 2 3 boost::asio::ip::tcp::socket socket (io_context) ; socket.connect (endpoint); boost::asio::write (socket, boost::asio::buffer ("Hello, World!" ));
二、day2 2.1 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的结构
每个 vector 存储的都是 mutable_buffer
的地址,每个mutable_buffer
的第一个字节表示数据的长度,后面跟着数据内容。
这么复杂的结构交给用户使用并不合适,所以asio
提出了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函数的参数使用。
简单概括一下,我们可以用buffer()
函数生成我们要用的缓存存储数据。比如boost的发送接口send要求的参数为ConstBufferSequence
类型
1 2 template <typename ConstBufferSequence>std::size_t send (const ConstBufferSequence & buffers) ;
如果将“Hello World”转换成该类型:
1 2 3 4 5 6 7 8 9 10 void use_const_buffer () { std::string buf = "Hello World!" ; boost::asio::const_buffer asio_buf (buf.c_str(), buf.length()) ; std::vector< boost::asio::const_buffer> buffers_sequence; buffers_sequence.push_back (asio_buf); }
实际中我们使用并没有这么复杂,简化上述函数,用buffer函数将其转化为send需要的参数类型,output_buf可以直接传递给send接口使用,充当ConstBufferSequence类型:
1 2 3 4 5 void use_buffer_str () { boost::asio::const_buffers_1 optput_buf = boost::asio::buffer ("hello world" ); }
也可以将数组转换为send需要的类型(ConstBufferSequence):
1 2 3 4 5 6 7 8 9 10 11 12 13 void use_buffer_array () { const size_t BUF_SIZE_BYTES = 20 ; std::unique_ptr<char []> buf (new char [BUF_SIZE_BYTES]) ; auto input_buf = boost::asio::buffer (static_cast <void *>(buf.get ()), BUF_SIZE_BYTES); }
对于流式操作,我们可以用streambuf,将输入输出流和streambuf绑定,可以实现流式输入和输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 void use_stream_buffer () { asio::streambuf buf; std::ostream output (&buf) ; output << "Message1\nMessage2" ; std::istream input (&buf) ; std::string message1; std::getline (input, message1); }
2.2 同步写write_some boost::asio
提供了几种同步写的api,write_some
可以每次向指定的空间(socket) 写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数。举个栗子,用户buffer发送缓冲区长度为5 ,TCP发送缓冲区长度为12 ,虽然下面的write_some
一开始希望发送buf.length() - total_bytes_written = 12 - 0=12
个长度,但是用户buffer只有5 个,所以只能先发送5 个,剩下的7 个循环继续发送:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 void write_to_socket (boost::asio::ip::tcp::socket& sock) { std::string buf = "Hello World!" ; std::size_t total_bytes_wtitten = 0 ; while (total_bytes_wtitten != buf.length ()) { total_bytes_wtitten += sock.write_some (boost::asio::buffer (buf.c_str () + total_bytes_wtitten, buf.length () - total_bytes_wtitten)); } } int send_data_by_write_some () { 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_context ioc; boost::asio::ip::tcp::socket sock (ioc, ep.protocol()) ; sock.connect (ep); write_to_socket (sock); } catch (boost::system::system_error& e){ std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } }
2.3 同步写send write_some使用起来比较麻烦,需要多次调用(while) ,asio提供了send函数。send函数会一次性 将buffer中的内容发送给对端,如果有部分字节因为发送缓冲区满无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 int send_data_by_send () { std::string raw_ip_address = "127.0.0.1" ; unsigned short port_num = 3333 ; std::string buf = "Hello World!" ; try { boost::asio::ip::tcp::endpoint ep (boost::asio::ip::address::from_string(raw_ip_address), port_num) ; boost::asio::io_context ioc; boost::asio::ip::tcp::socket sock (ioc, ep.protocol()) ; sock.connect (ep); int send_length = sock.send (boost::asio::buffer (buf.c_str (), buf.length ())); if (send_length <= 0 ) return 0 ; } catch (boost::system::system_error& e) { std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } }
2.4 同步写write 类似send方法,asio还提供了一个write函数,可以一次性 将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int send_data_by_write () { std::string raw_ip_address = "127.0.0.1" ; unsigned short port_num = 3333 ; std::string buf = "Hello World!" ; try { boost::asio::ip::tcp::endpoint ep (boost::asio::ip::address::from_string(raw_ip_address), port_num) ; boost::asio::io_context ioc; boost::asio::ip::tcp::socket sock (ioc, ep.protocol()) ; sock.connect (ep); int send_length = boost::asio::write (sock, boost::asio::buffer (buf.c_str (), buf.length ())); if (send_length <= 0 ) return 0 ; } catch (boost::system::system_error& e) { std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } }
2.5 同步读read_some 同步读和同步写类似,提供了读取指定字节数的接口read_some,但read_some可能会被多次调用,比较麻烦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 std::string read_from_socket (boost::asio::ip::tcp::socket& sock) { const unsigned char MESSAGE_SIZE = 7 ; char buf[MESSAGE_SIZE]; std::size_t total_byter_read = 0 ; while (total_byter_read != MESSAGE_SIZE) { total_byter_read += sock.read_some (boost::asio::buffer (buf + total_byter_read, MESSAGE_SIZE - total_byter_read)); } return std::string (buf, total_byter_read); } int read_data_by_read_some () { 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_context ioc; boost::asio::ip::tcp::socket sock (ioc, ep.protocol()) ; sock.connect (ep); read_from_socket (sock); } catch (boost::system::system_error& e) { std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } }
2.6 同步读receive 可以一次性同步接收对方发送的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int read_data_by_receive () { 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_context ioc; boost::asio::ip::tcp::socket sock (ioc, 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 ) { std::cout << "receive failed " << std::endl; } } catch (boost::system::system_error& e) { std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } }
2.7 同步读read 可以一次性同步读取对方发送的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 int read_data_by_read () { 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_context ioc; boost::asio::ip::tcp::socket sock (ioc, 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 ) { std::cout << "receive failed " << std::endl; } } catch (boost::system::system_error& e) { std::cout << "Error occured! Error code = " << e.code () << ". Message: " << e.what (); return e.code ().value (); } }
2.8 读取直到指定字符 我们可以一直读取,直到读取指定字符结束:
1 2 3 4 5 6 7 8 9 10 11 12 13 std::string read_data_by_until (boost::asio::ip::tcp::socket& sock) { boost::asio::streambuf buf; boost::asio::read_until (sock, buf, '\n' ); std::string message; std::istream input_stream (&buf) ; std::getline (input_stream, message); return message; }
总结:
1.c++中,sock.receive和boost::asio::read有什么区别?
同:sock.receive
和 boost::asio::read
都用于网络编程中的数据接收操作
不同:1)sock.receive
相对比较简单,只是接收数据,并返回接收的数据长度或错误代码。它不具备高级功能,如超时处理、数据分片重组、异步操作等。boost::asio::read
提供了更高层次的功能封装。例如,它可以在接收到特定数量的字节后才返回结果,还可以支持异步操作 (async_read
),使得程序能够同时处理多个 I/O 操作。2)sock.receive
本身是一个阻塞调用,如果需要非阻塞行为,需要使用特定的套接字选项来配置。boost::asio::read
支持同步和异步两种方式,通过 boost::asio::io_context
及回调机制,可以轻松实现异步 I/O 操作。