一个TCP C#客户端可以接收和发送连续/连续不睡觉? [英] Can a TCP c# client receive and send continuously/consecutively without sleep?

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

问题描述

这是在一定程度上是TCP的基础知识的问题,但在同一时间,我还没有在别的地方找到一个令人信服的答案,我相信我有TCP的基础知识的OK /很好的理解。我不知道问题的组合(或一个问题,虽然我在它的一对夫妇点的确认请求)是违反规则的。希望不会。

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.

我想写一个C#实现TCP客户端,即包含一个TCP服务器的现有应用程序通信(我没有访问它的code,所以没有WCF)。我如何连接到它,发送并根据需要为新的信息进来或出去接受,最终断开连接。使用以下MSDN code 作为一个例子,他们列出发送和接收异步方法(或只是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 code似乎仍然需要一个位(与直觉相反)中描述的循环和睡眠,在那里我运行在一个循环中接收方法在单独的线程与根据需要在主(或第三)线程休眠(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 code),使我们能够接受的时候,只有当,有新的可用数据?

在事后,我注意到,插座可以从另一个侧面切未经客户意识到,直到下一个拙劣的发送。为了澄清然后,客户有责任定期发送保活(和接收是不够的,只发),以确定如果套接字仍然活着。和存活的频率决定我如何很快就会知道链接断开。那是对的吗?我想投票,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... :)

您链接显示了如何做一个一和做TCP通信,即MSDN文章,一个发送和一个接收。您还会注意到它使用的<一个href=\"http://msdn.microsoft.com/en-us/library/system.net.sockets.socket%28v=vs.110%29.aspx\"><$c$c>Socket类直接大多数人,包括我自己,会建议使用<$c$c>TcpClient类代替。你总是可以通过<一个得到基本插座 href=\"http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.client%28v=vs.110%29.aspx\"><$c$c>Client属性应该需要配置一定的插座,例如(例如,<一个href=\"http://msdn.microsoft.com/en-us/library/System.Net.Sockets.Socket.SetSocketOption%28v=vs.110%29.aspx\"><$c$c>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()).

约注意本实施例的另一个方面是,虽然它使用线程来执行<一href=\"http://msdn.microsoft.com/en-us/library/system.asynccallback%28v=vs.110%29.aspx\"><$c$c>AsyncCallback与会代表对于<一个href=\"http://msdn.microsoft.com/en-us/library/7h44aee9%28v=vs.110%29.aspx\"><$c$c>BeginSend()和<一个href=\"http://msdn.microsoft.com/en-us/library/dxkwh6zw%28v=vs.110%29.aspx\"><$c$c>BeginReceive(),它本质上是因为如何<一个单线程例子href=\"http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent%28v=vs.110%29.aspx\">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 。连接到服务器(例如,<一href=\"http://msdn.microsoft.com/en-us/library/system.net.sockets.tcplistener%28v=vs.110%29.aspx\"><$c$c>TcpListener)应该是简单 - 使用<一个href=\"http://msdn.microsoft.com/en-us/library/System.Net.Sockets.TcpClient.Connect%28v=vs.110%29.aspx\"><$c$c>Connect()如果你想要一个阻塞操作或<一个href=\"http://msdn.microsoft.com/en-us/library/System.Net.Sockets.TcpClient.BeginConnect%28v=vs.110%29.aspx\"><$c$c>BeginConnect()如果你想要一个非阻塞操作。一旦连接建立,使用<$c$c>GetStream()方法来获取<一个href=\"http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream%28v=vs.110%29.aspx\"><$c$c>NetworkStream对象用于读取和写入。使用<$c$c>Read()/<$c$c>Write()对于阻塞I / O和操作的<一个href=\"http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.beginread%28v=vs.110%29.aspx\"><$c$c>BeginRead()/<$c$c>BeginWrite()对于非阻塞I / O操作。请注意,的BeginRead() BeginWrite()使用相同的的AsyncCallback BeginReceive() BeginSend()方法使用机制的插座类。

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.

其中一个关键的事情在这一点要注意的是MSDN文档中的这个小做广告的的NetworkStream

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 实例写,你就需要两个线程这样做。使用单独的线程将确保,而在同一时间有人试图发送接收数据,没有数据丢失。我已经在我的项目接近这个问题的方法是创建一个顶级对象,说客户端,封装了的TcpClient 及其基础的NetworkStream 。此类还创建并管理两个<一个href=\"http://msdn.microsoft.com/en-us/library/system.threading.thread%28v=vs.110%29.aspx\"><$c$c>Thread物体,施工期间通过了的NetworkStream 对象每个。第一个线程是发件人线程。任何人想要发送的数据通过对客户端,将数据路由到公共送出数据()方法,这样做发件人进行传输。第二个线程是接收线程。此线程通过发布已的客户端暴露一个公共事件的所有接收到的数据有兴趣的人士。它看起来是这样的:

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文件,但定义相同的客户端类的不同方面。我这里使用Visual Studio招描述窝各自客户端文件下的接收器发件人文件。概括地说,这是我做的方式。

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.

关于<一个href=\"http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.dataavailable%28v=vs.110%29.aspx\"><$c$c>NetworkStream.DataAvailable/<$c$c>Thread.Sleep()题。我会同意,事件将是很好的,但你可以通过使用阅读()方法结合无限<一个有效地实现这一目标href=\"http://msdn.microsoft.com/en-us/library/bk6w7hs8%28v=vs.110%29.aspx\"><$c$c>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();
        }
    }
}

至于保持连接'而言,很不幸绕不知道什么时候对方已经退出了连接默默除了尝试发送一些数据问题的一种方式。对我来说,因为我控制的发送和接收双方,我添加了一个小小的的KeepAlive 消息(8字节)到我的协议。这是每五秒钟从TCP连接的双方均派出除非已被送往其他数据。

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天全站免登陆