0

I have 2 seperate program instances running on different computers. They are connected to each other other via a TcpClient (well 2 connections, an ingoing and outgoing, I am aware a TcpClient allows messages both ways, but for code simplicity it is easier to establish 2 connections).

Both are sending a lot of messages, and will be sending more data then there is in the buffer. Both threads run through a cycle of reading then writing.

ReceiveIncomingMessagesFromTcpClient()
ParseAndDeliverIncomingMessagesInternaly()
CollectNewOutgoingMessagesInternaly()
DeliverOutgoingMessagesViaTcpClient()

My program seems to run fine, and then stops sending messages, so I fear that the reason for this is that both are trying to Write Outgoing messages to their respective streams, both streams are full, and as nothing is there to empty the streams I have a deadlocked program.

If that is the case verify if there is room in the buffer before I start sending the message, and only send what is room for? (to avoid blocking a send, and due to this enter the deadlock?)


I have this function that sends the data, it can be called a lot of times in a row. The buffer size of the connection is the default 8kb.

So if this action gets repeated enough times in a row the 8kb buffer will get filled, and the stream.Write() is a blocking statement, until the other side pulls the data.

As the other side is a perfect mirror, if it has pulled a lot of data to send, both sides can be trying to send at the same time, causing the deadlock.

private void DeliverOutgoingMessagesViaTcpClient()
{
    for(int i = 0; i < message.Count;i++)
    {
        //send request
        Byte[] data = System.Text.Encoding.ASCII.GetBytes(message[i]);
        NetworkStream stream = connection.GetStream();
        stream.Write(data, 0, data.Length);
    }
}
Taoh
  • 331
  • 2
  • 12
  • Can you add some code of the receive and transmit functions. It seems odd that a transmit would block indefinitely for any reason. Anyway, how would the transmitting side know that the receiving side hasn't emptied its buffer? It doesn't seem likely – o_weisman Dec 30 '13 at 08:16
  • @o_weisman I add the function that sends the List message, which is the internal buffer of outstanding messages (can be long). – Taoh Dec 30 '13 at 11:20
  • Is it possible that both sides are waiting to receive data, resulting in a dead-lock type situation? You could try putting a time-out in the receive method, so if no bytes are received for a set time the program will move on to the send phase. See http://msdn.microsoft.com/en-us/library/system.io.stream.readtimeout(v=vs.110).aspx – David Kirkland Dec 30 '13 at 11:54
  • @DavidKirkland Problem here is not the reading, but the writing, but if I recall correct there is a similar setting for the Write timeout. As I would like to keep the "main loop" I have tight and non blocking, I will look into moving the write function (and possible read as well) into separate single purpose threads. – Taoh Dec 30 '13 at 12:19
  • 1
    @Taoh Although it will complicate your program a little, I think multi-threading is the way to go. I'm not certain this will resolve the issue, but it won't hurt (if you're very careful with threading). – David Kirkland Dec 30 '13 at 12:27
  • @DavidKirkland Is the solution I picked. I decided to make a generic class for it. Might even separate out the stream reader as well in a similar setup. (to avoid blocking the main loop). – Taoh Dec 30 '13 at 13:51

1 Answers1

0

Based on the feedback, it seems my issue is that I risk blocking when two communicating programs both have their sending messages blocked. So I moved the sending function to a seperate class now.

Following is the class I decided to use. It should be started up once ready for use, and can be called with a target TcpClient and a message, the call can be done either Blocking or Nonblocking (Nonblocking returns true is message is accepted)

    public class TcpSender
    {
        Mutex moveToInternal = new Mutex();
        Mutex startOnce = new Mutex();

        List<Message> internalMessages = new List<Message>();
        List<Message> externalMessages = new List<Message>();

        Thread mainLoop;

        String status = "stopped";

        //returns true if started.
        public Boolean Start()
        {
            if (startOnce.WaitOne(0))
            {
                status = "started";
                mainLoop = new Thread(new ThreadStart(InternalMainLoop));
                mainLoop.IsBackground = true;
                mainLoop.Start();
                return true;
            }
            return false;
        }

        private void InternalMainLoop()
        {

                while (status == "started")
                {
                    Boolean didSomething = false;

                    //pickup new messages and move to internal buffer
                    if (externalMessages.Count > 0)
                    {
                        didSomething = true;
                        if (moveToInternal.WaitOne())
                        {
                            internalMessages.AddRange(externalMessages);
                            externalMessages.Clear();
                            moveToInternal.ReleaseMutex();
                        }
                    }

                    //deliver messages
                    for (int i = 0; i < internalMessages.Count; i++)
                    {
                        //send request
                        Byte[] data = System.Text.Encoding.ASCII.GetBytes(internalMessages[i].GetMessage());
                        NetworkStream stream = internalMessages[i].GetClient().GetStream();
                        stream.Write(data, 0, data.Length);
                    }
                    internalMessages.Clear();

                    if (!didSomething) Thread.Sleep(1); //Nothing to receive or send, maybe tomorr... next milisec there is something to do
                }
                startOnce.ReleaseMutex();
        }

        public void Stop()
        {
            status = "stopped";
        }

        public Boolean Status()
        {
            return (status == "started");
        }

        //return true if message was accepted
        public Boolean addMessageNonBlocking(TcpClient client, String message)
        {
            Boolean gotMutex = false;
            if(gotMutex = moveToInternal.WaitOne(0));
            {
                Message tmpMessage = new Message(client, message);
                externalMessages.Add(tmpMessage);
                moveToInternal.ReleaseMutex();
            }
            return gotMutex;
        }

        public void addMessageBlocking(TcpClient client, String message)
        {
            //loop until message is delivered
            while (!addMessageNonBlocking(client, message))
            {
                Thread.Sleep(1); //could skip this, but no need for busy waiting
            }
        }

        private class Message
        {
            TcpClient client;
            String message;

            public Message(TcpClient client, String message)
            {
                this.client = client;
                this.message = message;
            }

            public TcpClient GetClient()
            {
                return client;
            }

            public String GetMessage()
            {
                return message;
            }
        }
    }

Test Code for verifying if TcpClient is blocking

Add this code anywhere it can be run. testClient must be a TcpClient that is connected to anywhere.

int i = 0;
String testmsg = "This is a 25 char test!!!";
Byte[] data = System.Text.Encoding.ASCII.GetBytes(testmsg);
while (true)
{
    i++;
    testClient.GetStream().Write(data, 0, data.Length);
    if (i % 10 == 0) Console.WriteLine("Cycle Count: " + i);
}
Taoh
  • 331
  • 2
  • 12
  • 1
    I'm pretty sure that your theory about why the programs are blocking is not correct. The write method of NetworkStream doesn't block based on the other side's read of the input buffer but rather on the transport system's ability to transfer the message (i.e local transmit buffer and network availability) as stated here: http://stackoverflow.com/questions/7667254/what-conditions-cause-networkstream-write-to-block – o_weisman Dec 31 '13 at 07:38
  • @o_weisman Thanks for the update, not sure how much I can read from it. The non blocking is refering to the WinSocket, not the TcpStream writing. And I have no clue if the handoff from the program to the TcpStream is handled if the buffer is full (and could not find reference in the link to it either, might have missed it). So since the documentation states http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream%28v=vs.110%29.aspx (...The NetworkStream class provides methods for sending and receiving data over Stream sockets in blocking mode...) I have to work from that. – Taoh Jan 01 '14 at 00:12
  • @o_weisman (continued). All I could see was that some data did not get through, and new requests did not get processed. But that might have been another issue. Currently I am rewriting this from scratch, and tested by sending a huge message (several mb of data) with this without losing any data. I might just have reinvented the wheel but is learning stuff doing it :) Made a similar class to handle the listener and reader (1 reader per stream, considering adding the option to signup multiple streams to 1 reader for later). – Taoh Jan 01 '14 at 00:16
  • @o_weisman I did a test, setup a loop that kept sending a message to a NetworkStream that was never read from. It would WriteLine every 10th iteration to show how far it was, and after a while it stopped writing (between 840-850th loops with a 25 character message). To me this clearly indicate the TcpStream does block once the receive buffer is filled. – Taoh Jan 01 '14 at 12:20
  • What do you mean not read from? Is there an application that opens a socket on the other end of the TCP stream? Did you try the same write with a timeout to see if it continues? Btw, if what you claim is true, shouldn't this always block on the same iteration when the buffer is full? As far as I know TcpStream is based on WinSocket and to the best of my knowledge, there is no mechanism in that library that blocks write operations based on responses from the receiving side. Keep in mind, that there isn't really a stream object. It is all based on messaging. – o_weisman Jan 01 '14 at 13:08
  • @o_weisman Sorry I can not answer that question. I have added the test code I used above to determine there was a blocking effect. I did not try to read after the block. To the best of my knowledge TCP acknowledge data was received. As the buffer is full that message never gets acknowledged. How this is handled by the code in C# appears to be blocking it (as the documentation says it will). Most likely transmitting over until received. I believe there were controls to modify this behavioer, but I am to much of a rookie to have messed with that, just saw there were options for it. – Taoh Jan 01 '14 at 18:03