2010年:Socket实践问题求助解决一贴通

由于工作需要,最近需要用到Socket,在学习的过程中,深感痛苦。在网上翻过来翻过去,

也没太大的帮助,缺少一个完整的知识体系,网上的例程和文章都是东一点西一点,零零散散,

而大家的方法各不相同,非但不具备太高的价值,更让人觉得一头雾水,迷失方向。

遇到许多问题,没少在CSDN上求助发贴,分散了不少,真正问题一个没解决,

发现CSDN没落了!真的,懂的高人一般高高在上,都很忙都很隐世,

而热心的回贴的,也只是帮顶,路过...

我虽然很菜,也还有很多问题没有解决,但我希望写下自己知道的,和解决的问题,

供还没有接触这方面的同学们参考,少走点弯路...

要用到Socket,需要引入两个命名空间:

using System.Net;
using System.Net.Sockets;    

MSDN上说:

如果要编写相对简单的应用程序,而且不需要最高的性能,则可以考虑使用 TcpClientTcpListener 和 UdpClient。这些类为 Socket 通信提供了更简单、对用户更友好的接口。

我这里不讲TcpClient等,都是直接讲的Socket,至于为什么,是因为我也没用过那些。

使用Socket,一般的流程为:

1.建立Socket

2.和目标服务器建立连接

3.发送或接收数据

4.关闭Socket

 基本代码如下:

 1 Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
 2 
 3 IPAddress ip = IPAddress.Parse("192.168.1.240");
 4 int port = 11000;
 5 //创建远程服务器的端点
 6 IPEndPoint remoteEP = new IPEndPoint(ip, port);
 7 //连接远程服务器
 8 socket.Connect(remoteEP);
 9 
10 //连接无误的话,就可以发送数据了,上面的连接操作没有处理异常,后面会讲到
11 string msg = "Hello,This is Client,Can you Hear me?";
12 
13 byte[] bytes_Send = System.Text.Encoding.ASCII.GetBytes(msg);
14 //向服务端发送数据
15 socket.Send(bytes_Send);
16 
17 //关闭Sokcet
18 socket.Shutdown(SocketShutdown.Both);
19 socket.Close();

至此,基本上完成了使用Socket通信的基本功能了。但Socket远不止这么简单,由于Socket通信,基本上属于I/O操作,可能会阻塞线程,而一旦和线程打交道,事情就会变得复杂和不可理喻。

Socket通信方式,从网上查的资料来看,基本可分为同步和异步两种。同步的就是按照顺序一步一步的走,也就是说前一步如果没有完成,就不会执行到下一步,举个例子:

同步的工作方式好比大家去灾区求灾,把物资从车上搬下来,由于工具的缺乏,大家只好一字排开,物资从第一人手中传给第二个人,再由第二个人传给第三个人...依次往下传,如果第一个人不把物资往下传,那么第二个人只好等待,一直等到第一个人的物资...

异步的方式,接上面的例子,大家觉得一字排开的工作方式太慢了,有男有女,有力气大的也有力气小,如果大家工作不协调,效率太差了,于是大家就换成另一种方式,每个人都各自从车上搬物资下来,送到目的存放点,这样,力气大的手脚麻利的就能更快的完成更多的工作,而力气小的速度慢的女同志的也不会耽误到速度快的男同志的工作了。

从另一个方面来说,就好像公路,同步的公路是单行道,车量就只好排队通过,万一哪辆车在中间抛锚了,后面的就不用走了..
而异步的公路是深南大道(在深圳)有N个车道,什么公交车道,货车道......可以同时跑N辆车...

可能举例说的不太明确,但总体上就是这个样子的,本身我自己对异步和同步的理解也仅限于此了。

Socket类的方法中,提供了不少的异步方法,按照MSDN上的说法:

Socket 类对异步方法遵循 .NET Framework 命名模式。例如,同步的 Receive 方法对应于异步的 BeginReceive 和 EndReceive 方法。

一看就知道,其他是异步方法基本是说:BeginXXX和EndXXX,貌似还要求有BeginXXX就一定要有EndXXX,是不是一定要有这规则,我也不是太清楚,等高手...

连接服务器时(Connect)的异步:

我们上面的代码中,socket.Connect(remoteEP)方法就是同步的方法,会阻塞线程,造成界面假死(如果连接服务器正常的话,也许就半秒钟,如果连接异常的话,少则几秒,多则半分钟,下面会说到Connect常见的异常。),为了解决这个问题,就需要用到异步的工作方式。

socket.Connect()对应的异步方法为BeginConnect 和 EndConnect :

public IAsyncResult BeginConnect(EndPoint remoteEP,AsyncCallback callback,Object state)
第一个参数指定服务端的端点,
第二个参数指定调用该方法后的回调方法,至于该回调方法何时被调用,我也不是太清楚(可能是三次握手的第二次),我的理解是BeginConnect方法在底层发送一个数据包出去,如果服务端响应回发了一个数据包,那么我们的程序接收回发的数据包,调用第二个参数指定的callback回调函数。MSDN上对该方法的描述如下:

您可以创建一个实现 AsyncCallback 委托的回调方法并将它的名称传递给 BeginConnect 方法。至少必须通过 state 参数将 Socket 传递给 BeginConnect。如果您的回调需要更多信息,则可以创建一个小型类来保存 Socket 和其他必需的信息。通过 state 参数将此类的一个实例传递给 BeginConnect 方法。

回调方法应调用 EndConnect 方法。当应用程序调用 BeginConnect 时,系统将使用单独的线程执行指定的回调方法,并在 EndConnect 上一直阻止到 Socket 成功连接或引发异常为止。如果想要在调用 BeginConnect 方法后使原始线程阻止,请使用 WaitOne。当需要原始线程继续执行时,请在回调方法中调用 T:System.Threading.ManualResetEvent 的 Set 方法。有关编写回调方法的其他信息,请参见 Callback 示例。

也就是说,系统会自动开辟一个新的线程来执行callback函数,而原线程则继续往下执行。

现在,我们来修改上面的代码,达到连接时的异步。

 1 //socket.Connect(remoteEP); //此行注释掉,改成下面的异步方法
 2 socket.BeginConnect(remoteEP, new AsyncCallback(ConnectCallBack), socket);
 3 
 4 //同时,我们要写一个回调的方法ConnectCallBack
 5 public void ConnectCallBack(IAsyncResult ar)
 6 {
 7   try
 8   {
 9      Socket client = (Socket)ar.AsyncState;
10      //有BeginXXX就得有EndXXX
11      client.EndConnect(ar);
12   }
13   catch (Exception e)
14   {
15      MessageBox.Show(e.Message);
16   }
17 }

有些不明白的是,我们将连接时创建的BeginConnect方法将socket包装传入到回调函数,可能是引用传值,所以我们在ConncetCallBack里面创建Socket client时,client是指向原socket的引用(哎,早知就换个变量名了,这里指前面创建的那个Socket socket变量),所以这里连接正常的话,我们再用socket.Send()方法发送数据时,不会产生未连接的错误!所以这里其实从头到尾只有一个Socket变量被创建了,不知道这样理解是不是正确的,高手看到的话,不防指点一二。

未完,下班了...待续!

原文地址:https://www.cnblogs.com/anduinlothar/p/14436968.html