简体   繁体   English

C#中的套接字,我如何通过NetworkStream异步读取和写入数据

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

[I am limited to Visual Studio 2010, and therefore, C# 4. async and await are not available to me.] [我仅限于Visual Studio 2010,因此,C#4。异步和等待对我不可用。]

I'm working on network architecture for a project of mine, that sends packets of data over a network between a server and a client, but the client and server must continue running while waiting, so the code has to be non-blocking, so I thought to use the asynchronous methods. 我正在为我的一个项目开发网络架构,该项目通过服务器和客户端之间的网络发送数据包,但是客户端和服务器必须在等待时继续运行,因此代码必须是非阻塞的,因此我想使用异步方法。 However, except for simple synchronous one-time IO, I don't know much about what to do, especially when using a NetworkStream. 但是,除了简单的同步一次性IO之外,我对执行的操作知之甚少,尤其是在使用NetworkStream时。 What I'm trying to do is: 我想做的是:

1) Client connects to server 1)客户端连接到服务器

2) Server accepts connection 2)服务器接受连接

3) Server waits for data from client 3)服务器等待来自客户端的数据

4) Server processes data 4)服务器处理数据

5) Server responds to client 5)服务器响应客户端

6) While connection is open, repeat from 3. 6)打开连接时,请从3开始重复。

And I would like to use a NetworkStream to wrap the socket. 我想使用NetworkStream包装套接字。 But I am new to asynchronous I/O and i'm not really sure how to do this without blocking other parts of the server/client code when waiting for a response, especially with a NetworkStream. 但是我是异步I / O的新手,我不确定在等待响应时,特别是在NetworkStream中,如何在不阻止服务器/客户端代码其他部分的情况下执行此操作。 In my research I see examples that use something like this: 在我的研究中,我看到了使用如下形式的示例:

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

But it seems like that loop would still hold up the application. 但是似乎该循环仍会阻止应用程序。 Can anyone give me some pointers (ha) on how exactly to do this? 谁能给我一些关于如何做到这一点的指示(公顷)? I haven't been able to find many examples that keep the connection open, only Client Connect -> Client Send -> Server Recieve -> Server Send -> Disconnect. 我无法找到许多使连接保持打开状态的示例,只有客户端连接->客户端发送->服务器接收->服务器发送->断开连接。 I'm not asking for full code, just the general idea with a few snippets. 我不是要完整的代码,而只是要求提供一些摘要。

Here is a very simple example of using async/await with a NetworkStream : 这是对NetworkStream使用async / await的非常简单的示例:

SocketServer.cs: 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: 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");
    }
}

It's a very simple example, hosting both the server and client in the same process and accepting just one connection at a time. 这是一个非常简单的示例,在同一过程中同时托管服务器和客户端,并且一次仅接受一个连接。 It's really just for illustration purposes. 它实际上只是出于说明目的。 Real-world scenarios will no doubt include other features to suit their needs. 现实世界中的场景无疑将包括其他满足其需求的功能。

Here, I'm wrapping the NetworkStream s in StreamReader s and StreamWriter s. 在这里,我将NetworkStream包装在StreamReaderStreamWriter Note that you have to call Flush() to ensure that the data is actually sent. 请注意,您必须调用Flush()以确保实际发送了数据。 For better control over the I/O, you can of course use the NetworkStream directly. 为了更好地控制I / O,您当然可以直接使用NetworkStream Just use the Stream.ReadAsync() method instead of StreamReader.ReadLineAsync() . 只需使用Stream.ReadAsync()方法而不是StreamReader.ReadLineAsync() Note also that in my example, writing is synchronous. 还要注意,在我的示例中,编写是同步的。 You can make this asynchronous as well if you like, using the same basic technique as shown for reading. 如果愿意,您也可以使用与阅读所示相同的基本技术来使此异步。

EDIT: 编辑:

The OP indicates they are unable to use async / await . OP表示他们无法使用async / await Here is a version of the client which uses NetworkStream and the old-style Begin/EndXXX() API (similar changes would be made to the server of course): 这是使用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);
            }
        }
    }
}

Note that this code, even in spite of leaving out a bunch of exception-handling logic, is considerably longer, at least in part due to the fact that TextReader has no built-in asynchronous API, and so the processing of the input data is much more verbose here. 请注意,即使遗漏了一堆异常处理逻辑,该代码也相当长,至少部分原因是TextReader没有内置的异步API,因此对输入数据的处理是这里更加详细。 Of course, this is for a simple line-based text exchange protocol. 当然,这是针对简单的基于行的文本交换协议。 Other protocols may be more or less complex in terms of the data-unpacking aspects, but the underlying read and write elements of the NetworkStream would be the same. 就数据拆包方面而言,其他协议可能会或多或少复杂,但是NetworkStream的底层读取和写入元素将是相同的。

This is good examples that shows general idea of realisation async communication in C# 这是很好的示例,展示了C#中实现异步通信的一般思想

Asynchronous Client Socket Example: http://msdn.microsoft.com/en-us/library/bew39x2a(v=vs.110).aspx 异步客户端套接字示例: http : //msdn.microsoft.com/zh-cn/library/bew39x2a(v=vs.110).aspx

Asynchronous Server Socket Example: http://msdn.microsoft.com/en-us/library/fx6588te%28v=vs.110%29.aspx 异步服务器套接字示例: http : //msdn.microsoft.com/zh-cn/library/fx6588te%28v=vs.110%29.aspx

The code in server example binds to socket. 服务器示例中的代码绑定到套接字。 And start to accept clients. 并开始接受客户。 When some client connects, the callback provided to BeginAccept called. 当某些客户端连接时,提供给BeginAccept的回调将被调用。 In accept callback you can manage client socket and start to read or write. 在accept回调中,您可以管理客户端套接字并开始读取或写入。 At the end of accept callback it signal allDone event and the main loop start to accept new client. 在accept回调结束时,它发出allDone事件信号,并且主循环开始接受新客户端。

Take attention to: 注意:

public static ManualResetEvent allDone = new ManualResetEvent(false);

this help to not waste cpu in loop. 这有助于避免浪费CPU循环。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM