簡體   English   中英

C#中的套接字,我如何通過NetworkStream異步讀取和寫入數據

[英]Sockets in C#, how can I asynchronously read and write data through a NetworkStream

[我僅限於Visual Studio 2010,因此,C#4。異步和等待對我不可用。]

我正在為我的一個項目開發網絡架構,該項目通過服務器和客戶端之間的網絡發送數據包,但是客戶端和服務器必須在等待時繼續運行,因此代碼必須是非阻塞的,因此我想使用異步方法。 但是,除了簡單的同步一次性IO之外,我對執行的操作知之甚少,尤其是在使用NetworkStream時。 我想做的是:

1)客戶端連接到服務器

2)服務器接受連接

3)服務器等待來自客戶端的數據

4)服務器處理數據

5)服務器響應客戶端

6)打開連接時,請從3開始重復。

我想使用NetworkStream包裝套接字。 但是我是異步I / O的新手,我不確定在等待響應時,特別是在NetworkStream中,如何在不阻止服務器/客戶端代碼其他部分的情況下執行此操作。 在我的研究中,我看到了使用如下形式的示例:

while(true){
    socket.BeginAccept(new AsyncCallback(AcceptCallback), socket );
}

但是似乎該循環仍會阻止應用程序。 誰能給我一些關於如何做到這一點的指示(公頃)? 我無法找到許多使連接保持打開狀態的示例,只有客戶端連接->客戶端發送->服務器接收->服務器發送->斷開連接。 我不是要完整的代碼,而只是要求提供一些摘要。

這是對NetworkStream使用async / await的非常簡單的示例:

SocketServer.cs:

class SocketServer
{
    private readonly Socket _listen;

    public SocketServer(int port)
    {
        IPEndPoint listenEndPoint = new IPEndPoint(IPAddress.Loopback, port);
        _listen = new Socket(SocketType.Stream, ProtocolType.Tcp);
        _listen.Bind(listenEndPoint);
        _listen.Listen(1);
        _listen.BeginAccept(_Accept, null);
    }

    public void Stop()
    {
        _listen.Close();
    }

    private async void _Accept(IAsyncResult result)
    {
        try
        {
            using (Socket client = _listen.EndAccept(result))
            using (NetworkStream stream = new NetworkStream(client))
            using (StreamReader reader = new StreamReader(stream))
            using (StreamWriter writer = new StreamWriter(stream))
            {
                Console.WriteLine("SERVER: accepted new client");

                string text;

                while ((text = await reader.ReadLineAsync()) != null)
                {
                    Console.WriteLine("SERVER: received \"" + text + "\"");
                    writer.WriteLine(text);
                    writer.Flush();
                }
            }

            Console.WriteLine("SERVER: end-of-stream");

            // Don't accept a new client until the previous one is done
            _listen.BeginAccept(_Accept, null);
        }
        catch (ObjectDisposedException)
        {
            Console.WriteLine("SERVER: server was closed");
        }
        catch (SocketException e)
        {
            Console.WriteLine("SERVER: Exception: " + e);
        }
    }
}

Program.cs中:

class Program
{
    private const int _kport = 54321;

    static void Main(string[] args)
    {
        SocketServer server = new SocketServer(_kport);
        Socket remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
        IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Loopback, _kport);

        remote.Connect(remoteEndPoint);

        using (NetworkStream stream = new NetworkStream(remote))
        using (StreamReader reader = new StreamReader(stream))
        using (StreamWriter writer = new StreamWriter(stream))
        {
            Task receiveTask = _Receive(reader);
            string text;

            Console.WriteLine("CLIENT: connected. Enter text to send...");

            while ((text = Console.ReadLine()) != "")
            {
                writer.WriteLine(text);
                writer.Flush();
            }

            remote.Shutdown(SocketShutdown.Send);
            receiveTask.Wait();
        }

        server.Stop();
    }

    private static async Task _Receive(StreamReader reader)
    {
        string receiveText;

        while ((receiveText = await reader.ReadLineAsync()) != null)
        {
            Console.WriteLine("CLIENT: received \"" + receiveText + "\"");
        }

        Console.WriteLine("CLIENT: end-of-stream");
    }
}

這是一個非常簡單的示例,在同一過程中同時托管服務器和客戶端,並且一次僅接受一個連接。 它實際上只是出於說明目的。 現實世界中的場景無疑將包括其他滿足其需求的功能。

在這里,我將NetworkStream包裝在StreamReaderStreamWriter 請注意,您必須調用Flush()以確保實際發送了數據。 為了更好地控制I / O,您當然可以直接使用NetworkStream 只需使用Stream.ReadAsync()方法而不是StreamReader.ReadLineAsync() 還要注意,在我的示例中,編寫是同步的。 如果願意,您也可以使用與閱讀所示相同的基本技術來使此異步。

編輯:

OP表示他們無法使用async / await 這是使用NetworkStream和舊式Begin/EndXXX() API的客戶端版本(當然會對服務器進行類似更改):

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

namespace TestOldSchoolNetworkStream
{
    class Program
    {
        private const int _kport = 54321;

