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\n"; 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\n"); Thread.Sleep(2000); } catch (Exception ex) { Console.WriteLine("send error\n"); 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; } } } }
下面的代码实现使用多线程,定时检测,发现异常,不停的重新连接。
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...\n"); 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\n"; 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\n"); } Thread.Sleep(2000); } catch (Exception ex) { Console.WriteLine("send error\n"); 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(); } } } }
技术关键点:
重连线程框架如下
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=""Logs_"yyyyMMdd".txt"" /> <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--------------------------
" /> <param name="Footer" value=" ----------------------footer--------------------------
" /> </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>
注意这一行
<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