1.2 创建一个端点

来源:互联网 发布:淘宝网店地址怎么改 编辑:程序博客网 时间:2024/06/11 21:30

1.2 创建一个端点

​ 对典型的客户端应用程序而言,在其能与服务器应用程序通信以获取服务之前,它必须获取运行服务器应用程序的主机的IP地址以及与服务器应用程序相关联的协议端口号。由IP地址和协议端口号组成的一对值——唯一标识计算机网络中的特定主机上运行的特定应用程序——称为端点(endpoint)

​ 客户端应用程序通常通过应用程序UI,或命令行参数、或应用程序的配置文件,来获取标识服务器应用程序的IP地址和端口号。

​ 如果IP地址是IPv4地址(例如192.168.10.112),IP地址可以表示为包含点分十进制的字符串。如果IP地址是IPv6地址(例如,FE36::0404::C3FA::EF1E::3829),IP地址可以表示为包含十六进制数字的字符串。此外,服务器IP地址可以以间接形式提供给客户端应用程序,即包含DNS名称的字符串(例如localhost或www.google.com)。表示IP地址的另一种方法是使用整数,IPv4地址表示为32位整数,IPv6表示为128位整数。然而,由于这种方法表示的IP地址可读性差和可记忆性差,所以很少使用。

​ 如果客户端应用程序在与服务器应用程序通信之前被提供了一个DNS名称,则必须先解析DNS名称以获取运行服务器应用程序的主机的实际IP地址。有时,DNS名称可能对应着多个IP地址,在这种情况下,客户端应用程序可能想逐个尝试,直到找到可用的IP地址。我们将在本章后面介绍如何使用Boost.Asio解析DNS名称的方法。

​ 服务器应用程序也需要处理端点,它使用端点来指定要在其上侦听来自客户端的传入消息的IP地址和协议端口的操作系统。如果运行服务器应用程序的主机只有一个网络接口和一个分配给它的IP地址,那么服务器应用程序只有在此IP地址上进行监听这一个选择。然而,有时主机可能有多个网络接口和相应的多个IP地址,在这种情况下,服务器应用程序遇到一个难题,那就是如何选择适当IP地址以在其上侦听传入的消息。问题在于服务器应用程序对底层IP协议设置,分组路由规则,映射到相应IP地址的DNS名称等细节一概不知。因此,对于服务器应用程序而言,预测客户端应用程序发送的消息将通过哪个IP地址传递到主机是一件非常复杂的任务(有时甚至不可解决)。

​ 如果服务器应用程序只选择一个IP地址来监听传入的消息,它可能会错过路由到主机的其他IP地址的消息。因此,服务器应用程序通常希望监听主机上所有有效的IP地址,这保证服务器应用程序将接收到达任何IP地址和特定协议端口的所有消息。

​ 总而言之,端点服务于两个目标:

  • 客户端应用程序使用端点来指定要与之通信的特定服务器应用程序。
  • 服务器应用程序使用端点来指定要接收从客户端传入消息的本地IP地址和端口号。如果主机有多个IP地址,则服务器应用程序将创建一个表示所有IP地址的特殊端点。

    这个方法说明如何使用Boost.Asio在客户端应用程序和服务器应用程序中创建端点。

Getting ready

​ 在创建端点之前,客户端应用程序必须获取将要与之通信的服务器应用程序的原始IP地址和指定协议端口号。另一方面,由于服务器应用程序通常监听所有IP地址上传入的消息,所以只需获取要监听的端口号。

​ 这里,我们不考虑应用程序如何获得原始IP地址或端口号。在以下方法中,我们假设应用程序已经获取IP地址和端口号,并且在相应算法开始时有效。

How to do it…

​ 以下算法和相应代码示例说明了创建端点的两种常见情况。第一个代码示例说明了客户端应用程序如何创建一个端点来指定要与之通信的服务器应用程序。第二个示例说明了服务器应用程序如何创建一个端点来指定它希望监听哪个IP地址和端口,从而接收客户端应用程序传入的消息。

