【问题标题】:Asynchronous Server Socket in .Net Core - How do I return the result?.Net Core 中的异步服务器套接字 - 如何返回结果?
【发布时间】:2020-10-02 16:43:01
【问题描述】:

我需要通过 TCP 异步发送数据。它是一个字符串集合ICollection<string>

我搜索并找到了一个很好的starting example from Microsoft(见下文)。该示例似乎在.NET Framework 下,但我认为它也适用于.NET Core

我在做什么:

  1. 我将代码重新用作非静态

  2. 我想发送一组字符串ICollection<string>。我知道我可以重写它以在 main 方法中发送字符串集合。没问题。

  3. 我希望收到对发送的每条消息的响应并对其进行处理。当前响应静态存储在private static String response = String.Empty; 中。我不希望它是静态的。我想要一个局部方法变量。

  4. 我的挑战从项目 3. 开始。如何返回似乎只能从 private static void ReceiveCallback( IAsyncResult ar ) 内部访问的响应消息

    我不认为将其更改为 private static string ReceiveCallback( IAsyncResult ar ) 会起作用。如果是这样,我如何从client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state); 读取它?

我在一篇非常古老的帖子上针对我发现的一个类似问题提出了 300 分赏金:C# Asyn. Socket Programming。很高兴奖励任何在这里和那里回答的人。

另外一个问题是:建议的做法是打开一个 TCP 连接,发送多条消息,然后关闭它?还是为正在发送的每条消息打开一个 TCP 连接?

微软示例

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 {  
    // Client socket.  
    public Socket workSocket = null;  
    // Size of receive buffer.  
    public const int BufferSize = 256;  
    // Receive buffer.  
    public byte[] buffer = new byte[BufferSize];  
    // Received data string.  
    public StringBuilder sb = new StringBuilder();  
}  
  
public class AsynchronousClient {  
    // The port number for the remote device.  
    private const int port = 11000;  
  
    // ManualResetEvent instances signal completion.  
    private static ManualResetEvent connectDone =
        new ManualResetEvent(false);  
    private static ManualResetEvent sendDone =
        new ManualResetEvent(false);  
    private static ManualResetEvent receiveDone =
        new ManualResetEvent(false);  
  
    // The response from the remote device.  <------ ### the response data that I want to access, non statically
    private static String response = String.Empty;  
  
