NCindy

.net平台上的高性能网络程序开发框架

使用C#查询路由接口,同时小议一些.net 2.0的诡异API

有时候我们希望知道程序正在使用那个IP地址连接到远程的服务器(类似pathping命令返回的第一个结果,P2P应用中尤其多见),文末的代码可以完成这个任务


写完这段代码之后,我对.net API的设计有些不满了。

首先就是Socket类的IOControl方法,该方法脱胎于Winsock2 API的WSAIoctl函数。对于基于C语言的Winsock2 API,设计出WSAIoctl显得还合情合理,虽然冗长的参数列表很是吓人,但是对于面向对象的C#,居然还需要使用byte[]这些弱类型的数据结构来做参数,实在是有些匪夷所思了,我觉得Socket类应该对IOControl进行充分的包装,以便没有Win32 API经验的用户更好的使用。

 

接着是IPEndPoint的序列化形式SocketAddress类,这个类明显的与sockaddr structure一样,不同的是,它比sockaddr structure更加难以使用。它提供了一个象数组一样的索引器,允许用户以[]运算符获取其中的byte元素,但是却不提供方法简便的将其中的内容复制到一个byte[]中供Socket.IOControl调用,竟然需要客户自己使用循环来调用,实在傻的可以。

 

最后是IPEndPoint,它居然需要实例化之后才能调用Create成员函数把一个SocketAddress实例反序列化成一个IPEndPoint对象,我晕,为啥不是静态的呢?看了这个Create方法的代码之后,我发现完全没有必要将其做成成员函数(为了证明静态方法的可行,我在文中创建了一个CreateIPEndPoint静态方法,并用它替换了IPEndPoint.Create成员方法),不知道为了使这个方法看起来更像是成员方法还是其他什么原因,SocketAddress的AddressFamily居然必须和IPEndPoint实例的AddressFamily一致,否则就抛出异常,狂晕,人家反序列化还得看你一个不知所谓的对象的脸色,真是惨。

 

以上是我的观点,欢迎大家一起议议。



using System;

using System.Net.Sockets;

using System.Net;

 

 

class Program

{

    static IPEndPoint QueryRoutingInterface(Socket sock,

                                            IPEndPoint remoteEP)

    {

        SocketAddress sa = remoteEP.Serialize();

 

        byte[] addrBytes = new byte[sa.Size];

 

        for (int i = 0; i < sa.Size; i++)

        {

            addrBytes[i] = sa[i];

        }

 

        byte[] outBytes = new byte[addrBytes.Length];

 

        sock.IOControl(IOControlCode.RoutingInterfaceQuery,

                                   addrBytes,

                                   outBytes);

 

        for (int i = 0; i < sa.Size; i++)

        {

            sa[i] = outBytes[i];

        }

 

 

        EndPoint ep = CreateIPEndPoint(sa);//remoteEP.Create(sa);

 

        return (IPEndPoint)ep;

    }

 

    /// <summary>

    /// 根据SocketAddress创建IPEndPoint

    /// </summary>

    /// <remarks>该函数从IPEndPointCreate方法反编译出来</remarks>

    /// <param name="socketAddress"></param>

    /// <returns></returns>

    public static IPEndPoint CreateIPEndPoint(SocketAddress socketAddress)

    {

        //if (socketAddress.Family != this.AddressFamily)

        //{

        //    throw new ArgumentException(SR.GetString("net_InvalidAddressFamily",

        //        new object[] { socketAddress.Family.ToString(),

        //            base.GetType().FullName, this.AddressFamily.ToString() }),

        //            "socketAddress");

        //}

 

        if (socketAddress.Size < 8)

        {

            //throw new ArgumentException(SR.GetString("net_InvalidSocketAddressSize",

            //    new object[] { socketAddress.GetType().FullName,

            //        base.GetType().FullName }),

            //        "socketAddress");

 

            throw new ArgumentException();

        }

 

//if (this.AddressFamily == AddressFamily.InterNetworkV6)

        if (socketAddress.Family == AddressFamily.InterNetworkV6)

        {

            byte[] buffer1 = new byte[0x10];

            for (int num1 = 0; num1 < buffer1.Length; num1++)

            {

                buffer1[num1] = socketAddress[num1 + 8];

            }

            int num2 = ((socketAddress[2] << 8) & 0xff00) | socketAddress[3];

            long num3 = (((socketAddress[0x1b] << 0x18)

                + (socketAddress[0x1a] << 0x10))

                + (socketAddress[0x19] << 8)) +

                        socketAddress[0x18];

            return new IPEndPoint(new IPAddress(buffer1, num3), num2);

        }

 

        int num4 = ((socketAddress[2] << 8) & 0xff00) | socketAddress[3];

 

        long num5 = ((((socketAddress[4] & 0xff)

            | ((socketAddress[5] << 8) & 0xff00))

            | ((socketAddress[6] << 0x10) & 0xff0000))

            | (socketAddress[7] << 0x18)) & ((long)0xffffffff);

 

        return new IPEndPoint(num5, num4);

    }

 

 

    static void Main(string[] args)

    {

        try

        {

            Socket s = new Socket(AddressFamily.InterNetwork,

                                  SocketType.Dgram,

                                  ProtocolType.Udp);

 

            s.Bind(new IPEndPoint(IPAddress.Any, 10001));

 

            IPEndPoint remoteEP = new IPEndPoint(

                Dns.GetHostAddresses("www.google.com")[0],

                1000);

 

            IPEndPoint ep = QueryRoutingInterface(s, remoteEP);

            Console.WriteLine(remoteEP);

            Console.WriteLine(ep);

        }

        catch (SocketException e)

        {

            Console.WriteLine(e);

            Console.WriteLine(e.ErrorCode);

        }

    }

}

 

 

