项目使用Socket与设备通信,可能同时会有成百上千个连接传送数据,当然不可能针对每个连接都要单独建立一个线程在后台接收数据,很明显的方式就是异步。
1. 怎么理解异步
这里,我举个不太恰当的例子,打电话和写信。打电话的时候,理想情况下,我们希望我问个问题,对方立马就能答复的,如果对方憋了半天,啥话也不说,我也只能一直等着,因为我接下来要说的话,是要根据他的回复来说的,这样在这期间什么事也干不了,对于程序来说,就叫同步;而写信不是这样,写信是即使我只问一个问题,我也可以在发完信以后,做其他事情,比如吃个饭啊,睡个觉什么的,不用一直守在信箱旁边,等着那边的回信,这个就可以称为异步
所以引申到编程中来,可以知道异步实际上就是程序发送一个指令以后,并不是阻塞等待回复,而是继续运行,这样减少了资源浪费,也让程序运行更加流畅。
2. Socket异步通信原理
Socket异步通信实际上是利用了Windows系统的消息机制,当发送异步请求的时候,Socket向操作系统注册了一个回调函数,告诉操作系统,这个回调是用来接收Socket那边发来的数据;当有回复返回的时候,操作系统通过调用注册的回调函数,通知应用程序有数据返回,从而达到处理回复的目的。
3. 了解.net异步编程的规则
在实现异步通信前,我们来看看.net是如何实现异步的,以下摘自网络(自己写打字太多。。)
.NET Framework允许异步调用任何方法,定义与需要调用的方法具有相同签名的委托,CLR将自动为该委托定义添加适当签名的BeginInvoke虚方法和EndInvoke虚方法和Invoke方法。
我们先来了解这2个方法和一个委托和一个接口:
(1)
BeginInvoke方法用于启动异步调用
它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数,将 AsyncCallback 和 AsyncState(可通过 IAsyncResult 接口的AsyncState 属性获得)。作为最后两个参数,如没有可以为空。
BeginInvoke立即返回,不等待异步调用完成。
BeginInvoke返回IasyncResult,可用于监视调用进度。
结果对象IAsyncResult是从开始操作返回的,并且可用于获取有关异步开始操作是否已完成的状态。
结果对象被传递到结束操作,该操作返回调用的最终返回值。
在开始操作中可以提供可选的回调。如果提供回调,在调用结束后,将调用该回调;并且回调中的代码可以调用结束操作。
(2)
EndInvoke方法用于检索异步调用结果。
在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke。
如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。
EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。
要注意的是,始终在异步调用完成后调用EndInvoke
(3)
AsyncCallback委托用于指定在开始操作完成后应被调用的方法。
AsyncCallback委托被作为开始操作上的第二个到最后一个参数传递。
代码原型如下:
[Serializable]
public delegate void AsyncCallback(IAsyncResult ar);
(4)
IAsyncResult接口
它表示异步操作的状态,该接口定义了4个公用属性。
4. Socket异步通信的实现
.net已经为我们建好了Socket的异步通信模型,并提供了相应的函数接口,我们只要直接拿来用就行了。接口主要一下三种:BeginAccept, EndAccept; BeginReceive, EndReceive; BeginSend, EndSend。使用这三种接口就能实现我们想要的异步通信方式。
新建一个控制台程序,在服务端,代码如下: (注:为了描述方便,所有代码只写关键步骤,没有考虑异常和其他条件处理)
建立服务端Socket,并设置好参数,绑定ip和端口后,使用BeginAccept异步监听连接,其中定义了新连接回调函数Accept,并设置参数“new socket connect”
static Socket socket; static void Main(string[] args) { //新建Socket,并开始监听连接 socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234)); socket.Listen(100); socket.BeginAccept(new AsyncCallback(Accept), "new socket connect"); Console.ReadKey(); }
当有新的连接请求时,触发回调,建立完成后,启动异步接收
static void Accept(IAsyncResult ar) { Console.WriteLine(ar.AsyncState.ToString()); //结束监听 Socket _socket = socket.EndAccept(ar); //投递接收请求 Mark mark = new Mark(_socket); _socket.BeginReceive(mark.buffer, 0, mark.buffer.Length, SocketFlags.None, new AsyncCallback(Receive), mark) }
其中,定义类Mark,用于保存新连接和要接收的数据。
class Mark { public Socket socket; public byte[] buffer; public Mark(Socket socket) { this.socket = socket; buffer = new byte[1024]; } }
接收到数据,并打印,同时异步发送一条信息
static void Receive(IAsyncResult ar) { Mark mark = (Mark)ar.AsyncState; //结束接收 int length = mark.socket.EndReceive(ar); if (length > 0) { Console.WriteLine("Receive: " + Encoding.Default.GetString(mark.buffer, 0, length)); byte[] senddata = Encoding.Default.GetBytes("hello, this is server."); Console.WriteLine("Send: hello, this is server."); //投递发送请求 mark.socket.BeginSend(senddata, 0, senddata.Length, SocketFlags.None, new AsyncCallback(Send), mark); } }
最后,结束发送
static void Send(IAsyncResult ar) { Mark mark = (Mark)ar.AsyncState; //结束发送 mark.socket.EndSend(ar); }
这样,服务端的简单异步处理完成,客户端类似,不做赘述。
class Program { static Socket socket; static void Main(string[] args) { socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Connect(IPAddress.Parse("127.0.0.1"), 1234); byte[] senddata = Encoding.Default.GetBytes("Hello, this is client."); Console.WriteLine("Send: Hello, this is client."); socket.BeginSend(senddata, 0, senddata.Length, SocketFlags.None, new AsyncCallback(Send), null); Console.ReadKey(); } static void Send(IAsyncResult ar) { socket.EndSend(ar); byte[] buffer = new byte[1024]; socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Receive), buffer); } static void Receive(IAsyncResult ar) { int length = socket.EndReceive(ar); if (length > 0) { byte[] buffer = (byte[])ar.AsyncState; Console.WriteLine("Receive: " + Encoding.Default.GetString(buffer, 0, length)); } } }
启动服务端,再启动客户端,结果如下:
client:
server: