Astronaut
Astronaut

Reputation: 7031

EndRead throws IO exception

This is for local communication between a MoSync application and an external DLL, MoSync does not allow me to use 3rd part DLLs and that is the reason why I have to implement this bridge software instead of using a simple call to a DLL, I have to convert from xml to the DLL Message format, and again to XML. I know this is a dumb thing, unfortunately there is no flexibility to change the architecture. Initially i thought that there was only one request so I had Sync coms but now I find out there can be more than one request, so I need to implement Async back again.

I have an exception that is thrown from time to time, since I am new to C# I am unable to find the memory leak... perhaps a pair of more well trained eyes can find the issue

SOURCE CODE:

I have written the following code, I am quite new to C# and Sockets, so perhaps I have made some big mistakes that only more experienced eyes can detect. This is to be used in a Windows Mobile 6.1 device, so I am trying to avoid using many threads.

using System;

using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Diagnostics;

namespace SmartDevice_Server
{
    //ClientConnection saves connection information is used to keep context in Async and Event calls
    public class ClientConnection : EventArgs
    {
        public NetworkStream NetworkStream { get; private set; }
        public byte[] Data { get; private set; }
        public int byteReadCount { get; set; }

        public ClientConnection(NetworkStream networkStream, byte[] data)
        {
            NetworkStream = networkStream;
            Data = data;
        }
    }

    //MySocket - Is a server that listens for events and triggers Events upon Request Completion 
    public class MySocketTCP
    {
        #region Class Members
        TcpListener myTcpListener;
        TcpClient myTcpClient;
        NetworkStream myNetworkStream;

        const string localHost = "127.0.0.1";
        IPAddress myAddress = IPAddress.Parse(localHost);
        int myPortNumber = 58889;
        byte[] myData;

        int bytesReadCount;
        const int MIN_REQUEST_STRING_SIZE = 10;

        int TimeStart;

        //Event
        public event socketReadCompleteHandler socketReadCompleteEvent;
        public EventArgs eventArguments = null;
        public delegate void socketReadCompleteHandler(MySocketTCP myTcpSocket, ClientConnection eventArguments);

        #endregion

        //Constructor
        public MySocketTCP()
        {
            Init();
        }

        //Constructor overloaded to receive IPAdress Host, and Port number
        public MySocketTCP(IPAddress hostAddress, int portNumber)
        {
            myAddress = hostAddress;
            myPortNumber = portNumber;

            Init();
        }