程序的核心是QueryRoutingInterface方法,代码相当的简单,我就不多做解释了。

 

posted on 2007-01-02 19:16 iceboundrock 阅读(2504) 评论(12) 编辑 收藏

Feedback

#1楼 2007-01-02 21:49 didasoft      

呵呵,看样子你对这方面也不算太了解。

随便举个例子,你能说说string强类型应该怎么在winsock传呢?
选项那么多,没必要去封装
 回复 引用 查看   

#2楼[楼主] 2007-01-02 22:55 iceboundrock      

@didasoft
第一个问题,不是很理解哦。如果用string做IOControl某种封装的参数有啥不能处理的?在封装函数里面把它处理成byte []呗?

第二个问题,我觉得,IOControl方法的选项多不是问题吧,你觉得是几十个方法,每个方法都有强类型的参数更容易用还是一个方法,有几十种参数而且有着不知所谓的byte []参数容易用呢?我选择前者。

 回复 引用 查看   

#3楼 2007-01-03 23:39 James[匿名][未注册用户]

IOControl 的封装方式是为了扩展准备的,如果将其设计为枚举的形式,一旦 Winsock 有了更新,那么仅仅为了 IOControl 函数就不得不推出新的版本,这样耦合性太强了。  回复 引用   

#4楼[楼主] 2007-01-03 23:50 iceboundrock      

@James[匿名]
这个理由太牵强了点吧,如果Winsock更新了,很多程序都得改。
最起码,你初始化的时候就不能再用2.2版本来初始化Winsock库了吧。
多改这一处又如何啊?
另外,Winsock的error code,现在不也被封装成异常了?而且不同的error code有不同的异常描述,按照你的说法,这个举动岂不是耦合性更强?
 回复 引用 查看   

#5楼 2007-01-04 08:55 Dominic[未注册用户]

问一个问题:最后网络接口方便是怎么操作的呢?让.net框架自己转换string 到byte[]么?还要自己判断是否unicode?UTF8? GB2312?等等字符集么?


之后当OS的底层网络接口全部用.Net 实现之后才能像你说的那样做。这样看来microsoft目前这样做还是有道理的。

其实有很多封装类和控件的,没有人强迫你一定的用自带的IOControl 函数。
 回复 引用   

#6楼[楼主] 2007-01-04 09:27 iceboundrock      

@Dominic
第一,.net vm内部的string对象都是unicode编码,而Windows API接收的编码只有两种,分别在API的后缀有A和W来标记到底接收ASCII还是Unicode。

第二,IOControl的作用是为Socket设定参数选项的。我的想法是,增加一种IOControl的重载,接收一个选项接口。有多个选项类,实现选项接口。每种选项类封装一组IOControl所需的参数组,传递给IOControl。

第三,有封装只能说明原来的API使用不方便,不能说明原有的方法设计没有问题。
 回复 引用 查看   

#7楼 2007-01-05 00:48 Guest[未注册用户]

很多东西在你还没深入时,你觉得是没必要的,但是当你遇到更多问题时,你会发现M$做得是有道理的,任何问题不能只以你自己的角度去思考  回复 引用   

#8楼[楼主] 2007-01-05 08:38 iceboundrock      

@Guest
ooooo,知道我最不想听到的话是什么吗?就是你说的这段!充满了指责但是毫无建设性,甚至根本不提任何论据。难到MS就没有设计错过?ok,如果设计很完美,为啥会有那么多类/方法被deprecated?


你认为MS的设计就一定是正确的设计,有人觉得那个设计错了只是因为他的理解不深刻或者思考不全面?听听,多么象皇帝新衣里面那个伟大的裁缝的话啊。


当然,MS的设计可能真的是正确的,但是在我认为他是正确的之前,我需要充足的理由,而不是因为他是MS设计的我就认为他一定是正确的。
 回复 引用 查看   

#9楼 2007-06-07 19:57 ljf[未注册用户]

支持,程序员就需要有这种精神.  回复 引用   

#10楼 2007-10-09 13:31 wjywin[未注册用户]

MS 把什么都搞好了,我们还干什么?  回复 引用   

#11楼 2007-12-15 21:36 颖之守翼      

Create是成员方法的原因,我想应该是为了让EndPoint派生类继承这个方法。
interface的成员不能是static的
static的成员不能用virtual, override 和 abstract修饰。
所以M$为了让所有的EndPoint派生类都实现Create方法,就声明了一个成员方法。
而且考虑static method的调用方法,必须是类名.方法名,这样会使得代码里必须指定你使用的具体的EndPoint派生类。若是将来突然发现IPEndPoint不够用了,需要从它派生出一个新的EndPoint类;或者要放弃TCP/IP协议,换用其他的协议;更极端地,如果程序对所处的网络不确定,需要从外部获取一个EndPoint的派生类的对象来做网络操作……
所以,其实M$这样设计还是有道理的。
 回复 引用 查看   

#12楼[楼主] 2007-12-16 01:51 iceboundrock      

@颖之守翼
也许,不过请注意Create方法的参数,其实这个参数已经决定了反序列化的结果。
 回复 引用 查看   

导航

统计信息

News

搜索

 

常用链接

最新随笔

我的标签

随笔分类

随笔档案

积分与排名