.NET Framework中的Socket类的BeginReceive和EndReceive成员方法详解


请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

类所属的空间: System.Net.Sockets
类所属于的程序集: System.dll, netstandard.dll, System.Net.Sockets.dll, System.Net.dll


使用BeginReceive和EndReceive的示例代码

using System;  
using System.Net;  
using System.Net.Sockets;  
using System.Threading;  
using System.Text;  

// State object for receiving data from remote device.  
public class StateObject {  
    public Socket workSocket = null;              // Socket连接的对象 
    public const int BufferSize = 256;            // 客户端接受socket数据的缓冲区字节数大小 
    public byte[] buffer = new byte[BufferSize];  // 客户端接受socket数据的缓冲区
    public StringBuilder sb = new StringBuilder();// 客户端接受数据字符串的容器
}  

public class AsynchronousClient {  
    private const int port = 11000;   // 端口号

    // ManualResetEvent类详见 http://www.cnblogs.com/li-peng/p/3291306.html
    // 当初始化为true时,为终止状态
    // 当初始化为false时,为非终止状态
    // 终止状态时调用成员方法WaitOne()允许线程访问下边的语句
    // 非终止状态时调用成员方法WaitOne()阻塞线程,不允许线程访问下边的语句
    private static ManualResetEvent connectDone = new ManualResetEvent(false);  
    private static ManualResetEvent sendDone = new ManualResetEvent(false);
    private static ManualResetEvent receiveDone = new ManualResetEvent(false);
    private static String response = String.Empty; // 从服务端处收到的字符串 

    private static void StartClient() {  
        try {  
            // 利用DNS解析域名,获得目标服务器的IP
            IPHostEntry ipHostInfo = Dns.GetHostEntry("host.contoso.com");  
            IPAddress ipAddress = ipHostInfo.AddressList[0];  
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);  

            // 创建连接用的socket,使用TCP,流式数据  
            Socket client = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);  

            // 开始连接服务器,注意这里的第三个参数,当传递client进去的时候,传给回调函数ConnectCallback
            // 中IAsyncResult类参数ar的AsyncState,就是这个client
            client.BeginConnect( remoteEP, new AsyncCallback(ConnectCallback), client);

            // 因为一开始connectDone处于非终止状态,所以这里执行了WaitOne方法之后,会阻塞住当前的线程
            // 直到在ConnectCallback函数中connectDone执行了Set()方法为止
            connectDone.WaitOne();  

            // 向服务端发送消息,然后阻塞本线程,直到发送成功
            Send(client,"This is a test<EOF>");  
            sendDone.WaitOne();  

            // 收取服务端对本客户端发送的消息的应答,然后阻塞本线程,直到收取完成
            Receive(client);
            receiveDone.WaitOne();

            // 打印出从服务端接受到的信息
            Console.WriteLine("Response received : {0}", response);  

            // 关闭socket连接
            client.Shutdown(SocketShutdown.Both);  
            client.Close();  

        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  

    private static void ConnectCallback(IAsyncResult ar) {  
        try {  
            // 在BeginConnect中传递过来的第三个参数就是Socket对象
            Socket client = (Socket) ar.AsyncState;  

            // 完成后,一个BeginConnect就要对应一个EndConnect
            client.EndConnect(ar);  

            // 打印输出
            Console.WriteLine("Socket connected to {0}",client.RemoteEndPoint.ToString());  

            // 通知完成,让StartClient函数所处线程解除阻塞,往下执行。
            connectDone.Set();  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  

    private static void Receive(Socket client) {  
        try {  
            // 创建一个state object,这个object将要传递给ReceiveCallback方法
            StateObject state = new StateObject();  
            state.workSocket = client;  

            // 开始从服务端处接受数据
            client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,  
                new AsyncCallback(ReceiveCallback), state);  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  

    private static void ReceiveCallback( IAsyncResult ar ) {  
        try {  
            // 拿到state object
            StateObject state = (StateObject) ar.AsyncState;  
            Socket client = state.workSocket;  

            // 看看从服务端收到多少字节的数据
            int bytesRead = client.EndReceive(ar);  

            if (bytesRead > 0) {  
                // 收到多少,就把它格式化一下,编码成字符串
                state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));  

                // 因为不是一次收完所有服务端要发送过来的数据,所以再继续首收发
                client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,  
                    new AsyncCallback(ReceiveCallback), state);  
            } else {  
                // 收到的字节数为0了,表示收取完毕了
                if (state.sb.Length > 1) {  
                    response = state.sb.ToString();  
                }  
                // 通知线程解除阻塞
                receiveDone.Set();  
            }  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  

    private static void Send(Socket client, String data) {  
        // 编码并发送数据到服务端
        byte[] byteData = Encoding.ASCII.GetBytes(data); 
        client.BeginSend(byteData, 0, byteData.Length, 0,  
            new AsyncCallback(SendCallback), client);  
    }  

    private static void SendCallback(IAsyncResult ar) {  
        try {  
            // 发送完成
            Socket client = (Socket) ar.AsyncState;  

            // 拿到实际发送的字节数,并打印出来
            int bytesSent = client.EndSend(ar);  
            Console.WriteLine("Sent {0} bytes to server.", bytesSent);  

            // 通知线程解除阻塞
            sendDone.Set();  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  

    public static int Main(String[] args) {  
        StartClient();  
        return 0;  
    }  
}

在调用BeginReceive方法之前,需要创建一个AsyncCallback类型的delegate回调方法。并且作为参数传递给BeginReceive方法。BeginReceive方法返回之后,系统会开一个独立的线程去执行这个回调方法。这个回调函数方法将使用一个IAsyncResult类型的值作为输入参数。这个输入参数也是BeginReceive方法所返回的。

在这个回调函数里面,可以通过IAsyncResult参数的AsyncState方法获取到由BeginReceive方法传递过来的状态对象(state object)。可以从这个state object中对传递过来的socket包进行收取(obtain)。收取完毕之后,可以调用EndReceive方法分析这些socket数据字节。

EndReceive方法会处阻塞住当前线程,直接数据收取完成为止,如果使用的是非连接协议的话,EndReceive会收取第一个入队列的数据报(first enqueued datagram)。如果使用的是面向连接的协议(connection-oriented protocol)的话,EndReceive方法将会尽可能地读取你所在BeginReceive方法中指定的缓冲区空间的大小那么多的字节数据。如果已经收到了所有的数据,并且服务器关闭了socket连接的话,EndReceive方法将会理解完成并且返回0

执行EndReceive函数时可能抛出的异常

异常名 异常描述和发生条件
ArgumentNullException 当传递给EndReceive的IAsyncResult类型的参数为null时发生此异常。
ArgumentException 当调用BeginReceive(Byte[], Int32, Int32, SocketFlags, AsyncCallback, Object) 函数时没有正确返回一个IAsyncResult类型的返回值。
InvalidOperationException EndReceive(IAsyncResult)在上一个异步读操作中被调用了
SocketException An error occurred when attempting to access the socket.
ObjectDisposedException socket连接已经被关闭时发生此异常。


参考网页

https://docs.microsoft.com/en-us/dotnet/framework/network-programming/asynchronous-client-socket-example
https://docs.microsoft.com/en-us/dotnet/framework/network-programming/asynchronous-server-socket-example