        //Initializes the TCPListner
        public void Init()
        {
            try
            {
                myTcpListener = new TcpListener(myAddress, myPortNumber);

                //myNetworkStream = myTcpClient.GetStream();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /*TODO_Listener_Timer: After you accept a connection you wait for data to be Read indefinitely
         *Possible solution: Use a timeout to close the socket connection.
         *Check WIKI, TODOS
         * */
        //Listens Asynchronously to Clients, class a recieveMessageHandler to process the read
        public void ListenAsync()
        {
            myTcpListener.Start();

            while (true)
            {
                //blocks until a client has connected to the server
                myTcpClient = myTcpListener.AcceptTcpClient();

                var client = new ClientConnection(myTcpClient.GetStream(), new byte[myTcpClient.ReceiveBufferSize]);

                // Capture the specific client and pass it to the receive handler
                client.NetworkStream.BeginRead(client.Data, 0, client.Data.Length, r => receiveMessageHandler(r, client), null);
            }
        }

        //Callback is used to Process the request Asynchronously, triggers socketReadCompleteEvent
        public void receiveMessageHandler(IAsyncResult asyncResult, ClientConnection clientInstance)
        {
            bytesReadCount = 0;

            lock (clientInstance.NetworkStream)
            {
                try
                {
                    bytesReadCount = clientInstance.NetworkStream.EndRead(asyncResult);
                    clientInstance.byteReadCount = bytesReadCount;
                }
                catch (Exception exc)
                {
                    throw exc;
                }
            }

            if (bytesReadCount < MIN_REQUEST_STRING_SIZE)
            {
                //Could not read form client.
                Debug.WriteLine("NO DATA READ");
            }
            else
            {
                if (socketReadCompleteEvent != null)
                {
                    socketReadCompleteEvent(this, clientInstance);
                }
            }
        }

        //Reads the request, uses the ClientConnection for context
        public string ReadAsync(ClientConnection connObj)
        {
            int bytesReadCount = connObj.byteReadCount;
            byte[] myData = connObj.Data;

            string xmlMessage;

            try
            {
                xmlMessage = Encoding.ASCII.GetString(myData, 0, bytesReadCount);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return xmlMessage;
        }

        //Deprecated
        public string Read()
        {
            string xmlMessage;

            try
            {
                xmlMessage = Encoding.ASCII.GetString(myData, 0, bytesReadCount);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            return xmlMessage;
        }

        //Deprecated
        public void Write(byte[] outBytes)
        {
            try
            {
                myNetworkStream.Write(outBytes, 0, outBytes.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        //Deprecated
        public void Write(string outMessage)
        {
            byte[] outBytes = Encoding.ASCII.GetBytes(outMessage);

            try
            {
                myNetworkStream.Write(outBytes, 0, outBytes.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            int TimeEnd = Environment.TickCount;
            int TimeResult = TimeEnd - TimeStart;
        }

        //Is used to send the message to the correct socket
        public void WriteAsync(ClientConnection connObj, string outMessage)
        {
            byte[] outBytes = Encoding.ASCII.GetBytes(outMessage);

            try
            {
                connObj.NetworkStream.Write(outBytes, 0, outBytes.Length);
            }
            catch (Exception ex)
            {
                throw ex;
            }

            int TimeEnd = Environment.TickCount;
            int TimeResult = TimeEnd - TimeStart;
        }

        //Closes the client
        public void Close()
        {
            //myNetworkStream.Close();
            try
            {
                myTcpClient.Close();
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
    }
}

Upvotes: 1

Views: 2562

Answers (2)

Iridium
Iridium

Reputation: 23721

I think the problem here is that you're only holding a single NetworkStream (myNetworkStream) as such, if a second client connects before the first has sent data, your accept loop will overwrite myNetworkStream with the stream for the 2nd connection. When the first client then sends some data your receiveMessageHandler will call EndRead on the 2nd connection's NetworkStream (which was stored in myNetworkStream when the 2nd client connected), but passing in the asyncResult from the 1st client's read. This causes the exception you indicate. Specifically when I tested it, I got the following message:

Unable to read data from the transport connection: The IAsyncResult object was not returned from the corresponding asynchronous method on this class. Parameter name: asyncResult.

Try making the following changes:

// Create a class to hold details related to a client connection
public class ClientConnection
{
    public ClientConnection(NetworkStream networkStream, byte[] data)
    {
        NetworkStream = networkStream;
        Data = data;
    }

    public NetworkStream NetworkStream { get; private set; }
    public byte[] Data { get; private set; }
}

public void Listen()
{
    myTcpListener.Start();

    while (true)
    {
        //blocks until a client has connected to the server
        myTcpClient = myTcpListener.AcceptTcpClient();

        var client = new ClientConnection(myTcpClient.GetStream(), new byte[myTcpClient.ReceiveBufferSize]);

        // Capture the specific client and pass it to the receive handler
        client.NetworkStream.BeginRead(client.Data, 0, client.Data.Length, r => receiveMessageHandler(r, client), null);
    }
}

public void receiveMessageHandler(IAsyncResult asyncResult, ClientConnection client)
{
    var byteReadCount = client.NetworkStream.EndRead(asyncResult);

    if (byteReadCount < MIN_REQUEST_STRING_SIZE)
    {
        //Could not read form client.
        //Erro - Como tratar? Close()
    }
    else
    {
        if (socketReadCompleteEvent != null)
        {
            socketReadCompleteEvent(this, eventArguments);
        }
    }
}

As others have mentioned, there are additional issues related to your expectations of matched reads/writes between sender and receiver, but this seems to be the cause of the actual issue you're seeing.

Edit:

Here's a server that will receive data, and call a callback method when a full message is received. The callback returns a string, which is then sent back to the client, which calls its own replyCallback with the response data. Only a single request-response is sent per connection (which is rather inefficient, but should serve as a good starting point).

public static class Server
{
    public static void Run(int port, Action<string> callback)
    {
        var listener = new TcpListener(IPAddress.Loopback, port);
        listener.Start();

        while (true)
        {
            using (var client = listener.AcceptTcpClient())
            {
                try
                {
                    var buffer = new byte[2048];
                    using (var memoryStream = new MemoryStream())
                    {
                        using (var stream = client.GetStream())
                        {
                            stream.ReadTimeout = 1000; // 1 second timeout
                            int bytesRead;
                            // Loop until Read returns 0, signalling the socket has been closed
                            while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                            {
                                memoryStream.Write(buffer, 0, bytesRead);
                            }
                        }

                        // Pass the client's message to the callback and use the response as the reply message to the client.
                        var reply = Encoding.UTF8.GetBytes(callback(Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length)));
                        stream.Write(reply, 0, reply.Length);
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine("Error: {0}", e.Message);
                }
            }
        }
    }
}

Here's a small client program that will connect, send its data and wait for a response. Once the response is received, it will pass call replyCallback with the server's response:

public static class Client
{
    public static void Run(string hostname, int port, string dataToSend, Action<string> replyCallback)
    {
        using (var client = new TcpClient(hostname, port))
        {
            using (var stream = client.GetStream())
            {
                var buffer = Encoding.UTF8.GetBytes(dataToSend);
                stream.Write(buffer, 0, buffer.Length);
                // Shutdown the send side of the socket, indicating to the server we're done sending our message
                client.Client.Shutdown(SocketShutdown.Send);
                using (var memoryStream = new MemoryStream())
                {
                    stream.ReadTimeout = 1000; // 1 second timeout
                    int bytesRead;
                    // Loop until Read returns 0, signalling the socket has been closed
                    while ((bytesRead = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        memoryStream.Write(buffer, 0, bytesRead);
                    }
                    replyCallback(Encoding.UTF8.GetString(memoryStream.GetBuffer(), 0, (int)memoryStream.Length));
                }
            }
        }
    }
}

And a small test-harness to tie it all together:

static class Program
{
    static void Main(string[] args)
    {
        var port = 12345;
        ThreadPool.QueueUserWorkItem(o => Server.Run(port, ProcessClientMessage));
        while (true)
        {
            Console.WriteLine("Enter a message to send and hit enter (or a blank line to exit)");
            var data = Console.ReadLine();
            if (string.IsNullOrEmpty(data)) break;
            Client.Run("localhost", port, data, m => Console.WriteLine("Client received server reply: {0}", m));
        }
    }

    private static string ProcessClientMessage(string clientMessage)
    {
        Console.WriteLine("Server received client message: {0}", clientMessage);
        // This callback would ordinarily process the client message, then return a string that will be sent back to the client in response.
        // For now, we'll just return a fixed string value...
        return "This is the server reply...";
    }
}

Upvotes: 3

Nikolai Fetissov
Nikolai Fetissov

Reputation: 84159

The most likely problem is that you are expecting to do exactly three "reads" for the three "writes" that client did.

This is a wrong assumption since TCP socket is a byte stream and does not preserve your application message boundaries. The server might consume those three "messages" sent by the client in one, or two, or seventeen reads.

You need to tell the server somehow where the message ends in the byte stream. Usual choices are fixed length messages, delimiters, message headers that tell length of the payload, self-describing formals like XML, etc.

So you continue reading from the stream until you have a complete message for processing, but at the same time you might have a part of the next message already read into your buffer.

Upvotes: 6

Related Questions