【boost网络库从青铜到王者】第二篇:asio网络编程中的socket的监听和连接

1、网络编程基本流程

网络编程的基本流程对于服务端是这样:

  • 服务器:

    • socket——创建socket对象。

    • bind——绑定本机ip+port

    • listen——监听来电,若在监听到来电,则建立起连接。

    • accept——再创建一个socket对象给其收发消息。原因是现实中服务端都是面对多个客户端,那么为了区分各个客户端,则每个客户端都需再分配一个socket对象进行收发消息。

    • readwrite——就是收发消息了。消息就是协议,这里通过protobuf来解析。

  • 客户端

    • socket——创建socket对象。

    • connect——根据服务端ip+port,发起连接请求。

    • write、read——建立连接后,就可发收消息了。

图文如下:
在这里插入图片描述
接下来按照上述流程,我们用boost::asio逐步介绍。

2、终端节点endpoint的创建

2.1、客户端终端节点endpoint的创建

所谓终端节点就是用来通信的端对端的节点,可以通过ip地址和端口构造,其的节点可以连接这个终端节点做通信。

如果我们是客户端,我们可以通过对端的ip端口构造一个endpoint,用这个endpoint和其通信。

#include"asio_demo.h"

int32_t BoostAsio::ClientEndPoint(std::string& raw_ip_address) {
    
    

	//step1:Assume that the client application has already 
	// obtained the IP-address and the protocol port number.
	
	//std::string raw_ip_address = "127.0.0.1";
	uint16_t port_num = 9273;

	// Used to store information about error that happens
	// while parsing the raw IP-address.
	boost::system::error_code ec;

	//Step 2. Using IP protocol version independent address
	// representation.
	boost::asio::ip::address ip_address = boost::asio::ip::address::from_string(raw_ip_address, ec);
	if (0 != ec.value()) {
    
    
		std::cout << "Failed to parse the Ip Adress,error_code = " << ec.value() << " . Message: " << ec.message();
		return ec.value();
	}

	// Step 3.
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);

	// Step 4. The endpoint is ready and can be used to specify a 
	// particular server in the network the client wants to 
	// communicate with.
}

这段代码是一个使用 Boost.Asio 库创建 TCP 客户端端点的示例。以下是代码中每个步骤的解释:

  • 包含语句: 代码包含了 “asio_demo.h” 头文件。这个头文件可能包含了 Boost.Asio 库的声明和必要的包含。

  • ClientEndPoint 函数: 这个函数用于使用 Boost.Asio 创建 TCP 客户端端点。这个函数用于使用 Boost.Asio 创建 TCP 客户端端点,并接受一个 IP 地址字符串作为输入参数。

  • 步骤 1: 客户端应用程序假设已经获取了原始 IP 地址(在本例中是 “127.0.0.1”)和端口号(9273),以便与服务器通信。

  • 步骤 2Boost.Asio 提供了从字符串表示转换为 IP 地址的方法。在这里,它使用 boost::asio::ip::address::from_string 函数将原始 IP 地址字符串转换为 ip::address 对象。error_code 参数用于检查在解析过程中是否发生了错误。

  • 步骤 3boost::asio::ip::tcp::endpoint 类表示一个端点(IP 地址和端口号),可以用于指定网络中的特定服务器。使用解析后的 IP 地址和端口号创建了 ep 对象。

  • 步骤 4: 此时,ep 对象表示客户端端点,可以用于连接指定的服务器上的 TCP。然而,代码并没有显示实际连接过程或与服务器的任何通信,因此它只是为将来使用而准备端点。

请注意,这段代码似乎是更大程序的一部分,所提供的片段并没有显示客户端实际如何与服务器交互或执行网络通信。Boost.Asio 库通常用于在 C++ 中进行异步网络编程,允许开发人员创建高效和可扩展的网络应用程序。

2.2、服务器终端节点endpoint的创建

如果是服务端,则只需根据本地地址绑定就可以生成endpoint

int32_t BoostAsio::ServerEndPoint(uint16_t& port_num) {
    
    
	// Step 1. Here we assume that the server application has
	//already obtained the protocol port number.

	//uint16_t port_num = 9273;

	// Step 2. Create special object of asio::ip::address class
	// that specifies all IP-addresses available on the host. Note
	// that here we assume that server works over IPv6 protocol.

	boost::asio::ip::address ip_address = boost::asio::ip::address_v6::any();

	//Step 3.
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);

	// Step 4. The endpoint is created and can be used to 
   // specify the IP addresses and a port number on which 
   // the server application wants to listen for incoming 
   // connections.

	return 0;
}

