自己写的客户端马上要发布了,忽然发现了一大堆问题,主要集中在与服务器的TCP连接经常莫名断开,客户端又检测不到,不能及时重连。一个多星期的修改,有一些心得,与大家分享。也希望大家多发表意见,您的意见也许最后就实现在我的软件中了!主要分为两部分:
一,如何更好的检测TCP连接是否正常
二,如何提取本机TCP连接状态一,如何更好的检测TCP连接是否正常
这方面问题,我上网查了很久,一般来说比较成熟的有两种方法:
1是在应用层制定协议,发心跳包,这也是C#,JAVA等高级语言比较常用的方法。客户端和服务端制定一个通讯协议,每隔一定时间(一般15秒左右),由一方发起,向对方发送协议包;对方收到这个包后,按指定好的通讯协议回一个。若没收到回复,则判断网络出现问题,服务器可及时的断开连接,客户端也可以及时重连。
2通过TCP协议层发送KeepAlive包。这个方法只需设置好你使用的TCP的KeepAlive项就好,其他的操作系统会帮你完成。操作系统会按时发送KeepAlive包,一发现网络异常,马上断开。我就是使用这个方法,也是重点向大家介绍的。使用第二种方法的好处,是我们在应用层不需自己定协议,通信的两端,只要有一端设好这个值,两边都能及时检测出TCP连接情况。而且这些都是操作系统帮你自动完成的。像我们公司的服务端代码就是早写好的,很难改动。以前也没加入心跳机制,后面要改很麻烦,boss要求检测连接的工作尽量客户端单独完成....
还有一个好处就是节省网络资源。KeepAlive包,只有很简单的一些TCP信息,无论如何也是比你自己设计的心跳包短小的。然后就是它的发送机制,在TCP空闲XXX秒后才开始发送。自己设计心跳机制的话,很难做到这一点。这种方法也是有些缺陷的。比如某一时刻,网线松了,如果刚好被KeepAlive包检测到,它会马上断开TCP连接。但其实这时候TCP连接也算是established的,只要网线再插好,这个连接还是可以正常工作的。这种情况,大家有什么好办法处理吗?C#中设置KeepAlive的代码
                uint dummy = 0;
                byte[] inOptionValues = new byte[Marshal.SizeOf(dummy) * 3];
                BitConverter.GetBytes((uint)1).CopyTo(inOptionValues, 0);
                BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, Marshal.SizeOf(dummy));
                BitConverter.GetBytes((uint)15000).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2);                IPEndPoint iep = new IPEndPoint(this._IPadd, xxxx);
                this._socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                this._socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
                this._socket.Connect(iep);
这里我设定TCP15秒钟空闲,就开始发送KeepAlive包,其实完全可是设定得长一点。
二,如何提取本机TCP连接状态
设好了KeepAlive值,又遇到麻烦了,我没找到当网络异常时,它断开连接后怎么通知我...我搜了很久都没找到,要是哪位兄弟知道的话告诉我吧。我是使用笨办法的,找到所有本地TCP连接的信息,筛选出我需要的那个TCP。
查看本机所有TCP连接信息,网上一般的方法,都是通过程序调用CMD命令里的netstat进行,然后再分析其内容。但在CMD窗口用过这个命令的都知道,悲剧的时候它显示完所有TCP信息需要15s,或者更长时间,这在我的程序中是不能忍受的。
然后我又查找了一些牛人的博客,发现有人提到用iphlpapi.dll。这是一个在win98以上操作系统目录System32都包含的库函数,功能异常强大,大家可以放心使用!但是使用起来比较麻烦,基本找不到C#现成使用的例子,就算有,也是很老版本的,完全不能用
我参考了这两位高人的博客
http://blog.csdn.net/yulinlover/archive/2009/02/08/3868824.aspx
(另一位的博客连接找不到了..悲剧啊!)
下载了里面提到的项目,仔细结合自己体会进行修改,终于能用了。每隔一段时间,我的客户端就用这个方法扫描一遍本地TCP信息,若发现连接有问题,则断开重连。
这个方法能瞬间得到本机所有TCP连接信息(如果你有兴趣可以扩充,它的功能真的是太强大了),没有CMD命令netstat那不能忍受的延迟,相当好用。代码比较长,就不贴出来了。
这些是我不太成熟的做法,下星期项目就要提交了,不能再出啥岔子,希望大家多提意见,帮我改善一下。
本版人气很旺,但貌似用socket的人不多,不知道帖子发这是否合适。要是不合适,请前辈提点下发在哪个版比较好?

解决方案 »

  1.   

    设好了KeepAlive值,又遇到麻烦了,我没找到当网络异常时,它断开连接后怎么通知我... 这句是什么意思不懂
      

  2.   

    呵呵,原来是我的Blog被楼主引用了,受用啊~~~~有好东西一定要分享哦!!
      

  3.   

    切,你应该叫姐姐
    不过据我所知设好了KeepAlive值后不会出现这样的问题啊,不然设置KeepAlive还有什么用呢
      

  4.   

    倒,真是MM啊
    不敢相信CSDN上有美女,所以...
    Keepalive这东西,在底层是可以直接知道链接断开的
    我和做嵌入式的师兄讨论,他写的嵌入式C程序就是通过发Keepalive包保持链接,一旦有错,函数会返回一个信息。但我查找了很多C#的资料,只找到这一个设置Keepalive值的东西,设好以后就不知道怎么继续了
    我后面用iphlpapi.dll   也是无奈才走了这条弯路  
    大家有什么好办法查看TCP链接状态吗?
      

  5.   

    c# 设置Keepalive就够了啊, 那你说怎么做可以出现你说的情况,姐做c#socket通信大半年了还没碰到你说的情况
      

  6.   

    我用的下面的代码        #region 检测网络状态        [DllImport("sensapi.dll")]
            private extern static bool IsNetworkAlive(out int connectionDescription);        private void NetCheckThread()
            {
                while (true)
                {
                    int flags;//上网方式
                    bool m_bOnline = true;//是否在线             
                    m_bOnline = IsNetworkAlive(out flags);
                    if (m_bOnline)
                    {
                        if (!NetAlive)
                        {
                            NetAlive = true;
                            try
                            {
                                if (NetConnect != null) NetConnect(this, null);
                            }
                            catch { }
                        }
                    }
                    else
                    {
                        if (NetAlive)
                        {
                            NetAlive = false;
                            try
                            {
                                if (NetDisconnect != null) NetDisconnect(this, null);
                            }
                            catch { }
                        }
                    }                Thread.Sleep(1);
                }
            }        #endregion
      

  7.   

    就比如我上面的代码,设置好Keepalive值,链接服务器
    然后线程循环监听服务器发过来的命令,按命令进行一些操作
    这个时候就捕捉不到异常
      

  8.   

    Keepalive 引发的异常不是在这个地方引发的
      

  9.   

    建议楼主发到vc版的网络通讯板块。对于dll,C#也好,vc也好都是差不多的。
      

  10.   

    17L的代码
    没啥说明,看着头痛哦
    if (m_bOnline)
                    {
                        if (!NetAlive)
                        {
                            NetAlive = true;
                            try
                            {
                                if (NetConnect != null) NetConnect(this, null);
                            }
                            catch { }
                        }
                    }
    这地方如何解释?细节如何?
      

  11.   

                            try 
                            { 
                                if (NetConnect != null) NetConnect(this, null); 
                            } 
                            catch { } 用来触发自定义事件的
      

  12.   


     public AsyncSocket(Socket sock,int index)
            {
                this.index = index;
                this.sock = sock;
                this.SetXinTiao(this.sock);
                Socket obj_Socket = sock;
                StateObject obj_SocketState = new StateObject();
                obj_SocketState.workSocket = obj_Socket;
                obj_Socket.BeginReceive(obj_SocketState.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), obj_SocketState);
            }        //设置心跳
            private void SetXinTiao(Socket tmpsock)
            {
                byte[] inValue = new byte[] { 1, 0, 0, 0, 0x20, 0x4e, 0, 0, 0xd0, 0x07, 0, 0 };// 首次探测时间20 秒, 间隔侦测时间2 秒
                tmpsock.IOControl(IOControlCode.KeepAliveValues, inValue, null);
            }        private void ReceiveCallback(IAsyncResult ar)
            {
                try
                {
                    StateObject obj_SocketState = (StateObject)ar.AsyncState;
                    Socket obj_Socket = obj_SocketState.workSocket;
                    int BytesRead = obj_Socket.EndReceive(ar);
                    if (BytesRead > 0)
                    {
                        byte[] tmp = new byte[BytesRead];
                        Array.ConstrainedCopy(obj_SocketState.buffer, 0, tmp, 0, BytesRead);
                        if (socketDataArrival!=null)
                        {
                            socketDataArrival(this.index, tmp);
                        }
                    }
                    else
                    {
                        if (this.sock.Connected)
                        {
                            if (socketDisconnected!=null)
                            {
                                socketDisconnected(index);
                            }
                        }
                    }
                    obj_Socket.BeginReceive(obj_SocketState.buffer, 0, StateObject.BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), obj_SocketState);
                }
                catch (Exception ex)
                {
                    if (socketDisconnected!=null)
                    {
                        socketDisconnected(index);  //Keepalive检测断线引发的异常在这里捕获
                    }
                }
            }
      

  13.   

    你那段代码是服务端的吧
    不知道你里面的函数是如何具体实现
    但我想其实你的程序里面也没有太多的判断机制,我之前的程序就出现这样的错误
    比如我帖子里举的例子,客户端网线被拔,这时候TCP还是established状态,网线连上也能继续使用,但我想你的程序里这个是检测不错来的  对吧?这个TCP不能及时释放,就占用了服务器的资源
    这也是我要做TCP长链接检测的原因
      

  14.   

    我晕  你居然在客户端直接一个
    while(true)
    {
       thread.sleep(1)
    }
    单核的电脑还不给跑死啊!别告诉我这是投入运行的产品...
    你的
    if (NetDisconnect != null) NetDisconnect(this, null);
    这个NetDisconnect和NetDisconnect(this, null)是如何保证网络状态的?说说思路吧
    粗略看了下26楼美女的代码,应该是比较可行的
    我忘了KeepAlive包在应用层也是可以收到的
    再仔细研究下
      

  15.   

    我多研究下有啥潜在问题MM你也太搞了private void SetXinTiao(Socket tmpsock)
    我英语也很烂
      

  16.   

    讨论了这么多,怎么就不考虑下不保持长连接的做法呢?服务器资源毕竟宝贵,如果说长时间不使用的连接也保持的话,显然是不值得的,你必须明白,保持一个长连接的开销比创建一个新连接更大,所以一般设置一个超时时间,当在规定时间内没有动作就自动断开连接,FTP就有这种设置。
      

  17.   

    socket了解不多,
    关注学习中....
      

  18.   

            private void ReceiveCallBack(IAsyncResult ar)
            {
                try
                {
                    lock (this)
                    {
                        int recvCount = _client.GetStream().EndRead(ar);//这种情况就是断开了连接
                        if (recvCount < 1)
                        {
                            if (OnSocketError != null)
                            {
                                _client.Close();
                                OnSocketError(this, new EventArgs());
                            }
                        }
      

  19.   

    这段代码刚好有我关心的很多问题首先说明一下,在我的客户端里面,是采用同步发送的通信方式,
    一个线程一值
    while()


    处理消息因为通信在我这是非常重要的,可以说是主线程了,所有不可能像26楼MM那样改成异步通信的形式,如果仅仅加一个异步接受返回的KeepAlive包,就会和我主通信线程里的消息冲突我对TCP的理解,不知道是不是有偏差,说错大家帮我更正。
    就像45楼的代码,是在异步等待TCP的包。如果收到一个包,但里面没内容,就判断TCP断开,是这么理解这段程序吧?
    但TCP其实有很多时候是在自己发送一下TCP层的,在应用层看来没内容的包,比如我说的这个KeepAlive包。像45L代码,会不会接受到一个试探网络的KeepAlive包就判断是网络出故障,主动断开呢?一般的系统,如果没有设置KeepAlive值,默认是2个小时的,就是说2个小时Tcp没有流量,就发一个这种包,red_angelx兄,你们的TCP连接能保持2个小时以上没流量又不断开吗?
    而且有时候网络出故障,你不一定能收到这种没内容的包,这时候不就判断不出来了?
    要是45L的代码没有任何问题,就证明我对TCP的理解有重大错误...恳请red_angelx指证
      

  20.   

    长连接是必须的,关于心跳这么一回事好像26楼的MM(也可能是阿姨)就写了C#比较常用的写法,如果真的要再改进的话,可以参考C++的通迅了,引用C++的底层通信包,仿C++写通迅程序,那个优点绝对拼得过QQ。而且QQ在通迅这一块也并非完美。只是路过。。
      

  21.   

    你说的是双工通讯的情况,显然这不是很多情况下都需要的,是个特例,不过这种特例也只需要一个长连接即可,并发的其它连接都应该使用短连接。另外你可以学学人家腾讯公司,他们就连一个长连接都没有就可以得到服务器的通知,你知道这是什么原因吗?因为UDP根本不需要连接,客户端定时发送UDP包到服务器端确认是否有消息需要接收,这个确认包的开销远比TCP小的多,显然这种确认包是否丢失都无所谓,你可以把频率放高,一旦有消息需要接收,也可以切换到TCP连接去服务器读取消息。最后,如果条件允许,开始用WCF来实现远程通讯吧,毕竟WCF的功能比Socket强大得多。
      

  22.   

    to 青龙白虎兄
    哎  你说的这些  是很美好  但我没权利这么做
    服务器端的代码不是我写的,是一个牛人用java开发的,整个框架都是他来定,他那边搞好了我来配合
    一开始我的想法也是你说的那样,隔段时间询问
    但他说这样的开销和保持长连接没多大区别,现在好,保持连接都全放我这做了
    人家近10年的工作经验,目前就职国内最顶尖的网络公司,我能说什么呢
    不过一折腾,倒是学到很多东西
    我在47楼提的问题,帮忙看看
      

  23.   

    哥哥诶  不是我偷懒   是条件不允许我这么做
    服务器那边不改  广我客户端发应用层的包有啥用啊
    我一开始改的时候  就是发应用层的心跳包   程序都写好了
    但服务器收到不是原来通讯协议的包都把TCP断掉而且让底层去做,确实有很多优势,服务器那边也可以马上检测到网络异常,我在帖子里也说明了。如果我这方法行的通,应该是目前最好的解决方法了。到时候总结出来给大家看看
    吃饭去,各位高手帮忙瞧瞧,不要吝啬留下您的意见
      

  24.   

    学习,但我还是有一些其他的问题。
    比如说用SOCKET传输数据的时候,如何考虑边界沾包等问题?尤其是在传输一些STRUCT,表,DATASET这些数据的时候,该如何处理这些问题?如果是SREING的话还好说呢,呵呵。
      

  25.   

    “如果仅仅加一个异步接受返回的KeepAlive包,就会和我主通信线程里的消息冲突”
    看了你的回答,我只能说楼主对KeepAlive并没有完全理解,KeepALive包并不会到应用层,它只是在监测到网络断线时向应用层抛出异常,那它会把异常抛到哪里呢?就是Receive、BeginReceive、EndReceive这些接收数据的地方。请问楼主是采用的阻塞模式吗?用多线程加上非阻塞模式也就是异步了,看来楼主对同步和异步也没搞清楚。这个贴早该结束了,从上面所有人的回答来看楼主早该找到答案了,还没找到答案是你自己有问题。说话狠了点,还请楼主多多包涵
      

  26.   

    是的是的  我的程序只用过一个C#提供的异步,是用本地端口,跨进程通信,基本没可能出现异常,所以异步的怎么抛出异常基本不了解
    我的客户端是自己做的异步模式,也就是你说的多线程非阻塞,自己设计消息队列,每次人家说到系统提供的异步函数我就不了解...
    如果你说的正确,那么可推出,其实KeepAlive也是底层利用超时抛出异常,只不过我们上层程序不需要管而已
    而自己写的异步(多线程非阻塞),其实是管不到底层这些东西的,这也正是我的问题所在!
    难怪大家不理解我说的东西,因为我开始就不是常规做法,也不应该这么做;如果现在又回归C#封装好的异步,不伦不类了
    哎,进退两难啊
    我还是用自己的笨方法检测吧
    MM你的方法是对的,但不适用我这,以后再写的话,我考虑用你的模式
    现在才明白sp大大说的,自己写异步是多么恶心的事
      

  27.   

    我们的通信是用XML标准格式的
    以<?xml version="1.0" encoding="utf-8" ?>开头
    协议定得很严格,服务器收到就可以自己判断到底数据接收完全没
    中间要发送什么东西,都作为XML中一项发送,所有收没收完全,有没有粘包服务器都可以自己判断的
    而我用socket传,只需拼接成规定好的协议字符串,再转换成2进制传输   就OK了
    你是想说论坛里的另一个帖子遇到这个问题吧?其实下面的回帖已经有人说了,只要协议定得好,很难出错的
      

  28.   

    将所有的数据序列化成XML吗?
      

  29.   


    对我的问题本身来说,我已经找到答案了,谢谢各位!
    比答案本身更重要的是对这一问题的理解,这个是网上代码不能给予我的。17L的代码貌似就是一种新的思想,但其中的原理我不理解这句“代码只是判断是否因为网络因素断开连接的”,判断的原理是什么呢?也是MM所说的底层抛出的异常,用就行?
    对这些都完全理解了,我才能进行判断,下手改自己的程序to72L
    建议你好好研究下标准话的XML,为什么这么多人用,是有一定原因的。很多标准的东西,我们在使用,却不知道它的妙处。也是你提出的问题,让我感受到它的好处
    举个例子
    你要传输email = [email protected]
    <?xml version="1.0" encoding="utf-8" ?>
    <protocol>
       <command>XXXX</command>
           <params><param name="EMAIL" value="[email protected]" />
           </params>
    </protocol>
    你想,这样容易粘包吗?就算粘包了,服务器也能自己判断吧
      

  30.   

    异步的通信还真没怎么搞过,我做同步tcp通信的时候,在服务端判断客户端是否退出连接:_SendBuffer = ReadNowData(_tmpDevice.Address.Trim());//构造读取数据的包
                                                        _tmpdtuclient.DTUNetworkStream.Write(_SendBuffer, 0, _SendBuffer.Length);
                                                        SendCount++;
                                                        SetMSG("发送:" + NomalFunction.ByteArrayToHexString(_SendBuffer, _SendBuffer.Length));
                                                        _readBytesCount = _tmpdtuclient.DTUNetworkStream.Read(_receiveBuffer, 0, _receiveBuffer.Length);//如果客户端断开了连接,则read方法会立即返回0
                                                        if (_readBytesCount == 0)
                                                        {
                                                            //客户端退出连接。
                                                            SetMSG("客户端退出连接!客户端信息:" + _tmpdtuclient.DTUTCPClient.Client.RemoteEndPoint.ToString());
                                                            Thread.CurrentThread.Abort();
                                                        }
      

  31.   

    代码格式没弄好,重新贴:while(true)
    {
    _SendBuffer = ReadNowData(_tmpDevice.Address.Trim());//构造读取数据的包_tmpdtuclient.DTUNetworkStream.Write(_SendBuffer, 0, _SendBuffer.Length);
    SendCount++;
    SetMSG("发送:" + NomalFunction.ByteArrayToHexString(_SendBuffer, _SendBuffer.Length));
     _readBytesCount = _tmpdtuclient.DTUNetworkStream.Read(_receiveBuffer, 0, _receiveBuffer.Length);//如果客户端断开了连接,则read方法会立即返回0
    if (_readBytesCount == 0)
    {
    //客户端退出连接。
    SetMSG("客户端退出连接!客户端信息:" + _tmpdtuclient.DTUTCPClient.Client.RemoteEndPoint.ToString());
    Thread.CurrentThread.Abort();
    }
    }
      

  32.   

    楼上的,这句
    if (_readBytesCount == 0)
    判断网络是否异常
    为什么会_readBytesCount == 0?是通过接受没内容的包吗?
    那岂不是陷入我在47楼提出的那种境况?
      

  33.   

    NetworkStream.Read方法会阻止(至少在同步tcp连接中是这样)直到有数据读取或网络异常(连接中断)。
    你看我的从网络流中读取数据的那句话后面的注释。我这样判断同步连接模式下客户端断开连接还没出过问题。
    只是在服务端关闭连接时,用NetworkStream.Close()时有点儿小麻烦:
    MSDN里是这样介绍的:关闭当前流并释放与之关联的所有资源(如套接字和文件句柄)。 
    但是貌似我调用关闭方法后一直到线程退出,所花的时间比较长。至今不知道为什么
    我是这样实现服务端关闭连接的:while(true)
    {
    try
    {
    //some codes,receive client's datas and send datas to client and so on.
    NetworkStream.Close();
    Thread.CurrentThread.Abort();
    }
     catch (ThreadAbortException thae)
                    {  
                        break;
                    }
    }
    //end of the thread's target function.
    是不是我的退出线程的方法使用有问题?
      

  34.   

    好像你没有回答我的问题啊!
    我推测,网络断开,你能知道,是因为对方给你发了个TCP的FIN包,这个包是没内容的
    要是客户端网线把了一会,你能检测出来吗?2个小时以上,没任何消息的TCP,还能保持吗?
    到底为什么会有if (_readBytesCount == 0) ?
      

  35.   


    好像你没有回答我的问题啊!
    ==============================
    呃,我是看这个地方热闹,凑个热闹让大家看看我的做法合理不。我推测,网络断开,你能知道,是因为对方给你发了个TCP的FIN包,这个包是没内容的
    要是客户端网线把了一会,你能检测出来吗?2个小时以上,没任何消息的TCP,还能保持吗?
    ====================================================================
    这个我试了下,如果客户端把无线网卡关闭,服务端用这种方法是监测不到客户端的断开动作。到底为什么会有if (_readBytesCount == 0) ?
    ===============================================
    _readBytesCount是NetworkSream.Read方法返回值。如果客户端close。则此方法立即返回0。
      

  36.   

    对的,你这样的测试,就帮我解决了很多疑问,也证明了我的一些设想。
    如果我们仅仅靠if (_readBytesCount == 0) 
    来判断,是不可靠的。
    因为它的判断原理,和我上面提到的一样,是靠接受一个TCP协议层的包来做的,这个包的内容,对我们应用层来说,内容是空的,既  _readBytesCount == 0
    如果对方发过来的并不是请求断开的FIN包,而是试探网络的KeepAlive包,你的程序应该也是if(_readBytesCount == 0) 
    而导致断开连接
    默认的TCP   KeepAlive值是2个小时,既2个小时TCP没流量,操作系统会自己发一个KeepAlive包,我推测你的TCP是不能保持2个小时没流量的长连接的。或者说只要对方发了一个TCP层的包过来,不管要求你做什么,你都会断开它...
    而且,网络上出现异常的情况很复杂,很多时候收不到这种TCP层的没内容的包,网络就断了,你的程序应该也是检测不到的
    以上很多是我的推测,希望高手给出肯定答案
      

  37.   

    NetworkSream.Read 就是我贴代码的tcp版 我贴的是UDP的 原理一样