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

Table of Contents

请尊重原作者的工作,转载时请务必注明转载自: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

kumakoko avatar
kumakoko
pure coder
comments powered by Disqus