IP

4.  IP 终结点

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

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

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

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

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

 

 

图 4

 

         每个 TCP 数据包都包容着两个端口信息(每个端口占 2 个字节,是一个 16 位的二进制数值,所以,最大端口值为 2 的 16 次方减 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 终结点对象。

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

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

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

         IPAddress.Any 这个特殊的地址可以派上用场了,当一个套接字对象绑定一个 IPAddress.Any 对象时, 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 对象实现应用程序的“单例模式”(即此应用程序不允许同时启动两个进程),以及如何利用它来现进程间的“通知机制”。

         建议读者对照学习。

        

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

         给读者留个作业:

         1 依据本文介绍的内容使用 Visual Studio 创建类库项目 MyNetworkLibrary ,将 GetLocalhostIPv4Addresses() 、 GetRemoteMachineIPEndPoint() 、 GetOneAvailablePortInLocalhost

() 三个方法封装到 AddressHelper 静态类中,后面文章的示例将调用它们。

 

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

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/bitfan/archive/2010/12/06/6058367.aspx

发布了23 篇原创文章 · 获赞 0 · 访问量 2万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章