    private static void StartClient() {  
        // Connect to a remote device.  
        try {  
            // Establish the remote endpoint for the socket.  
            // The name of the
            // remote device is "host.contoso.com".  
            IPHostEntry ipHostInfo = Dns.GetHostEntry("host.contoso.com");  
            IPAddress ipAddress = ipHostInfo.AddressList[0];  
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);  
  
            // Create a TCP/IP socket.  
            Socket client = new Socket(ipAddress.AddressFamily,  
                SocketType.Stream, ProtocolType.Tcp);  
  
            // Connect to the remote endpoint.  
            client.BeginConnect( remoteEP,
                new AsyncCallback(ConnectCallback), client);  
            connectDone.WaitOne();  
  
            // Send test data to the remote device.  
            Send(client,"This is a test<EOF>");  
            sendDone.WaitOne();  
  
            // Receive the response from the remote device.  
            Receive(client);  
            receiveDone.WaitOne();  
  
            // Write the response to the console.  
            Console.WriteLine("Response received : {0}", response);  
  
            // Release the socket.  
            client.Shutdown(SocketShutdown.Both);  
            client.Close();  
  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  
  
    private static void ConnectCallback(IAsyncResult ar) {  
        try {  
            // Retrieve the socket from the state object.  
            Socket client = (Socket) ar.AsyncState;  
  
            // Complete the connection.  
            client.EndConnect(ar);  
  
            Console.WriteLine("Socket connected to {0}",  
                client.RemoteEndPoint.ToString());  
  
            // Signal that the connection has been made.  
            connectDone.Set();  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  
  
    private static void Receive(Socket client) {  
        try {  
            // Create the state object.  
            StateObject state = new StateObject();  
            state.workSocket = client;  
  
            // Begin receiving the data from the remote device.  
            client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,  
                new AsyncCallback(ReceiveCallback), state);  //<------ The receive callback is here, how do I return the result to the caller?
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  
  
    private static void ReceiveCallback( IAsyncResult ar ) {  
        try {  
            // Retrieve the state object and the client socket
            // from the asynchronous state object.  
            StateObject state = (StateObject) ar.AsyncState;  
            Socket client = state.workSocket;  
  
            // Read data from the remote device.  
            int bytesRead = client.EndReceive(ar);  
  
            if (bytesRead > 0) {  
                // There might be more data, so store the data received so far.  
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));  
  
                // Get the rest of the data.  
                client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,  
                    new AsyncCallback(ReceiveCallback), state);  
            } else {  
                // All the data has arrived; put it in response.  
                if (state.sb.Length > 1) {  
                    response = state.sb.ToString();  //<--------- ### Where it is assigned, I want it returned
                }  
                // Signal that all bytes have been received.  
                receiveDone.Set();  
            }  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  
  
    private static void Send(Socket client, String data) {  
        // Convert the string data to byte data using ASCII encoding.  
        byte[] byteData = Encoding.ASCII.GetBytes(data);  
  
        // Begin sending the data to the remote device.  
        client.BeginSend(byteData, 0, byteData.Length, 0,  
            new AsyncCallback(SendCallback), client);  
    }  
  
    private static void SendCallback(IAsyncResult ar) {  
        try {  
            // Retrieve the socket from the state object.  
            Socket client = (Socket) ar.AsyncState;  
  
            // Complete sending the data to the remote device.  
            int bytesSent = client.EndSend(ar);  
            Console.WriteLine("Sent {0} bytes to server.", bytesSent);  
  
            // Signal that all bytes have been sent.  
            sendDone.Set();  
        } catch (Exception e) {  
            Console.WriteLine(e.ToString());  
        }  
    }  
  
    public static int Main(String[] args) {  
        StartClient();  
        return 0;  
    }  
}

【问题讨论】:

  • 为您的通信类创建一个事件(类似于 IncomingData)并在 ReceiveCallback 中触发它。确保委托具有合适的参数来包含响应。如果这样做,客户端类可以订阅 IncomingData 事件并收到收到的每个响应的通知。
  • 根据 TCP 连接:为每条消息打开一个新的 TCP 连接并不高效,因为如果您需要持续的消息交换,这将需要为每个新连接重新握手。不过,这取决于您需要连接到这些设备的频率以及在每个会话中您要与它们交换多少消息。由于 TCP 是一种面向连接的协议,因此即使没有实际的数据流,连接也可以无限期地保持打开状态。
  • 谢谢@FandangoOnCore,但代码会是什么样子?

标签: c# asynchronous .net-core tcp tcpclient


【解决方案1】:

我从 .NET Core 团队找到了答案。据他们说:

关于Microsoft Example

这实际上不是一个很好的例子,因为它使用了过时的 Begin*/End* 模式(也称为 APM)。相反,您应该使用 using 异步等待。

如果你切换到那个,它会改变代码的方式 你想要更轻松,因为不再有任何回调, 相反,您会这样做等待 client.ReceiveAsync(...) 并在处理后 响应,返回结果。

https://github.com/dotnet/core/issues/4828#issuecomment-643619106

推荐的做法如下:

ICollection<string> strings = ...;
using Socket socket = ...;
using var stream = new NetworkStream(socket);
using var writer = new StreamWriter(stream);

foreach(string s in strings)
{
    await writer.WriteLineAsync(s);
}
await writer.FlushAsync();

添加注释:

如果您的字符串包含换行符,您需要为您的字符串添加长度前缀 消息或在写入之前转义换行符。

我的问题:Is it recommended practice to open a TCP connection, send the multiple messages, then close it? Or to open a TCP connection for each message being sent?

建立 TCP 连接通常比建立 TCP 连接要昂贵得多 使用现有的。但是,这最终取决于情景,并且 你会想在这里做更多的学习和原型设计 什么适合你。

https://github.com/dotnet/core/issues/4828#issuecomment-643694377

【讨论】:

    【解决方案2】:

    您可以直接从 Microsoft 示例中创建一个实现套接字通信的所有逻辑的类(非静态,我称之为 AsynchronousClient)。相关的补充是 3 个事件(更多关于 handling and raising events):

    1) ConnectionComplete,异步连接操作完成时触发;

    2) SendComplete,当数据(本例中为字符串)发送成功时触发;