        static void Main(string[] args)
        {
            SocketServer server = new SocketServer(_kport);
            Socket remote = new Socket(SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Loopback, _kport);

            remote.Connect(remoteEndPoint);

            using (NetworkStream stream = new NetworkStream(remote))
            {
                // For convenience, These variables are local and captured by the
                // anonymous method callback. A less-primitive implementation would
                // encapsulate the client state in a separate class, where these objects
                // would be kept. The instance of this object would be then passed to the
                // completion callback, or the receive method itself would contain the
                // completion callback itself.
                ManualResetEvent receiveMonitor = new ManualResetEvent(false);
                byte[] rgbReceive = new byte[8192];
                char[] rgch = new char[Encoding.UTF8.GetMaxCharCount(rgbReceive.Length)];
                Decoder decoder = Encoding.UTF8.GetDecoder();
                StringBuilder receiveBuffer = new StringBuilder();

                stream.BeginRead(rgbReceive, 0, rgbReceive.Length, result =>
                {
                    _Receive(stream, rgbReceive, rgch, decoder, receiveBuffer, receiveMonitor, result);
                }, null);

                string text;

                Console.WriteLine("CLIENT: connected. Enter text to send...");

                while ((text = Console.ReadLine()) != "")
                {
                    byte[] rgbSend = Encoding.UTF8.GetBytes(text + Environment.NewLine);

                    remote.BeginSend(rgbSend, 0, rgbSend.Length, SocketFlags.None, _Send, Tuple.Create(remote, rgbSend.Length));
                }

                remote.Shutdown(SocketShutdown.Send);
                receiveMonitor.WaitOne();
            }

            server.Stop();
        }

        private static void _Receive(NetworkStream stream, byte[] rgb, char[] rgch, Decoder decoder, StringBuilder receiveBuffer, EventWaitHandle monitor, IAsyncResult result)
        {
            try
            {
                int byteCount = stream.EndRead(result);
                string fullLine = null;

                if (byteCount > 0)
                {
                    int charCount = decoder.GetChars(rgb, 0, byteCount, rgch, 0);

                    receiveBuffer.Append(rgch, 0, charCount);

                    int newLineIndex = IndexOf(receiveBuffer, Environment.NewLine);

                    if (newLineIndex >= 0)
                    {
                        fullLine = receiveBuffer.ToString(0, newLineIndex);
                        receiveBuffer.Remove(0, newLineIndex + Environment.NewLine.Length);
                    }

                    stream.BeginRead(rgb, 0, rgb.Length, result1 =>
                    {
                        _Receive(stream, rgb, rgch, decoder, receiveBuffer, monitor, result1);
                    }, null);
                }
                else
                {
                    Console.WriteLine("CLIENT: end-of-stream");
                    fullLine = receiveBuffer.ToString();
                    monitor.Set();
                }

                if (!string.IsNullOrEmpty(fullLine))
                {
                    Console.WriteLine("CLIENT: received \"" + fullLine + "\"");
                }
            }
            catch (IOException e)
            {
                Console.WriteLine("CLIENT: Exception: " + e);
            }
        }

        private static int IndexOf(StringBuilder sb, string text)
        {
            for (int i = 0; i < sb.Length - text.Length + 1; i++)
            {
                bool match = true;

                for (int j = 0; j < text.Length; j++)
                {
                    if (sb[i + j] != text[j])
                    {
                        match = false;
                        break;
                    }
                }

                if (match)
                {
                    return i;
                }
            }

            return -1;
        }

        private static void _Send(IAsyncResult result)
        {
            try
            {
                Tuple<Socket, int> state = (Tuple<Socket, int>)result.AsyncState;
                int actualLength = state.Item1.EndSend(result);

                if (state.Item2 != actualLength)
                {
                    // Should never happen...the async operation should not complete until
                    // the full buffer has been successfully sent, 
                    Console.WriteLine("CLIENT: send completed with only partial success");
                }
            }
            catch (IOException e)
            {
                Console.WriteLine("CLIENT: Exception: " + e);
            }
        }
    }
}

請注意,即使遺漏了一堆異常處理邏輯,該代碼也相當長,至少部分原因是TextReader沒有內置的異步API,因此對輸入數據的處理是這里更加詳細。 當然,這是針對簡單的基於行的文本交換協議。 就數據拆包方面而言,其他協議可能會或多或少復雜,但是NetworkStream的底層讀取和寫入元素將是相同的。

這是很好的示例,展示了C#中實現異步通信的一般思想

異步客戶端套接字示例: http : //msdn.microsoft.com/zh-cn/library/bew39x2a(v=vs.110).aspx

異步服務器套接字示例: http : //msdn.microsoft.com/zh-cn/library/fx6588te%28v=vs.110%29.aspx

服務器示例中的代碼綁定到套接字。 並開始接受客戶。 當某些客戶端連接時,提供給BeginAccept的回調將被調用。 在accept回調中,您可以管理客戶端套接字並開始讀取或寫入。 在accept回調結束時,它發出allDone事件信號,並且主循環開始接受新客戶端。

注意:

public static ManualResetEvent allDone = new ManualResetEvent(false);

這有助於避免浪費CPU循環。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM