TcpClient例子(2)更robust的程序

目的:在上一个例子的基础上,我们的程序需要更加的robust。需求是一个在客户端的长连接,在连接,发送和接受发生各种非正常情况下,

程序需要自动重连,自动恢复运行。

怎样判定socket异常需要重连。

 连接:在回调函数的EndConnect时需要try catch

发送:在BeginSend直接try catch,也不需要回调了

接受:在BeginReceive的时候try catch。还有一种情况需要特别注意,没有异常出现,但是服务器close socket了,此时判断EndReceive的返回值,为0说明异常。

 下面的代码在连接,发送,接受都可以检测socket不正常的情况

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace meNet
{
    /// <summary>
    ///假定一切工作正常
    ///连接后发送一次消息,然后不停接受消息并且打印
    /// </summary>
    class Program
    {
        byte[] recvData = new byte[1024 * 10];
        TcpClient client = new TcpClient();
        NetworkStream stream = null;
        bool isConnect = false;
        void doWork()
        {
            string str = "legionis here
";
            byte[] toSent = Encoding.ASCII.GetBytes(str);
            Console.WriteLine("preparing to connect in main thread  " + Thread.CurrentThread.ManagedThreadId);
            client.BeginConnect("127.0.0.1", 8888, ConnectCallBack, client);
            while (true)
            {
                try
                {
                    if (isConnect)
                        client.Client.BeginSend(toSent, 0, toSent.Length, SocketFlags.None, SendCallBack, null);
                    Console.WriteLine("sent done
");
                    Thread.Sleep(2000);
                }

                catch (Exception ex)
                {
                    Console.WriteLine("send error
");
                    isConnect = false;
                }

            }
        }
        public void SendCallBack(IAsyncResult result)
        {
            try
            {
                client.Client.EndSend(result);
                Console.WriteLine("yes sent");
            }
            catch (Exception ex)
            {

            }
        }
        static void Main(string[] args)
        {
            Program p = new Program();
            p.doWork();
            Console.Read();
        }
        private void ConnectCallBack(IAsyncResult result)
        {
            Console.WriteLine("well, i am in the connect thread..." + Thread.CurrentThread.ManagedThreadId);
            TcpClient client = result.AsyncState as TcpClient;
            try
            {
                client.EndConnect(result);
            }
            catch (Exception ex)
            {
                isConnect = false;
                Console.WriteLine("well, seems an error occured...");
                Console.WriteLine("which is " + ex.ToString());
                return;
            }
            isConnect = true;
            Console.WriteLine("Hooray, it worked.");
            client.Client.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, RecvCallBack, client);

        }
        public void RecvCallBack(IAsyncResult result)
        {
            Console.WriteLine("here in recv callback thread.." + Thread.CurrentThread.ManagedThreadId);
            int count = -1;
            try
            {
                count = client.Client.EndReceive(result);
            }
            catch (Exception ex)
            {
                isConnect = false;
                Console.WriteLine("远程计算机关闭");
                return;
            }
            if (count <= 0)
            {
                Console.WriteLine("cannot recv any data");
                isConnect = false;
                return;
            }
            //an recv is done,display it and continue..
            string msg = Encoding.ASCII.GetString(recvData, 0, count);
            Console.WriteLine("your recv this time is:");
            Console.WriteLine(msg);
            try {
                client.Client.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, RecvCallBack, client);
 
            }
            catch(Exception ex)
            {
                isConnect = false;
            }
            
        }
    }
}
View Code

下面的代码实现使用多线程,定时检测,发现异常,不停的重新连接

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace meNet
{
    /// <summary>
    ///可以处理各种异常情况下的重连
    ///连接后不停发送消息,然后不停接受消息并且打印
    /// </summary>
    class Program
    {
        byte[] recvData = new byte[1024 * 10];
        TcpClient client = new TcpClient();
        NetworkStream stream = null;
        bool isConnect = false;
        AutoResetEvent do_connect=new AutoResetEvent(false);
        public void reconnectThread(){
            while (true)
            {
                do_connect.WaitOne();
                try
                {
                    if (!isConnect)
                    {
                        client.Close();
                        client = new TcpClient();
                        client.Connect("127.0.0.1", 8888);
                    }
                    
                }
                catch (Exception ex)
                {
                    Console.WriteLine("well, reconnect failed this time ,try again in five secs");
                    Thread.Sleep(5000);
                    do_connect.Set();
                    continue;
                }
                Console.WriteLine("connection re-established...
");
                isConnect = true;
                try
                {
                    client.Client.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, RecvCallBack, client);

                }
                catch (Exception ex)
                {
                    rees();
                }
            }
        }
        void rees()
        {
            Console.WriteLine("initiating an reopening...");

            isConnect = false;
            do_connect.Set();
        }
        void doWork()
        {
            Thread t = new Thread(reconnectThread);
            t.Start();
            string str = "legionis here
";
            byte[] toSent = Encoding.ASCII.GetBytes(str);
            Console.WriteLine("preparing to connect in main thread  " + Thread.CurrentThread.ManagedThreadId);
            client.BeginConnect("127.0.0.1", 8888, ConnectCallBack, client);
            while (true)
            {
                try
                {
                    if (isConnect)
                    {
                        client.Client.BeginSend(toSent, 0, toSent.Length, SocketFlags.None, null, null);
                        Console.WriteLine("sent done
");
                    }
                    Thread.Sleep(2000);
                }

                catch (Exception ex)
                {
                    Console.WriteLine("send error
");
                    rees();
                }

            }
        }
        public void SendCallBack(IAsyncResult result)
        {
            try
            {
                client.Client.EndSend(result);
                Console.WriteLine("yes sent");
            }
            catch (Exception ex)
            {
                rees();
            }
        }
        static void Main(string[] args)
        {
            Program p = new Program();
            p.doWork();
            Console.Read();
        }
        private void ConnectCallBack(IAsyncResult result)
        {
            Console.WriteLine("well, i am in the connect thread..." + Thread.CurrentThread.ManagedThreadId);
            TcpClient client = result.AsyncState as TcpClient;
            try
            {
                client.EndConnect(result);
            }
            catch (Exception ex)
            {
                isConnect = false;
                Console.WriteLine("well, seems an error occured...");
                Console.WriteLine("which is " + ex.ToString());
                rees();
                return;
            }
            isConnect = true;
            Console.WriteLine("Hooray, it worked.");
            client.Client.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, RecvCallBack, client);

        }
        public void RecvCallBack(IAsyncResult result)
        {
            Console.WriteLine("here in recv callback thread.." + Thread.CurrentThread.ManagedThreadId);
            int count = -1;
            try
            {
                count = client.Client.EndReceive(result);
            }
            catch (Exception ex)
            {
                isConnect = false;
                Console.WriteLine("远程计算机关闭");
                rees();
                return;
            }
            if (count <= 0)
            {
                Console.WriteLine("cannot recv any data");
                rees();
                return;
            }
            //an recv is done,display it and continue..
            string msg = Encoding.ASCII.GetString(recvData, 0, count);
            Console.WriteLine("your recv this time is:");
            Console.WriteLine(msg);
            try {
                client.Client.BeginReceive(recvData, 0, recvData.Length, SocketFlags.None, RecvCallBack, client);
 
            }
            catch(Exception ex)
            {
                rees();
            }
            
        }
    }
}
View Code

技术关键点:

重连线程框架如下

while(true)

{

  do_connect.WaitOne();

  尝试新建TcpClient,并且连接

}

其他代码发现socket不正常,就会使能AutoResetEvent,WaitOne不阻塞了,开始运行。

更加robust的封装之使用log4net

 考虑到程序以后使用winform,为了更好的调试,这里介绍log4net的使用

1.下载

到https://logging.apache.org/log4net/download_log4net.cgi

或者直接在此下载dll文件,项目中添加引用log4net.dll

2.在项目的Properties->AssemblyInfo.cs文件中,添加

[assembly: log4net.Config.XmlConfigurator(ConfigFileExtension="config",Watch=true)]

3.右键项目,添加新建项->应用程序配置文件App.config(当然,如果已经有,无需添加)

4.App.config 文件添加如下代码,这里我们的目的是将日志输出磁盘中的一个txt文件中。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  <log4net>
    <root>
      <level value="ALL" />
      <appender-ref ref="SysAppender" />
    </root>
    <logger name="WebLogger">
      <level value="DEBUG" />
    </logger>
    <appender name="SysAppender" type="log4net.Appender.RollingFileAppender,log4net">
      <!--<param name="File" value="App_Data/" />-->
      <param name="File" value="C:\TestWeb\Debug\Error\" />
      <param name="AppendToFile" value="true" />
      <param name="RollingStyle" value="Date" />
      <param name="DatePattern" value="&quot;Logs_&quot;yyyyMMdd&quot;.txt&quot;" />
      <param name="StaticLogFileName" value="false" />
      <layout type="log4net.Layout.PatternLayout,log4net">
        <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
        <param name="Header" value=" ----------------------header--------------------------&#xD;&#xA;" />
        <param name="Footer" value=" ----------------------footer--------------------------&#xD;&#xA;" />
      </layout>
    </appender>
    <appender name="consoleApp" type="log4net.Appender.ConsoleAppender,log4net">
      <layout type="log4net.Layout.PatternLayout,log4net">
        <param name="ConversionPattern" value="%d [%t] %-5p %c - %m%n" />
      </layout>
    </appender>
  </log4net>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
</configuration>
View Code

注意这一行

<param name="File" value="C:\TestWeb\Debug\Error\" />,日志文件会存在对应盘符目录下,

我们改成<param name="File" value="log\" />让日志存储在当前exe目录的log子目录下

5.代码中使用非常简单

using log4net;

程序开始创建一个logger

ILog logInfo = LogManager.GetLogger("loginfo");

然后在你需要的地方使用

loginfo.Info("blahblah");

 更多关于配置文件的说明:

https://blog.csdn.net/zhoufoxcn/article/details/2220533

https://www.cnblogs.com/xuxuzhaozhao/p/6640623.html

原文地址:https://www.cnblogs.com/legion/p/9115457.html