这段代码是一个使用 Boost.Asio 库创建 TCP 服务器端点的示例。以下是代码中每个步骤的解释:

  • ServerEndPoint 函数: 这个函数用于使用 Boost.Asio 创建 TCP 服务器端点,并且传入一个端口号作为参数。

  • 步骤 1: 服务器应用程序已经获得了一个协议端口号,并将这个端口号作为参数传递给函数。

  • 步骤 2: 使用 Boost.Asioip::address_v6::any() 函数创建了一个特殊的 ip::address 对象,表示服务器将监听主机上的所有可用 IPv6 地址。这意味着服务器将监听所有可用的网络接口。

  • 步骤 3: 使用步骤 2 中创建的 IP 地址和传入的端口号,创建了一个 boost::asio::ip::tcp::endpoint 对象 ep,表示服务器端点。

  • 步骤 4: 此时,ep 对象表示一个可以用于监听传入连接请求的 TCP 端点,其 IP 地址为所有可用的 IPv6 地址,而端口号由函数参数 port_num 决定。

最后,函数返回一个值,这里是 0,表示函数执行成功。

总之,这段代码允许你通过传入一个端口号来创建一个服务器端点,然后服务器可以用这个端点在特定的 IP 地址和端口上监听传入的连接请求。

3、服务器与客户端通信套接字socket的创建

服务器和客户端所用的通信的socket都是一样的。创建socket分为4步:

  • 创建上下文io_context
  • 选择协议。
  • 生成通信socket
  • 打开通信socket
int32_t BoostAsio::CreateTcpSocket() {
    
    
	// Step 1. An instance of 'io_service' class is required by
	// socket constructor. 
	boost::asio::io_context context;	

	// Step 2. Creating an object of 'tcp' class representing
	// a TCP protocol with IPv4 as underlying protocol.
	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4();

	// Step 3. Instantiating an active TCP socket object.
	boost::asio::ip::tcp::socket sock(context);

	// Used to store information about error that happens
	// while opening the socket.
	boost::system::error_code ec;

	// Step 4. Opening the socket.
	sock.open(protocol, ec);
	if (0 != ec.value()) {
    
    
		//Failed to open the socket
		std::cout << "Failed to open the socket,error_code = " << ec.value() << ". Message: " << ec.message();
		return ec.value();
	}

	return 0;
}

这段代码是使用 Boost.Asio 库创建一个 TCP 套接字的过程,让我们逐步解释:

  • io_context 创建: 首先,通过创建 boost::asio::io_context 类的实例 context,用于管理异步 I/O 操作。

  • 协议选择: 选择使用 IPv4 的 TCP 协议,这里使用 boost::asio::ip::tcp::v4() 创建 tcp 对象来表示。

  • 创建套接字: 创建了一个 boost::asio::ip::tcp::socket 对象 sock,它代表一个 TCP 套接字。

  • 错误处理对象: 创建一个 boost::system::error_code 对象 ec,用于存储可能在打开套接字时发生的错误信息。

  • 打开套接字: 使用 sock.open(protocol, ec) 打开套接字。如果套接字打开过程中发生错误,会将错误码存储在 ec 中,然后检查 ec 是否非零,如果非零则表示打开套接字失败,输出错误信息。

  • 返回结果: 最后返回一个整数值,表示操作的结果。如果返回值为 0,表示套接字创建并打开成功。

这段代码演示了如何使用 Boost.Asio 库来创建并打开一个 TCP 套接字,并在过程中处理错误情况。这对于进行网络通信的程序非常有用,因为可以通过异步操作来管理网络连接和数据传输。

4、服务器监听套接字socket的创建

服务端,我们还需要生成一个acceptorsocket,用来监听接收新来自客户端的连接。

  • 创建上下文iocontext
  • 选择协议
  • 生成监听socket
  • 打开监听socket
int32_t BoostAsio::CreateAcceptSocket() {
    
    
	// Step 1. An instance of 'io_service' class is required by
	// socket constructor. 
	boost::asio::io_context context;

	// Step 2. Creating an object of 'tcp' class representing
    // a TCP protocol with IPv6 as underlying protocol.

	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v6();

	// Step 3. Instantiating an acceptor socket object.
	boost::asio::ip::tcp::acceptor accept(context);

	// Used to store information about error that happens
	// while opening the acceptor socket.
	boost::system::error_code ec;

	// Step 4. Opening the acceptor socket.
	accept.open(protocol, ec);
	if (0 != ec.value()) {
    
    
		std::cout << "Failed to open the accept socket!" << " Error code = " << ec.value() << " . Message: " << ec.message();
		return ec.value();
	}

	return 0;
}

这段代码是使用 Boost.Asio 库创建一个监听套接字**(acceptor socket)**的过程,让我们逐步解释:

  • io_context 创建: 通过创建 boost::asio::io_context 类的实例 context,用于管理异步 I/O 操作。

  • 协议选择: 选择使用 IPv6TCP 协议,这里使用 boost::asio::ip::tcp::v6() 创建 tcp 对象来表示。

  • 创建接收套接字: 创建了一个 boost::asio::ip::tcp::acceptor 对象 accept它代表一个接收来自客户端连接监听套接字。

  • 错误处理对象: 创建一个 boost::system::error_code 对象 ec,用于存储可能在打开监听套接字时发生的错误信息。

  • 打开接收套接字: 使用 accept.open(protocol, ec) 打开监听套接字。如果在打开过程中发生错误,会将错误码存储在 ec 中,然后检查 ec 是否非零,如果非零则表示打开监听套接字失败,输出错误信息。

  • 返回结果: 最后返回一个整数值,表示操作的结果。如果返回值为 0,表示监听套接字创建并打开成功。

这段代码演示了如何使用 Boost.Asio 库来创建并打开一个监听套接字,用于接受传入的连接请求。监听套接字通常用于服务器端,用于等待客户端的连接。

5、绑定accpet监听套接字

对于accept类型的socket,服务器要将其绑定到指定的断点,所有连接这个端点的连接都可以被接收到。

  • 创建端点(ip+端口)。
  • 创建监听套接字acceptor
  • 绑定监听套接字acceptor
int32_t BoostAsio::BindAcceptSocket(uint16_t& port_num) {
    
    
	// Step 1. Here we assume that the server application has
	// already obtained the protocol port number.

	// Step 2. Creating an endpoint.
	boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num);

	// Used by 'accept' class constructor.
	boost::asio::io_context context;

	// Step 3. Creating and opening an acceptor socket.
	boost::asio::ip::tcp::acceptor accept(context, ep.protocol());

	boost::system::error_code ec;

	// Step 4. Binding the acceptor socket.
	accept.bind(ep, ec);

	// Handling errors if any.
	if (0 != ec.value()) {
    
    
		std::cout << "Failed to bind the accept socket!" << "Error code : " << ec.value() << ". Message:" << ec.message();
		return ec.value();
	}
	return 0;
}

这段代码演示了使用 Boost.Asio 库创建创建并绑定一个监听套接字 (acceptor socket) 的过程,让服务器可以监听指定端口号上的连接请求。下面是代码的解释:

  • 创建端点: 通过创建 boost::asio::ip::tcp::endpoint 对象 ep,指定了一个 IP 地址为 boost::asio::ip::address_v4::any(),表示监听所有可用的 IPv4 地址。port_num 则是提前获取到的端口号。

  • io_context 创建: 创建了 boost::asio::io_context 类的实例 context,用于管理异步 I/O 操作。

  • 创建和打开监听套接字: 使用 boost::asio::ip::tcp::acceptor 对象的构造函数创建了一个监听套接字 accept,并指定了 ep.protocol()(即 IPv4)。

  • 错误处理对象: 创建一个 boost::system::error_code 对象 ec,用于存储可能在绑定监听套接字时发生的错误信息。

  • 绑定监听套接字: 使用 accept.bind(ep, ec) 绑定监听套接字到指定的 IP 地址和端口。如果在绑定过程中发生错误,会将错误码存储在 ec 中,然后检查 ec 是否非零,如果非零则表示绑定监听套接字失败,输出错误信息。

  • 返回结果: 最后返回一个整数值,表示操作的结果。如果返回值为 0,表示监听套接字绑定成功。

这段代码展示了如何使用 Boost.Asio 库创建一个绑定到指定 IP 地址和端口的监听套接字,用于等待客户端的连接请求。

6、客户端连接指定的端点