Creating an endpoint in the client to designate the server

​ 以下算法描述了,为了创建指定客户端将要与之通信的服务器应用程序的端点,在客户端应用程序中所需执行的步骤。最初,如果IP地址是IPv4地址,则应表示包含点分十进制的字符串;如果IP地址是IPv6地址,则应表示包含十六进制数字的字符串:

1.获取服务器应用程序的IP地址和端口号。IP地址应指定为包含十进制数字(IPv4)或十六进制数字(IPv6)的字符串。

2.使用asio:: ip :: address类的对象表示原始IP地址。

3.使用步骤2中创建的address对象和端口号实例化asio:: ip :: tcp :: endpoint类的对象。

4.endpoint已经可以在Boost.Asio通信相关的方法中用于指定服务器应用程序。

​ 以下代码示例说明了算法的可能实现:

#include<boost/asio.hpp>#include<iostream>usingnamespace boost;int main(){    //步骤1.假设客户端应用程序已经获得了IP地址和协议端口号。    std::string raw_ip_address = "127.0.0.1";    unsigned short port_num = 3333;   //用于存储解析原始IP地址时发生的错误信息。   boost::system::error_code ec;   //步骤2.使用IP协议版本无关的地址表示。   asio::ip::address ip_address = asio::ip::address::from_string(raw_ip_address, ec);    if(ec.value() != 0)    {       //如果提供的IP地址无效,退出执行        std::cout<< "Failed to parse the IP address. Error code = " << ec.value()                  << ". Message: " << ec.message();        returnec.value();    }    //步骤3.   asio::ip::tcp::endpoint ep(ip_address, port_num);   //步骤4.端点准备就绪,可用于指定客户端要与之通信的网络中的特定服务器。    return0;}

Creating the server endpoint

​ 以下算法描述了,在服务器应用程序中创建端点所需执行的步骤。此端点用于指定服务器应用程序希望侦听的主机上的所有有效的IP地址以及端口号,以接收客户端传入的消息:

1.获取服务器应用程序侦听传入请求的协议端口号。

2.创建asio:: ip :: address类的特殊实例,表示运行服务器应用程序的主机上所有有效的IP地址。

3.使用步骤2创建的address对象和端口号实例化asio:: ip :: tcp :: endpoint类的对象。

4.端点可以用于指定服务器想要监听所有IP地址和特定协议端口号上的传入消息的操作系统。

​ 以下代码示例说明了算法的可能实现。请注意,这里假设服务器应用程序将通过IPv6协议进行通信:

#include<boost/asio.hpp>#include<iostream>usingnamespace boost;int main(){   //步骤1.这里我们假设服务器应用程序已经获得了协议端口号。    unsigned short port_num = 3333;   //步骤2.创建asio::ip::address类的特殊对象以指定主机上所有的有效IP地址。   //请注意,我们假设服务器通过IPv6协议工作。   asio::ip::address ip_address = asio::ip::address_v6::any();    //步骤3.   asio::ip::tcp::endpoint ep(ip_address, port_num);   //步骤4.创建端点,可用于指定服务器应用程序希望侦听传入连接的IP地址和端口号。    return 0;}

How it works…

​ 让我们考虑第一个代码示例,它实现的算法适用于那些向服务器应用程序主动发起通信会话的客户端应用程序。客户端应用程序需要被提供服务器应用程序的IP地址和协议端口号。通过步骤1可知,我们假设这些值已经被提供,并且在算法的开始有效。

​ 获得原始IP地址后,客户端应用程序必须使用Boost.Asio类型系统中的术语表示此IP地址。Boost.Asio提供了三个用于表示IP地址的类:

  • asio :: ip :: address_v4:表示IPv4地址。

  • asio :: ip :: address_v6:表示IPv6地址。

  • asio :: ip :: address:不知IP协议版本类(IP-protocol-version-agnosticclass),表示IPv4和IPv6地址。

​ 在上面的示例中,我们使用asio:: ip :: address类,从而保证客户端应用程序不必知道IP协议版本,这意味着它可以与IPv4和IPv6服务器一起透明地工作。

​ 在第2步中,我们使用asio:: ip :: address类的静态方法from_string()。此方法接受一个以字符串表示的原始IP地址,解析并验证该字符串,实例化asio:: ip :: address类的对象,并将其返回给调用者。该方法实现有四个重载,在我们的示例中,我们使用这一个:

static asio::ip::address from_string(const std::string & str, boost::system::error_code &ec);

​ 此方法非常有用,因为它会检查作为参数传递给它的字符串是否包含有效的IPv4或IPv6地址。如果地址有效,则实例化相应的对象。如果地址无效,则该方法将通过第二个参数指定错误。这意味着这个函数可用于验证原始用户输入。

​ 在第3步中,我们实例化一个boost:: asio :: ip :: tcp ::endpoint类的对象,将IP地址和协议端口号传递给其构造函数。现在,ep对象可以用于在Boost.Asio通信相关函数中指定服务器应用程序。

​ 虽然第二个示例与第一个示例有点不同,但其思路类似。服务器应用程序通常仅被提供侦听传入消息的协议端口号,而不提供IP地址。这是因为服务器应用程序通常希望监听主机上所有有效的IP地址的传入消息,而不仅是特定的IP地址。

​ 为了表示主机上所有有效的IP地址的概念,类asio:: ip :: address_v4和asio:: ip ::address_v6提供静态方法any(),这个方法实例化与主机上所有有效的IP地址这一概念相对应的类的特殊对象。在步骤2中,我们使用asio:: ip :: address_v6类的any()方法来实例化这样一个特殊的对象。

​ 请注意,不知IP协议版本类asio:: ip :: address不提供any()方法。服务器应用程序必须明确地指定其是否希望使用asio:: ip :: address_v4或asio:: ip :: address_v6类的any()方法返回的对象,相应地在IPv4或IPv6地址上接收传入请求。在第二个示例的第2步中,由于假设了服务器应用程序通过IPv6协议进行通信,因此我们使用asio:: ip :: address_v6类的any()方法。

​ 在第3步中,我们创建一个端点对象,它表示主机上所有有效的IP地址和一个特定的协议端口号。

There’s more…

​ 在我们之前的两个示例中,我们只使用在asio:: ip :: tcp类域内声明的endpoint类。如果我们打开asio:: ip :: tcp类的声明,我们将看到如下代码:

class tcp{public: //TCP端点的类型。 typedef basic_endpoint<tcp> endpoint; //...}

​ 这意味着此endpoint类是basic_endpoint<>模板类的专用化,专门用于指定通过TCP协议通信的客户端和服务器。

​ 然而,创建可以通过UDP协议进行通信的,在客户端和服务器中使用的端点同样简单。为了表示这样一个端点,我们需要使用在asio:: ip :: udp类域内声明的endpoint类。以下代码段演示了此endpoint类是如何声明的:

class udp{public: //UDP端点的类型。 typedef basic_endpoint<udp> endpoint; //...}

​ 例如,如果我们希望在客户端应用程序中创建一个端点,以指定要通过UDP协议与之进行通信的服务器应用程序,我们只需略微改变我们之前代码示例中的第3步的实现。这里,我们将突出显示需要更改的步骤:

 //步骤3.asio::ip::udp::endpoint ep(ip_address, port_num);

​ 因为其他代码是无关传输协议的,所有它们都不需要被更改。

​ 为了将通过TCP通信的服务器应用程序切换到通过UDP进行通信,我们需要在第二个代码示例中对步骤3执行同样的微不足道的修改。

See also

  • 将套接字绑定到端点的方法说明端点对象在服务器应用程序中的使用方式。

  • 连接套接字的方法说明如何在客户端应用程序中使用端点对象。

原创粉丝点击