0

Goal

Send and receive message from TCP socket on two different threads in a UI program.

Problem

I can't connect to a socket and send/receive data ONLY when I'm in Xamarin.Forms and WinForms. So this is some kind of a threading issue.

When I try connecting it just times out here so there is no error or nothing.

What I have been trying

I have been working heavily with Matt Davis solution here on StackOverflow.

I created a test code (TcpClientSendReceive) at GitHub with all the code needed.

My code

Its all on GitHub in this repository. I cant fit it all in here. But here is the connection code that does not seem to work with e.g Xamarin.Forms see that code here.

If I try to connect to my console server from a UI I can't connect (witch means that I can´t either send/receive)

Does the baseline code work?

Yes it does. At least when I run the two Client/Server programs it sends data between each other.

What could be the problem?

Definitely how threading is setup and how it interacts with the UI. I'm just lacking enough threading experience to be able to figure this out. I hope some of you do! :-)

EDIT:

BOUNTY INFORMATION

I created a more detailed bounty information page on GitHub so it would be totally clear what I needed.

I added LOTS of images and links to code. It should be very easy to get the code and just try it out. Please try it out. I really don´t think this is a big issue for somebody lower level than me.

Community
  • 1
  • 1
Sturla
  • 3,446
  • 3
  • 39
  • 56
  • Can you describe the error etc. "I can't connect" could mean a loada things! – noelicus Mar 04 '19 at 13:00
  • Yes sorry. I updated the question. It just times out. That's why I added the timeout part. I have tried all kinds of versions of this code. But it works with the server/client console apps but just not with the UI apps. – Sturla Mar 04 '19 at 13:09
  • What if one side is console and the other is xamarin? – noelicus Mar 06 '19 at 15:34
  • @noelicus that is my main problem and setup that doesn't seem to work. I run up the Android emulator and then the ServiceConsole. Take a look at the details in [this issue](https://github.com/sturlath/TcpClientSendReceive/issues/3) – Sturla Mar 08 '19 at 22:11

3 Answers3

0

how about this?

public class HandleClient
    {
        private TcpClient clientSocket;
        private static int bufferLength = 1024;
        private byte[] readBuffer = new byte[bufferLength];

        public void StartClient(TcpClient inClientSocket)
        {
            this.clientSocket = inClientSocket;

            var ctThread = new Thread(Chat);
            ctThread.Start();
        }

        private void Chat()
        {
            var reader = clientSocket.GetStream();

            try
            {
                while (true)
                {
                    var bytesRead = reader.Read(readBuffer, 0, bufferLength);

                    using (var memoryStream = new MemoryStream())
                    {
                        memoryStream.Write(readBuffer, 0, bytesRead);
                        var message = System.Text.Encoding.ASCII.GetString(memoryStream.ToArray());

                        Log.Debug("Server got this message from client: {message}", message);

                        foreach (TcpClient client in Program.GetClients())
                        {
                            var writer = new BinaryWriter(client.GetStream());
                            writer.Write($"Server got your message '{message}'");
                        }
                    }
                }
            }
            catch (EndOfStreamException)
            {
                Log.Error($"client disconnecting: {clientSocket.Client.RemoteEndPoint}");
                clientSocket.Client.Shutdown(SocketShutdown.Both);
            }
            catch (IOException e)
            {
                Log.Error($"IOException reading from {clientSocket.Client.RemoteEndPoint}: {e.Message}");
                Console.WriteLine();
            }
            catch (ObjectDisposedException e)
            {
                Log.Error(e, "Error (ObjectDisposedException) receiving from server");
            }
            catch (Exception e)
            {
                Log.Error(e, "Error receiving from server");
            }

            clientSocket.Close();
            Program.RemoveClient(clientSocket);
            Log.Debug("{count} clients connected", Program.GetClientCount());
        }
    }

Winform

android

communication

if your winform can not receive message. you must change OnClient_MainDataReceived method to

this.Invoke((MethodInvoker)delegate ()
            {
                txbResponseFromServer.AppendText(Environment.NewLine);
                txbResponseFromServer.AppendText(e.Data);
            });
LimGeomJe
  • 215
  • 1
  • 11
  • This looks promising. Do you mind doing a PR on GitHub with the full implementation so I can try it out? – Sturla Mar 09 '19 at 10:14
  • i changed just your HandleClient. i checked packet with wireshark. now your client application sent packet. but your binaryreader can not receive packet. i don't know why.... i found many website. but i couldn't find. so i changed your listener. my english is so bad. so this explain is my limit.... – LimGeomJe Mar 09 '19 at 11:36
  • Ahh I see. Thank you so much! Now the Service at least receives the data that the Client sends so the problem has become why the client isn't receiving the response from the Service. We are half way there! p.s I updated the GitHub code with your code thanks. – Sturla Mar 09 '19 at 13:09
  • your client can not get receive message? – LimGeomJe Mar 09 '19 at 13:11
0

The problem is clients and a server communicate inconsistently. Server uses BinaryReader and BinaryWriter and clients write/read bytes to a "raw" stream as is. The difference is that BinaryWriter writes not only string bytes but also its length bytes before the string itself and BinaryReader doesn't read all currently available bytes from network and converts them to a string but it reads the length first and then hanldes reading the exact amount of byte needed to get a full string.

So the easiest would be using BinaryWriter and BinaryReader everywhere since they hanlde reading complex data (string) bytes, encoding and decoding them for you.

Although implementing you own communication protocol and working directly with NetworkStream methods for reading/writing bytes gives you more flexibility it requires an extra effort to make it work correctly. For example there is a single method for reading multiple bytes from network

int Read(byte[] buffer, int offset, int size)

When server sends a string client doesn't know how many bytes it should read. It can create large buffer that can hold some reasonable amount of bytes. But if server sends two strings one by one client could read them both into a buffer at once and data could be possibly corrupted already. At this point you need to implement your communication protocol for sending complete messages via network. Another caveat for the Read method is that it may read not all currently available data from network at once and size value doesn't guarantee amount of read bytes. For example you know that server sends client n bytes and client reads them

stream.Read(buffer, 0, n)

Is is possible that client will read only some part of that bytes in one Read call and the remaining part will still be on network stream available to be read. You should consider that and handle this situation by reading bytes in a loop while need amount has been read.

Alexander
  • 9,104
  • 1
  • 17
  • 41
0

I haven't tested, but I believe I see the deadlock in this method: https://github.com/sturlath/TcpClientSendReceive/blob/a15c936889412e5a4ac249df2b291db817d40307/TcpClientLib/Client.cs#L37

This might be enough to fix it:

await Task.Run(() => _client.GetStream()).ConfigureAwait(false);

Looks like the client & server are trying to connect in the ui synchronization context. That's a single thread so it's not going to work. If it still locks up you may have some other places that need to be fixed as well.

Timothy Jannace
  • 1,401
  • 12
  • 18