作为客户端可以连接服务器指定的端点**(ip+端口)** 进行连接。

  • 创建端点(ip+端口)。
  • 创建通信socket套接字。
  • 通信socket套接字连接到指定端点。
int32_t BoostAsio::ClientConnectServer(std::string& raw_ip_address, uint16_t& port_num) {
    
    
	// Step 1. Assume that the client application has already
	// obtained the IP address and protocol port number of the
	// target server.

	try {
    
    
		// Step 2. Creating an endpoint designating 
		// a target server application.
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);

		boost::asio::io_context context;

		// Step 3. Creating and opening a socket.
		boost::asio::ip::tcp::socket socket(context, ep.protocol());

		// Step 4. Connecting a socket.
		socket.connect(ep);

		// At this point socket 'sock' is connected to 
	   // the server application and can be used
	   // to send data to or receive data from it.

	}
	// Overloads of asio::ip::address::from_string() and 
	 // asio::ip::tcp::socket::connect() used here throw
	// exceptions in case of error condition.
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured! Error code = " << e.code() << " .Message:" << e.what();
		return e.code().value();
	}
	return 0;
}

这段代码实现了一个客户端连接服务器的过程。下面是代码的解释:

  • 创建端点: 通过使用 boost::asio::ip::address::from_string(raw_ip_address) 将提供的 IP 地址字符串解析为 IP 地址对象,并使用 port_num 作为端口号,创建了一个 boost::asio::ip::tcp::endpoint 对象 ep,该对象指定了要连接的服务器的终端。

  • io_context 创建: 创建了 boost::asio::io_context 类的实例 context,用于管理异步 I/O 操作。

  • 创建和打开套接字: 使用 boost::asio::ip::tcp::socket 对象的构造函数创建了一个客户端套接字 socket,并指定了 ep.protocol(),即服务器的协议类型。

  • 连接套接字: 使用 socket.connect(ep) 方法来连接服务器。这一步骤将客户端的套接字与服务器的套接字建立连接,从此客户端可以通过该套接字与服务器进行通信。

  • 错误处理: 使用 try-catch 语句来捕获可能发生的异常。如果连接过程中发生错误,会抛出 boost::system::system_error 异常。在 catch 块中,会输出错误信息,包括错误码和错误消息。

  • 返回结果: 最后返回一个整数值,表示操作的结果。如果返回值为 0,表示连接成功。

这段代码用于在客户端创建套接字并连接到服务器,以便与服务器进行数据交换。

7、服务器接收连接

当有客户端连接时,服务器需要接收连接:

  • 创建端点(ip+端口)。
  • 创建监听套接字acceptor
  • 绑定监听套接字acceptor
  • 监听队列大小。
  • 创建与客户端通信套接字。
  • 接收客户端连接。
int32_t BoostAsio::ServerAcceptClientConnect(uint16_t& port_num) {
    
    
	// The size of the queue containing the pending connection
	// requests.
	//连接队列
	const int BACKLOG_SIZE = 30;

	// Step 1. Here we assume that the server application has
	// already obtained the protocol port number.

	// Step 2. Creating a server endpoint.
	boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num);

	boost::asio::io_context context;

	try {
    
    
		// Step 3. Instantiating and opening an acceptor socket.
		boost::asio::ip::tcp::acceptor accept(context, ep.protocol());

		// Step 4. Binding the acceptor socket to the 
		// server endpint.
		accept.bind(ep);

		// Step 5. Starting to listen for incoming connection
		// requests.
		accept.listen(BACKLOG_SIZE);

		// Step 6. Creating an active socket.
		boost::asio::ip::tcp::socket socket(context);

		// Step 7. Processing the next connection request and 
		// connecting the active socket to the client.
		accept.accept(socket);

		// At this point 'sock' socket is connected to 
		//the client application and can be used to send data to
		// or receive data from it.
	}
	catch (boost::system::system_error& e) {
    
    
		std::cout << "Error occured! Error code = " << e.code() << ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

这段代码实现了服务器接受客户端连接的过程。以下是代码的解释:

  • 连接队列大小: 定义了连接队列中待处理的连接请求的最大数量,通过 const int BACKLOG_SIZE = 30; 定义。

  • 创建服务器端点: 使用 boost::asio::ip::tcp::endpoint 对象创建一个服务器端点对象 ep,使用 boost::asio::ip::address_v4::any() 表示服务器可以绑定到任何可用的 IPv4 地址,同时使用提供的 port_num 作为端口号。

  • io_context 创建: 创建了 boost::asio::io_context 类的实例 context,用于管理异步 I/O 操作。

  • 实例化和打开接受套接字: 使用 boost::asio::ip::tcp::acceptor 构造函数创建一个接受套接字 accept,并指定协议类型。

  • 绑定接受套接字: 使用 accept.bind(ep) 方法将接受套接字绑定到服务器端点。

  • 开始监听连接请求: 使用 accept.listen(BACKLOG_SIZE) 方法开始监听传入的连接请求,并指定连接队列的大小

  • 创建活动套接字并处理连接请求: 创建一个活动套接字 socket,并使用 accept.accept(socket) 方法来处理下一个连接请求,并将活动套接字与客户端连接,进行协议传输。

  • 错误处理: 使用 try-catch 语句来捕获可能发生的异常。如果发生错误,会抛出 boost::system::system_error 异常。在 catch 块中,会输出错误信息,包括错误码和错误消息。

  • 返回结果: 最后返回一个整数值,表示操作的结果。如果返回值为 0,表示连接成功。

这段代码用于在服务器端创建接受套接字,绑定端点并开始监听连接请求,然后处理下一个连接请求并与客户端建立连接,以便进行数据交换。

8、ipv6协议和ipv4协议区别

IPv4(Internet Protocol version 4)IPv6(Internet Protocol version 6) 是互联网通信中使用的两种不同版本的IP协议,它们有以下主要区别:

  • 地址长度:

    • IPv4使用32位二进制地址,通常以点分十进制表示(例如:192.168.1.1)。
    • IPv6使用128位二进制地址,通常以冒号分隔的十六进制表示(例如:2001:0db8:85a3:0000:0000:8a2e:0370:7334)。
  • 地址数量:

    • IPv4提供了大约42亿个唯一地址,但随着互联网的发展,IPv4地址耗尽成为了问题,需要使用NAT等技术来实现地址复用。
    • IPv6提供了远远超过IPv4的地址数量,理论上可以支持约340十万亿亿亿亿个唯一地址,因此可以解决IPv4地址耗尽问题。
  • 地址表示:

    • IPv4地址以32位二进制表示,使用四个8位字节表示,每个字节使用十进制表示。
    • IPv6地址以128位二进制表示,使用八个16位字节表示,每个字节使用十六进制表示。
  • 地址类型:

    • IPv4的地址分为公有地址(用于公共互联网)和私有地址(用于局域网内部)。
    • IPv6的地址一般都是全球唯一的,不再使用私有地址的概念,因为地址数量足够满足所有需求。
  • 协议特性:

    • IPv6引入了一些新的特性,如移动IPv6支持、自动地址配置、IPSec等,以提升网络的效率、安全性和功能。
  • 包头大小:

    • IPv6包头相对于IPv4包头有一些增加,主要是因为引入了更多的特性,但这也使得IPv6的路由和分片等操作更加高效。
  • 部署和过渡:

    • 由于IPv4已经被广泛使用,IPv6的过渡是逐步进行的,许多网络同时支持IPv4IPv6,以保障兼容性。
    • 一些服务提供商、网站和设备已经开始支持IPv6,但完全的IPv6广泛部署仍在进行中。

总体来说,IPv6是为了解决IPv4地址耗尽问题而设计的,它提供了更大的地址空间和更多的功能,逐渐在全球范围内推广和部署。

9、0.0.0.0地址和127.0.0.1地址区别

  • 0.0.0.0地址:

    • 0.0.0.0: 这个 IP 地址表示“所有可用的 IP 地址”或“任何主机”。当一个服务器监听在这个 IP 地址上,它将能够接受来自网络上所有可用 IP 地址的连接。这在服务器需要在多个网络接口上监听时很有用,因为它能够接受来自所有接口的连接请求。这通常用于公共服务器,如 Web 服务器,以便能够处理来自不同来源的连接。
  • 127.0.0.1:

    • 127.0.0.1: 这是称为“回环地址”的特殊 IP 地址。它是本地主机上的回环接口,也就是说,它指向自己。当一个程序连接到 127.0.0.1 地址时,它实际上连接到本地计算机上的自身,这对于测试网络通信或本地服务非常有用。因此,127.0.0.1 被称为“本地回环地址”或“本地主机”。

猜你喜欢

转载自blog.csdn.net/qq_44918090/article/details/132275979