TCP c# 客户端可以在不休眠的情况下连续/连续地接收和发送吗? [英] Can a TCP c# client receive and send continuously/consecutively without sleep?

查看:39
本文介绍了TCP c# 客户端可以在不休眠的情况下连续/连续地接收和发送吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这在某种程度上是一个TCP 基础"问题,但与此同时,我还没有在其他地方找到令人信服的答案,并且相信我对 TCP 的基础知识有一个很好的/很好的理解.我不确定问题的组合(或一个问题,而我在此期间要求确认几点)是否违反规则.希望不会.

This is to a degree a "basics of TCP" question, yet at the same time I have yet to find a convincing answer elsewhere and believe i have a ok/good understanding of the basics of TCP. I am not sure if the combination of questions (or the one questions and while i'm at it the request for confirmation of a couple of points) is against the rules. Hope not.

我正在尝试编写 TCP 客户端的 C# 实现,该实现与包含 TCP 服务器的现有应用程序进行通信(我无权访问其代码,因此没有 WCF).我如何连接到它,在新信息传入或传出时根据需要发送和接收,并最终断开连接.使用 以下 MSDN 代码作为例如,他们列出了发送"和接收"异步方法(或仅 TcpClient),并忽略连接和断开连接是微不足道的,我怎样才能最好地继续检查收到的新数据包,同时在需要时发送?

I am trying to write a C# implementation of a TCP client, that communicates with an existing app containing a TCP server (I don't have access to its code, so no WCF). How do I connect to it, send and receive as needed as new info comes in or out, and ultimately disconnect. Using the following MSDN code as an example where they list "Send" and "Receive" asynchronous methods (or just TcpClient), and ignoring the connect and disconnect as trivial, how can I best go about continuously checking for new packets received and at the same time send when needed?

我一开始用的是TCPClient和GetStream(),msdn的代码好像还是需要稍微描述的loop和sleep(反直觉),我在一个单独的线程中循环运行receive方法,有sleep(10) 毫秒,并根据需要在主(或第三)线程中发送.这使我可以正常发送,并且接收方法有效地定期轮询以查找新数据包.然后将接收到的数据包添加到队列中.

I initially used TCPClient and GetStream(), and the msdn code still seems to require the loop and sleep described in a bit (counter intuitively), where I run the receive method in a loop in a separate thread with a sleep(10) milliseconds, and Send in the main (or third) thread as needed. This allows me to send fine, and the receive method effectively polls at regular intervals to find new packets. The received packets are then added to a queue.

这真的是最好的解决方案吗?难道不应该有一个等效的 DataAvailable 事件(或者我在 msdn 代码中缺少的东西)允许我们在有新数据可用时接收?

事后我注意到可以从另一端切断套接字,而客户端直到下一次拙劣的发送才意识到.澄清一下,客户端有义务发送常规的保持连接(接收是不够的,只发送)来确定套接字是否还活着.而keepalive 的频率决定了我多久会知道该链接已关闭.那是对的吗?我尝试了 Poll、socket.connected 等只是为了发现为什么每个都没有帮助.

As an afterthought I noticed that the socket could be cut from the other side without the client becoming aware till the next botched send. To clarify then, the client is obliged to send regular keepalives (and receive isn't sufficient, only send) to determine if the socket is still alive. And the frequency of the keepalive determines how soon I will know that link is down. Is that correct? I tried Poll, socket.connected etc only to discover why each just doesn't help.

最后确认一下(我认为不是,但好确定),在上面的按需发送和接收的场景中,如果tcpclient.DataAvailable每十秒一次,如果同时发送和接收会不会有数据丢失?如果我在接收的同时尝试发送,是否会失败、覆盖另一个或任何其他此类不需要的行为?

Lastly, to confirm (i believe not but good to make sure), in the above scenario of sending on demand and receiving if tcpclient.DataAvailable every ten seconds, can there be data loss if sending and receiving at the same time? If at the same time I am receiving I try and send will one fail, overwrite the other or any other such unwanted behaviour?

推荐答案

将问题组合在一起并没有错,但它确实使回答问题更具挑战性... :)

There's nothing wrong necessarily with grouping questions together, but it does make answering the question more challenging... :)

您链接的 MSDN 文章展示了如何进行一对一的 TCP 通信,即一次发送和一次接收.您还会注意到它使用了 Socket 类直接用于大多数人,包括我自己,会建议使用 TcpClient 类.您始终可以通过 Client 属性,如果您需要配置某个套接字(例如,SetSocketOption()).

The MSDN article you linked shows how to do a one-and-done TCP communication, that is, one send and one receive. You'll also notice it uses the Socket class directly where most people, including myself, will suggest using the TcpClient class instead. You can always get the underlying Socket via the Client property should you need to configure a certain socket for example (e.g., SetSocketOption()).

该示例要注意的另一方面是,虽然它使用线程来执行 AsyncCallback 代表 BeginSend()BeginReceive(),由于 ManualResetEvent 对象被使用.对于客户端和服务器之间的重复交换,这不是您想要的.

The other aspect about the example to note is that while it uses threads to execute the AsyncCallback delegates for both BeginSend() and BeginReceive(), it is essentially a single-threaded example because of how the ManualResetEvent objects are used. For repeated exchange between a client and server, this is not what you want.

好的,所以您想使用 TcpClient.连接到服务器(例如,TcpListener) 应该很简单 - 使用 Connect() 如果您想要阻止操作或 BeginConnect() 如果你想要一个非阻塞操作.建立连接后,使用 GetStream() 方法来获取 NetworkStream 用于读写的对象.使用 Read()/Write() 操作用于阻塞 I/O 和 BeginRead()/BeginWrite() 非阻塞 I/O 操作.请注意,BeginRead()BeginWrite() 使用与 BeginReceive()BeginSend() Socket 类的方法.

Alright, so you want to use TcpClient. Connecting to the server (e.g., TcpListener) should be straightforward - use Connect() if you want a blocking operation or BeginConnect() if you want a non-blocking operation. Once the connection is establish, use the GetStream() method to get the NetworkStream object to use for reading and writing. Use the Read()/Write() operations for blocking I/O and the BeginRead()/BeginWrite() operations for non-blocking I/O. Note that the BeginRead() and BeginWrite() use the same AsyncCallback mechanism employed by the BeginReceive() and BeginSend() methods of the Socket class.

此时需要注意的关键事项之一是 NetworkStream 的 MSDN 文档中的这个小说明:

One of the key things to note at this point is this little blurb in the MSDN documentation for NetworkStream:

读写操作可以同时进行NetworkStream 类的实例,无需同步.只要有一个唯一的线程进行写入操作和一个用于读取操作的独特线程,将有读写线程之间没有交叉干扰并且没有需要同步.

Read and write operations can be performed simultaneously on an instance of the NetworkStream class without the need for synchronization. As long as there is one unique thread for the write operations and one unique thread for the read operations, there will be no cross-interference between read and write threads and no synchronization is required.

简而言之,因为您计划从同一个 TcpClient 实例读取和写入,所以您需要两个线程来执行此操作.使用单独的线程将确保在有人尝试发送的同时接收数据时不会丢失任何数据.我在我的项目中处理这个的方法是创建一个顶级对象,比如 Client,它包装了 TcpClient 和它的底层 NetworkStream>.此类还创建和管理两个 Thread 对象,在构造过程中将 NetworkStream 对象传递给每个对象.第一个线程是 Sender 线程.任何想要发送数据的人都可以通过 Client 上的公共 SendData() 方法发送数据,该方法将数据路由到 Sender 进行传输.第二个线程是 Receiver 线程.该线程通过 Client 公开的公共事件将所有接收到的数据发布给感兴趣的各方.它看起来像这样:

In short, because you plan to read and write from the same TcpClient instance, you'll need two threads for doing this. Using separate threads will ensure that no data is lost while receiving data at the same time someone is trying to send. The way I've approached this in my projects is to create a top-level object, say Client, that wraps the TcpClient and its underlying NetworkStream. This class also creates and manages two Thread objects, passing the NetworkStream object to each during construction. The first thread is the Sender thread. Anyone wanting to send data does so via a public SendData() method on the Client, which routes the data to the Sender for transmission. The second thread is the Receiver thread. This thread publishes all received data to interested parties via a public event exposed by the Client. It looks something like this:

public sealed partial class Client : IDisposable
{
    // Called by producers to send data over the socket.
    public void SendData(byte[] data)
    {
        _sender.SendData(data);
    }

    // Consumers register to receive data.
    public event EventHandler<DataReceivedEventArgs> DataReceived;

    public Client()
    {
        _client = new TcpClient(...);
        _stream = _client.GetStream();

        _receiver = new Receiver(_stream);
        _sender   = new Sender(_stream);

        _receiver.DataReceived += OnDataReceived;
    }

    private void OnDataReceived(object sender, DataReceivedEventArgs e)
    {
        var handler = DataReceived;
        if (handler != null) DataReceived(this, e);  // re-raise event
    }

    private TcpClient     _client;
    private NetworkStream _stream;
    private Receiver      _receiver;
    private Sender        _sender;
}


private sealed partial class Client
{
    private sealed class Receiver
    {
        internal event EventHandler<DataReceivedEventArgs> DataReceived;

        internal Receiver(NetworkStream stream)
        {
            _stream = stream;
            _thread = new Thread(Run);
            _thread.Start();
        }

        private void Run()
        {
            // main thread loop for receiving data...
        }

        private NetworkStream _stream;
        private Thread        _thread;
    }
}


private sealed partial class Client
{
    private sealed class Sender
    {
        internal void SendData(byte[] data)
        {
            // transition the data to the thread and send it...
        }

        internal Sender(NetworkStream stream)
        {
            _stream = stream;
            _thread = new Thread(Run);
            _thread.Start();
        }

        private void Run()
        {
            // main thread loop for sending data...
        }

        private NetworkStream _stream;
        private Thread        _thread;
    }
}

请注意,这是三个单独的 .cs 文件,但定义了同一个 Client 类的不同方面.我使用 此处 中描述的 Visual Studio 技巧来嵌套相应的 ReceiverClient 文件下的 code> 和 Sender 文件.简而言之,我就是这样做的.

Notice that these are three separate .cs files but define different aspects of the same Client class. I use the Visual Studio trick described here to nest the respective Receiver and Sender files under the Client file. In a nutshell, that's the way I do it.

关于 NetworkStream.DataAvailable/Thread.Sleep() 问题.我同意事件会很好,但您可以通过使用 Read() 方法与无限 ReadTimeout.这不会对应用程序的其余部分(例如 UI)产生不利影响,因为它在自己的线程中运行.但是,这会使关闭线程变得复杂(例如,当应用程序关闭时),因此您可能希望使用更合理的值,例如 10 毫秒.但是你又回到了投票,这是我们首先试图避免的.这是我的做法,并附上解释说明:

Regarding the NetworkStream.DataAvailable/Thread.Sleep() question. I would agree that an event would be nice, but you can effectively achieve this by using the Read() method in combination with an infinite ReadTimeout. This will have no adverse impact on the rest of your application (e.g., UI) since it's running in its own thread. However, this complicates shutting down the thread (e.g., when the application closes), so you'd probably want to use something more reasonable, say 10 milliseconds. But then you're back to polling, which is what we're trying to avoid in the first place. Here's how I do it, with comments for explanation:

private sealed class Receiver
{
    private void Run()
    {
        try
        {
            // ShutdownEvent is a ManualResetEvent signaled by
            // Client when its time to close the socket.
            while (!ShutdownEvent.WaitOne(0))
            {
                try
                {
                    // We could use the ReadTimeout property and let Read()
                    // block.  However, if no data is received prior to the
                    // timeout period expiring, an IOException occurs.
                    // While this can be handled, it leads to problems when
                    // debugging if we are wanting to break when exceptions
                    // are thrown (unless we explicitly ignore IOException,
                    // which I always forget to do).
                    if (!_stream.DataAvailable)
                    {
                        // Give up the remaining time slice.
                        Thread.Sleep(1);
                    }
                    else if (_stream.Read(_data, 0, _data.Length) > 0)
                    {
                        // Raise the DataReceived event w/ data...
                    }
                    else
                    {
                        // The connection has closed gracefully, so stop the
                        // thread.
                        ShutdownEvent.Set();
                    }
                }
                catch (IOException ex)
                {
                    // Handle the exception...
                }
            }
        }
        catch (Exception ex)
        {
            // Handle the exception...
        }
        finally
        {
            _stream.Close();
        }
    }
}

就keepalives"而言,不幸的是,除了尝试发送一些数据之外,没有办法解决知道对方何时静默退出连接的问题.就我而言,由于我同时控制发送方和接收方,因此我在协议中添加了一个很小的 ​​KeepAlive 消息(8 个字节).除非已经在发送其他数据,否则 TCP 连接的双方每 5 秒发送一次.

As far as 'keepalives' are concerned, there is unfortunately not a way around the problem of knowing when the other side has exited the connection silently except to try sending some data. In my case, since I control both the sending and receiving sides, I've added a tiny KeepAlive message (8 bytes) to my protocol. This is sent every five seconds from both sides of the TCP connection unless other data is already being sent.

我想我已经解决了您提到的所有方面.希望这对您有所帮助.

I think I've addressed all the facets that you touched on. I hope you find this helpful.

这篇关于TCP c# 客户端可以在不休眠的情况下连续/连续地接收和发送吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