• “.NET 4.0 网络开发入门之旅系列文章”—— IP 知多少?(下)


    “.NET 4.0 网络开发入门之旅系列文章”——

       IP 知多少?(下)


    4. IP终结点

             现在介绍.NET网络开发中最重要的一个概念——IP终结点,.NET基类库中使用IPEndPoint类型代表它。

             要想理解它,还得从TCP/IP说起。

             前面说过,所有连在网络上的计算机都必须要有一个唯一的IP地址,它用于区分开网络上的不同计算机,现在的问题是:一台网络计算机上可能跑着多个网络应用程序,它们可能会使用同一个网络接口从网络中接收(或发送)数据,因而共享同一个IP地址,在这种情况下,你怎么将到达主机的数据包转发给真正的“需求者”?

             为了解决这个问题,TCP/IP协议设计者引入了“端口(Port)”这个概念,规定每个提供网络服务的应用程序都必须指定一个“端口”,不同的网络应用程序不能使用相同的端口。

             请看以下TCP数据包的结构:


    4

             每个TCP数据包都包容着两个端口信息(每个端口占2个字节,是一个16位的二进制数值,所以,最大端口值为216次方减1,为65535)。Source port指明发送此数据包的网络应用程序使用的端口,Destination port指明接收方网络应用程序使用的端口。

             这样一来,接收方计算机依据Destination port,就可以把此数据包转发给真正的网络应用程序。

             Windows平台上,操作系统内核中的TCP/IP驱动(\Windows\System32\drivers文件夹下的tcpip.sys)负责完成处理TCP数据包的工作。

             端口的问题解决了,但我们在TCP数据包中没有看到IP地址啊?没有这个地址,数据包怎样知道应该被送给哪台计算机?

             方法是这样的。

             由于TCP协议建构于IP协议之上, TCP数据包由IP数据包承载,(图5)。


    5

             5中清晰地展示出IP数据包中包容有发送此数据包的主机IP地址(即Source IP Address)和接收此数据包的主机IP地址(即Destination IP Address)信息。

             5中的“数据(Data)”部分,包容的就是TCP数据包。

             由此可知,IP地址与端口唯一地标识了一个网络中的网络应用程序,我们将这个组合称为“IP终结点(IP EndPoint)”,IP EndPoint是一个网络服务的访问点。

            

             提示:

             类似地,WCF中也有一个服务终结点(ServiceEndpoint),它代表一个WCF服务的访问点。大家看到了软件技术各领域之间的联系了吗?

             .NET中,使用IPEndPoint类表示一个IP终结点(图6),它派生自抽象基类EndPoint,注意它的3个属性(Address\AddressFamily\Port)所表达的重要信息。


    6

             .NET网络应用程序中,套接字(Socket)对象必须绑定到一个IPEndPoint对象。

             为了方便,我们编写了一个静态方法GetRemoteMachineIPEndPoint,并将其添加到前面介绍过的AddressHelper静态类中:

           

    // 以交互方式生成有效的远程主机访问终结点,适用于控制台程序
            public static IPEndPoint GetRemoteMachineIPEndPoint()
            {
                IPEndPoint iep 
    = null;
                
    try
                {
                    Console.Write(
    "请输入远程主机的IP地址:");
                    IPAddress address 
    = IPAddress.Parse(Console.ReadLine());
                    Console.Write(
    "请输入远程主机打开的端口号:");
                    
    int port = Convert.ToInt32(Console.ReadLine());
                    
    if (port > 65535 || port < 1024)
                        
    throw new Exception("端口号应该为[1024,65535]范围内的整数");
                    iep 
    = new IPEndPoint(address, port);
                }
                
    catch (ArgumentNullException)
                {
                    Console.WriteLine(
    "输入的数据有误!");
                }
                
    catch (FormatException)
                {
                    Console.WriteLine(
    "输入的数据有误!");
                }
                
    catch (Exception ex)
                {
                   Console.WriteLine(ex.Message);
                }
               
    return iep;
            }

             后面的示例将大量使用此方法创建可供Socket绑定的IP终结点对象。

    5. 我可以使用哪个端口?

             在实际开发中,经常需要为某个网络服务动态地指定一个端口,为避免与一些重要的网络服务程序冲突(比如80端口固定为Web服务器所使用)通常要求这个端口应位于[1024,65535]这区间内。

             现在的问题是怎样从中选取一个没有被占用的端口?要知道,某台计算机上运行的网络应用程序可能有多个,而且在不同的时间段内也不一样。

             IPAddress.Any这个特殊的地址可以派上用场了,当一个套接字对象绑定一个IPAddress.Any对象,并给其指定端口号为0时,Windows会自动为其分配一个未被占用的端口号,当套接字对象不再使用而可以被回收时,此端口将可以被复用。

             由此我们可以得到以下的思路:

             创建一个套接字对象,让它绑定到IPAddress.Any对象,提取出系统分配的端口,然后再销毁掉此套接字对象。

             Socket对象实现了IDisposable接口,所以可以使用using关键字来自动回收其占用的资源:

             以下是我们编写的代码片段:

    public static int GetOneAvailablePortInLocalhost()
    {
             IPEndPoint ep 
    = new IPEndPoint(IPAddress.Any, 0);
             
    using (Socket tempSocket = new Socket(AddressFamily.InterNetwork,
                                 SocketType.Stream, ProtocolType.Tcp))
             {
                       tempSocket.Bind(ep);
                       IPEndPoint ipep 
    = tempSocket.LocalEndPoint as IPEndPoint;
                       
    return ipep.Port;
             }
    }


             提示:

             一个对象实现IDisposable接口有许多讲究,.NET为此设计了一个“IDisposable编程模式”,请参看《.NET 4.0面向对象编程漫谈》的5.5节《与对象销毁相关的话题》

             呵呵,现在,我们在应用程序中可以随时调用AddressHelper. GetOneAvailablePortInLocalhost()方法获取一个可用端口了,真给力!

             但别高兴得太早!

             你考虑过两个网络应用程序(或同一应用程序的两个线程)同时调用上述方法获取可用端口的情形吗?

             这时,无法保证此方法的两次调用一定返回不同的端口。如果两个Socket对象尝试绑定到同一个IPEndPoint,将会引发一个SocketException异步。

             为此,我们需要一个能跨越进程边界的线程同步手段。

             命名的互斥同步对象Mutex可以解决这个问题,修正后的代码如下:

    // 获取本机当前可用的端口号,此方法是线程安全的
    public static int GetOneAvailablePortInLocalhost()
    {
             Mutex mtx 
    = new Mutex(false"MyNetworkLibrary.AddressHelper.GetOneAvailablePort");
             
    try
             {
                       mtx.WaitOne();
                       IPEndPoint ep 
    = new IPEndPoint(IPAddress.Any, 0);
                       
    using (Socket tempSocket = new Socket(
                                AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
                       {
                                tempSocket.Bind(ep);
                                IPEndPoint ipep 
    = tempSocket.LocalEndPoint as IPEndPoint;
                               
    return ipep.Port;
                       }
             }
             
    finally
             {
                       mtx.ReleaseMutex();
             }
    }
     

    提示:

             .NET 4.0面向对象编程漫谈》的17.3.2节详细介绍了Mutex对象的使用方法,15.3.5节介绍了如何活用命名Mutex对象实现应用程序的“单例模式”(即此应用程序不允许同时启动两个进程),以及如何利用它来现进程间的“通知机制”。建议读者对照学习。

      其实严格地说,除非将获取可用端口号以及后继的Socket对象绑定工作一起用Mutex“保护”起来,否则上述修改过的代码还是有可能得到一样的端口号的, 但这种可能性非常低,就算发生了,也可以很容易地使用异常捕获方法处理Socket对象绑定异常,再次尝试将其绑定到一个新的可用端口。

            

             好了,本文就写到这里吧。

             给读者留个作业:

             1 依据本文介绍的内容使用Visual Studio创建类库项目MyNetworkLibrary,将GetLocalhostIPv4Addresses()GetRemoteMachineIPEndPoint()GetOneAvailablePortInLocalhost()三个方法封装到AddressHelper静态类中,后面文章的示例将调用它们。

             2 .NET基类库中提供了一个Ping组件用于检测网络连接性,请先通过MSDN自学它的使用方法。

             后一篇文章——《我在“网”中央》(暂名)将介绍这方面的内容。

     
  • 相关阅读:
    free和delete把指针怎么啦?
    动态内存会被自动释放吗?
    杜绝“野指针”
    有了malloc/free为什么还要new/delete ?
    Linux Shell Bash 带有特殊含义的退出码
    centos中更换jdk的版本
    shift移动变量
    shell script针对参数已经有配置好变量名称
    输入两个数后输出相乘的结果
    输入变量
  • 原文地址:https://www.cnblogs.com/bitfan/p/1897829.html
Copyright © 2020-2023  润新知