C# Begin/EndReceive - how do I read large data?

后端 未结 7 1398
梦谈多话
梦谈多话 2020-11-30 05:59

When reading data in chunks of say, 1024, how do I continue to read from a socket that receives a message bigger than 1024 bytes until there is no data left? Should I just u

相关标签:
7条回答
  • 2020-11-30 06:37

    Also I troubled same problem.

    When I tested several times, I found that sometimes multiple BeginReceive - EndReceive makes packet loss. (This loop was ended improperly)

    In my case, I used two solution.

    First, I defined the enough packet size to make only 1 time BeginReceive() ~ EndReceive();

    Second, When I receive large size of data, I used NetworkStream.Read() instead of BeginReceive() - EndReceive().

    Asynchronous socket is not easy to use, and it need a lot of understanding about socket.

    0 讨论(0)
  • 2020-11-30 06:38

    No - call BeginReceive again from the callback handler, until EndReceive returns 0. Basically, you should keep on receiving asynchronously, assuming you want the fullest benefit of asynchronous IO.

    If you look at the MSDN page for Socket.BeginReceive you'll see an example of this. (Admittedly it's not as easy to follow as it might be.)

    0 讨论(0)
  • 2020-11-30 06:38

    You would read the length prefix first. Once you have that, you would just keep reading the bytes in blocks (and you can do this async, as you surmised) until you have exhausted the number of bytes you know are coming in off the wire.

    Note that at some point, when reading the last block you won't want to read the full 1024 bytes, depending on what the length-prefix says the total is, and how many bytes you have read.

    0 讨论(0)
  • 2020-11-30 06:43

    Dang. I'm hesitant to even reply to this given the dignitaries that have already weighed in, but here goes. Be gentle, O Great Ones!

    Without having the benefit of reading Marc's blog (it's blocked here due the corporate internet policy), I'm going to offer "another way."

    The trick, in my mind, is to separate the receipt of the data from the processing of that data.

    I use a StateObject class defined like this. It differs from the MSDN StateObject implementation in that it does not include the StringBuilder object, the BUFFER_SIZE constant is private, and it includes a constructor for convenience.

    public class StateObject
    {
        private const int BUFFER_SIZE = 65535;
        public byte[] Buffer = new byte[BUFFER_SIZE];
        public readonly Socket WorkSocket = null;
    
        public StateObject(Socket workSocket)
        {
            WorkSocket = workSocket;
        }
    }
    

    I also have a Packet class that is simply a wrapper around a buffer and a timestamp.

    public class Packet
    {
        public readonly byte[] Buffer;
        public readonly DateTime Timestamp;
    
        public Packet(DateTime timestamp, byte[] buffer, int size)
        {
            Timestamp = timestamp;
            Buffer = new byte[size];
            System.Buffer.BlockCopy(buffer, 0, Buffer, 0, size);
        }
    }
    

    My ReceiveCallback() function looks like this.

    public static ManualResetEvent PacketReceived = new ManualResetEvent(false);
    public static List<Packet> PacketList = new List<Packet>();
    public static object SyncRoot = new object();
    public static void ReceiveCallback(IAsyncResult ar)
    {
        try {
            StateObject so = (StateObject)ar.AsyncState;
            int read = so.WorkSocket.EndReceive(ar);
    
            if (read > 0) {
                Packet packet = new Packet(DateTime.Now, so.Buffer, read);
                lock (SyncRoot) {
                    PacketList.Add(packet);
                }
                PacketReceived.Set();
            }
    
            so.WorkSocket.BeginReceive(so.Buffer, 0, so.Buffer.Length, 0, ReceiveCallback, so);
        } catch (ObjectDisposedException) {
            // Handle the socket being closed with an async receive pending
        } catch (Exception e) {
            // Handle all other exceptions
        }
    }
    

    Notice that this implementation does absolutely no processing of the received data, nor does it have any expections as to how many bytes are supposed to have been received. It simply receives whatever data happens to be on the socket (up to 65535 bytes) and stores that data in the packet list, and then it immediately queues up another asynchronous receive.

    Since processing no longer occurs in the thread that handles each asynchronous receive, the data will obviously be processed by a different thread, which is why the Add() operation is synchronized via the lock statement. In addition, the processing thread (whether it is the main thread or some other dedicated thread) needs to know when there is data to process. To do this, I usually use a ManualResetEvent, which is what I've shown above.

    Here is how the processing works.

    static void Main(string[] args)
    {
        Thread t = new Thread(
            delegate() {
                List<Packet> packets;
                while (true) {
                    PacketReceived.WaitOne();
                    PacketReceived.Reset();
                    lock (SyncRoot) {
                        packets = PacketList;
                        PacketList = new List<Packet>();
                    }
    
                    foreach (Packet packet in packets) {
                        // Process the packet
                    }
                }
            }
        );
        t.IsBackground = true;
        t.Name = "Data Processing Thread";
        t.Start();
    }
    

    That's the basic infrastructure I use for all of my socket communication. It provides a nice separation between the receipt of the data and the processing of that data.

    As to the other question you had, it is important to remember with this approach that each Packet instance does not necessarily represent a complete message within the context of your application. A Packet instance might contain a partial message, a single message, or multiple messages, and your messages might span multiple Packet instances. I've addressed how to know when you've received a full message in the related question you posted here.

    0 讨论(0)
  • 2020-11-30 06:43

    There seems to be a lot of confusion surrounding this. The examples on MSDN's site for async socket communication using TCP are misleading and not well explained. The EndReceive call will indeed block if the message size is an exact multiple of the receive buffer. This will cause you to never get your message and the application to hang.

    Just to clear things up - You MUST provide your own delimiter for data if you are using TCP. Read the following (this is from a VERY reliable source).

    The Need For Application Data Delimiting

    The other impact of TCP treating incoming data as a stream is that data received by an application using TCP is unstructured. For transmission, a stream of data goes into TCP on one device, and on reception, a stream of data goes back to the application on the receiving device. Even though the stream is broken into segments for transmission by TCP, these segments are TCP-level details that are hidden from the application. So, when a device wants to send multiple pieces of data, TCP provides no mechanism for indicating where the “dividing line” is between the pieces, since TCP doesn't examine the meaning of the data at all. The application must provide a means for doing this.

    Consider for example an application that is sending database records. It needs to transmit record #579 from the Employees database table, followed by record #581 and record #611. It sends these records to TCP, which treats them all collectively as a stream of bytes. TCP will package these bytes into segments, but in a manner the application cannot predict. It is possible that each will end up in a different segment, but more likely they will all be in one segment, or part of each will end up in different segments, depending on their length. The records themselves must have some sort of explicit markers so the receiving device can tell where one record ends and the next starts.

    Source: http://www.tcpipguide.com/free/t_TCPDataHandlingandProcessingStreamsSegmentsandSequ-3.htm

    Most examples I see online for using EndReceive are wrong or misleading. It usually causes no problems in the examples because only one predefined message is sent and then the connection is closed.

    0 讨论(0)
  • 2020-11-30 06:47

    For info (general Begin/End usage), you might want to see this blog post; this approach is working OK for me, and saving much pain...

    0 讨论(0)
提交回复
热议问题