    3) DataReceived,当有来自远程端点的传入数据时触发。

    基本上,该类公开了 3 个公共方法:AsyncConnect、AsyncSend 和 AsyncReceive。在 3 个私有回调中,上面列表中的相应事件被触发,并且使用 AsynchronousClient 的类被通知操作终止。

    public class AsynchronousClient
    {
        /// <summary>
        /// The client's socket instance.
        /// </summary>
        private Socket _clientSocket;
    
        /// <summary>
        /// Define the signature of the handler of the ConnectionComplete event.
        /// </summary>
        public delegate void ConnectionCompleteEventDelegate(AsynchronousClient sender, Socket clientSocket);
    
        /// <summary>
        /// Define the signature of the handler of the SendComplete event.
        /// </summary>
        public delegate void SendCompleteEventDelegate(AsynchronousClient sender, Socket clientSocket);
    
        /// <summary>
        ///  Define the signature of the handler of the DataReceived event.
        /// </summary>
        public delegate void DataReceivedEventDelegate(AsynchronousClient sender, Socket clientSocket, string data);
    
        /// <summary>
        /// ConnectionComplete event the client class can subscribe to.
        /// </summary>
        public event ConnectionCompleteEventDelegate ConnectionComplete;
    
        /// <summary>
        /// SendComplete event a class using an AsynchronousClient instance can subscribe to.
        /// </summary>
        public event SendCompleteEventDelegate SendComplete;
    
        /// <summary>
        /// DataReceived event a class using an AsynchronousClient instance can subscribe to.
        /// </summary>
        public event DataReceivedEventDelegate DataReceived;
    
        /// <summary>
        /// The remote endpoint the socket is going to communicate to. 
        /// </summary>
        public IPEndPoint RemoteEndpoint { get; private set; }
    
        /// <summary>
        /// Class initializer.
        /// </summary>
        /// <param name="remoteEndpoint">The remote endpoint to connect to.</param>
        public AsynchronousClient(IPEndPoint remoteEndpoint)
        {
            RemoteEndpoint = remoteEndpoint;
            // Create a TCP/IP socket.  
            _clientSocket = new Socket(
                RemoteEndpoint.AddressFamily, 
                SocketType.Stream, 
                ProtocolType.Tcp);
        }
    
        /// <summary>
        /// Asynchronous connection request.
        /// </summary>
        public void AsyncConnect()
        {
            try
            {
                // Initiate the connection procedure to the remote endpoint.  
                _clientSocket.BeginConnect(
                    RemoteEndpoint,
                    new AsyncCallback(AsyncConnectCallback), _clientSocket);
            }
            catch (Exception ex)
            {
                // TODO: manage exception.
                throw;
            }
        }
    
        /// <summary>
        /// Called after the connection to the remote endpoint is established.
        /// </summary>
        private void AsyncConnectCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.  
                Socket client = (Socket)ar.AsyncState;
                // Complete the connection.  
                client.EndConnect(ar);
                // If a client class is subscribed to the event, invoke the delegate.
                if (!(ConnectionComplete is null))
                    ConnectionComplete.Invoke(this, client);
            }
            catch (Exception ex)
            {
                // TODO: manage exception.
                throw;
            }
        }
    
        /// <summary>
        /// Asynchronously sends a string to the remote endpoint.
        /// </summary>
        public void AsyncSend(string data)
        {
            try
            {
                // Convert the string data to byte data using ASCII encoding.  
                byte[] byteData = Encoding.ASCII.GetBytes(data);
                // Begin sending the data to the remote device.  
                _clientSocket.BeginSend(byteData, 0, byteData.Length, 0,
                    new AsyncCallback(AsyncSendCallback), _clientSocket);
            }
            catch(Exception ex)
            {
                // TODO: manage exception.
                throw;
            }
        }
    
        /// <summary>
        /// Called after the send operation is complete.
        /// </summary>
        private void AsyncSendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.  
                Socket client = (Socket)ar.AsyncState;
                // Complete sending the data to the remote device.  
                int bytesSent = client.EndSend(ar);
                // If a client class is subscribed to the event, invoke the delegate.
                if (!(SendComplete is null))
                    SendComplete(this, client);
            }
            catch (Exception ex)
            {
                // TODO: manage exception.
                throw;
            }
        }
    
        /// <summary>
        /// Asynchronously waits for a response from the remote endpoint.
        /// </summary>
        public void AsyncReceive(Socket client)
        {
            try
            {
                // Create the state object.  
                StateObject state = new StateObject();
                state.workSocket = client;
                // Begin receiving the data from the remote device.  
                client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                    new AsyncCallback(AsyncReceiveCallback), state);
            }
            catch (Exception ex)
            {
                // TODO: manage exception.
                throw;
            }
        }
    
        /// <summary>
        /// Called after the receive operation is complete.
        /// </summary>
        private void AsyncReceiveCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the state object and the client socket
                // from the asynchronous state object.  
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.workSocket;
                // Read data from the remote device.  
                int bytesRead = client.EndReceive(ar);
    
                if (bytesRead > 0)
                {
                    // There might be more data, so store the data received so far.  
                    state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                    // Get the rest of the data.  
                    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                        new AsyncCallback(AsyncReceiveCallback), state);
                }
                else
                {
                    // All the data has arrived; put it in response.  
                    if (state.sb.Length > 1)
                    {
                        var response = state.sb.ToString();  //<--------- ### Where it is assigned, I want it returned
                        // If a client class is subscribed to the event, invoke the delegate.
                        // Here the client class is notified, and the response is passed as parameter to the delegate.
                        if (!(DataReceived is null))
                            DataReceived.Invoke(this, client, response);
                    }
                }
            }
            catch (Exception ex)
            {
                // TODO: manage exception.
                throw;
            }
        }
    }
    

    为了说明如何使用该类,我刚刚创建了一个带有两个按钮(BtnConnect 和 BtnSendString)的简单表单,但它当然可以在不同的上下文中使用。 我使用 Microsoft 的 Asynchronous Server Socket Example 测试了连接。请注意,在此示例中,套接字连接始终在响应发送回后由服务器关闭,如果您需要发送字符串集合而不必为每个字符串创建新连接,这可能是您想要避免的事情。

            private AsynchronousClient _asyncClient;
    
            private void Form1_Load(object sender, EventArgs e)
            {
                // I'm testing on the loopback interface.
                var remoteIp = IPAddress.Parse("127.0.0.1");
    
                // Create a new remote endpoint.
                var remoteEndpoint = new IPEndPoint(remoteIp, 11000);
    
                // Create a new instance of the AsynchronousClient client, 
                // passing the remote endpoint as parameter.
                _asyncClient = new AsynchronousClient(remoteEndpoint);
    
                // Subscription to the ConnectionComplete event.
                _asyncClient.ConnectionComplete += AsyncClient_ConnectionComplete;
    
                // Subscription to the SendComplete event.
                _asyncClient.SendComplete += AsyncClient_SendComplete;
    
                // Subscription to the DataReceived event.
                _asyncClient.DataReceived += AsyncClient_DataReceived;
            }
    
            /// <summary>
            /// Handler of the DataReceived event.
            /// </summary>
            private void AsyncClient_DataReceived(AsynchronousClient sender, Socket clientSocket, string data)
            {
                // Here I manage the data received by the remote endpoint.
                MessageBox.Show(string.Format("Data received: {0}", data));
            }
    
            /// <summary>
            /// Handler of the SendComplete event.
            /// </summary>
            private void AsyncClient_SendComplete(AsynchronousClient sender, Socket clientSocket)
            {
                // Here I'm starting an async receive operation, as I expect the remote endpoint
                // to send back some data.
                _asyncClient.AsyncReceive(clientSocket);
            }
    
            /// <summary>
            /// Handler of the ConnectionComplete event.
            /// </summary>
            private void AsyncClient_ConnectionComplete(AsynchronousClient sender, Socket clientSocket)
            {
                // Here I just want to warn the user the connection is set.
                MessageBox.Show("Successfully connected to the remote endpoint.");
            }
    
            /// <summary>
            /// Handler of the connect button.
            /// </summary>
            private void BtnConnect_Click(object sender, EventArgs e)
            {
                _asyncClient.AsyncConnect();
            }
    
            /// <summary>
            /// Handler of the SendString button.
            /// </summary>
            private void BtnSendString_Click(object sender, EventArgs e)
            {
                _asyncClient.AsyncSend("TEST DATA<EOF>");
            }
    

    【讨论】:

    • 感谢您的回复。我在 .NET Core github 存储库上发布了相同的问题,他们说 Microsoft 示例是 ... actually not a good example, because it uses the outdated Begin*/End* pattern (also known as APM). Instead, you should use using async-await.。感谢您的努力,但必须从 .NET Core 团队发布正确的版本
    猜你喜欢
    • 2021-12-30
    • 1970-01-01
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 1970-01-01
    相关资源
    最近更新 更多