这段时间一直和Socket纠缠不休,甚至还让公司搞了台电脑来辅助我进行测试.背景是这样的:
由于这个项目中一个基本功能就是有聊天、提醒等功能,即:可以接收别人的消息,可以发送消息给别人。
所以设计之初我就规划着这样一个组件:
1、随系统启动而启动,随系统关闭而关闭。(生死相随)
2、自己独立管理socket对象。(系统不用关心)
3、将接收到的消息经统一格式,随时转化为系统命令,如果不符合格式则不进行进一步处理。方便多人开发和系统安全。(当然为了更安全你可以加密或加上通信密钥)
即:系统只处理命令而与socket对象无关。在这里索性取其名为:消息盒子。
----------以上是背景-----------咯咯咯---咯咯咯---咯咯咯---咯咯咯---咯咯咯---咯咯咯---------------------------
首先我想到这些的时候在网上借鉴了一些Socket的用法(呵呵 及时充电,没办法。。。),例如:(http://benben.iteye.com/blog/279291)写出了如下代码:
/// <summary> /// 消息推送监听Socket /// </summary> Socket msgSocket; Socket acceptedSocket; /// <summary> /// 消息推送监听线程 /// </summary> Thread msgThread; /// <summary> /// 消息盒子实例化 /// </summary> /// <param name="owner"></param> /// <returns></returns> public EndPoint InitMsgBox(int port) { EndPoint localEndPoint = null; IPEndPoint adderss = getIpAdderss(port); if (adderss != null) { try { msgSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); msgSocket.Bind(adderss); msgSocket.Listen(1); localEndPoint = msgSocket.LocalEndPoint; msgThread = new Thread(new ThreadStart(MessageListener)); msgThread.Start(); } catch (Exception ex) { Log(ex); } } return localEndPoint; } /// <summary> /// 实时监听 /// </summary> private void MessageListener() { //do some thing } /// <summary> /// 获取Ip4地址 /// </summary> /// <param name="port"></param> /// <returns></returns> private IPEndPoint getIpAdderss(int port) { IPEndPoint result = null; IPAddress[] ipEntry = Dns.GetHostEntry(Dns.GetHostName()).AddressList; //取得本机IP result = new IPEndPoint(ipEntry[0], port); return result; }
我一直相信网上的大牛们考虑的比较周全,应该没什么问题,可是这个版本发给测试组的时候根本不能测试通过的。
经过一番调试我终于发现由于我的开发环境是xp并且只有唯一的网卡。而测试的环境是win7并且网卡很多!测试的环境是以上代码没有考虑到的!并且还有一个致命的、根本的错误是我没有意识到的下面让我一一道来:
伴随着敲键盘的声音,第二个版本终于浮出水面,我把上一个版本的getIpAdderss修改成了如下:
1 /// <summary> 2 /// 获取Ip4地址 3 /// </summary> 4 /// <param name="port"></param> 5 /// <returns></returns> 6 private IPEndPoint getIpAdderss(int port) 7 { 8 IPEndPoint result = null; 9 IPAddress[] ipEntry = Dns.GetHostEntry(Dns.GetHostName()).AddressList; //取得本机IP 10 foreach (IPAddress var in ipEntry) 11 { 12 if (!var.IsIPv6LinkLocal) 13 { 14 result = new IPEndPoint(var, port); 15 break; 16 } 17 } 18 return result; 19 }
就是把ip6的网卡给屏蔽掉,以避免用默认的第一个网卡在某些情况下出错,在这一方案处理不成的时候我就对测试说先禁用掉另一个不用的网卡来解决此问题。。。
当然禁用掉不用的网卡且只留下一个网卡以后,以上代码是可以正常运行的。但是同时我心里也是虚的,如果发布以后客户有这样的多网卡的电脑怎么办?让他们禁用掉网卡?客户不知道什么是网卡呢?网卡必须启用呢?
然后我就开始了寻求解决方案之路,按理说可以解决的。比如QQ不就是很典型的吗!人家也不存在说禁用网卡才能使用的说法。于是乎我在博问里发布了一个问题寻求解决方案(http://q.cnblogs.com/q/56141/):但是很明显在我解决问题之前几乎无人问津。。。这个比较尴尬了。一般无人问津的问题不是白痴问题就是冷门问题,很明显这个它不是冷门问题。。。
那就自己解决吧,just do it~~~
一番查看msdn文档,我注意到了一个静态只读属性即:IPAddress.Any 为什么会搞他出来呢?微软对它的解释是:指示服务器应侦听所有网络接口上的客户端活动
网络接口?在这里我猛然惊醒终于意识到了问题的根本所在:我把网络接口和TCP端口混在了一块!!! 由于受IPEndPoint类构造方法的影响我默默的认为每一个网卡都有一套TCP/IP协议端口,而忽略了它本不属于网卡。。。 而且有还有不少和我一样想法的人,例如:http://bbs.csdn.net/topics/390383397以及上面博问里面第一个回答我问题的@会长
找到问题所在于是最后的代码:
/// <summary> /// 消息推送监听Socket /// </summary> Socket msgSocket; Socket acceptedSocket; /// <summary> /// 消息推送监听线程 /// </summary> Thread msgThread; /// <summary> /// 消息盒子实例化 /// </summary> /// <returns></returns> public EndPoint InitMsgBox(int port) { EndPoint localEndPoint = null; IPEndPoint adderss = new IPEndPoint(IPAddress.Any, port); if (adderss != null) { try { msgSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); msgSocket.Bind(adderss); msgSocket.Listen(1); localEndPoint = msgSocket.LocalEndPoint; msgThread = new Thread(new ThreadStart(MessageListener)); msgThread.Start(); } catch (Exception ex) { Log(ex); } } return localEndPoint; } /// <summary> /// 实时监听 /// </summary> private void MessageListener() { //do some thing }
现在我们不用关心网卡的事情,而只需要关心端口的活动,其他事情交给操作系统。
----功能实现时不能被代码所迷惑,而更从宏观考虑是很有必要的。同时多了解操作系统方面底层的知识无疑是很有好处的,至少不会有太多疑惑。
下面贴出百度百科对网络端口的解释:
硬件领域的端口又称接口,如:USB端口、串行端口等。软件领域的端口一般指网络中面向连接服务和无连接服务的通信协议端口,是一种抽象的软件结构,包括一些数据结构和I/O(基本输入输出)缓冲区。
在这里也提醒提醒大家应该对当时没有实现的案例,及时标注以提醒引用者注意该情况的发生,至少体现了你逻辑思维的完整性和对自己代码的责任心
我还找到了一个类似的 http://www.cnblogs.com/technology/archive/2010/08/15/1799858.html
欢迎大家及时指正