0

I'm building a TCP/IP connection for my application with a warehouse system. The communication goes like this.

  • I send a message to the TCP/IP(Socket) server of the warehouse system.
  • The warehouse system responds with a message the my local TCP/IP server.

So there are no direct response messages. Instead each application as it's own server. Yet I want my application to wait for the response coming from the other server.

So basicly I have the following code.

public string ControllerFunction() {
    startLocalTcpIpServer();
    sendMessage("a message");

    return clientMessage;
}

This is my own server started with the start() function

    public void Start() {
        // Start TcpServer background thread        
        tcpListenerThread = new Thread(new ThreadStart(ListenForIncommingRequests)) {
            IsBackground = true
        };
        tcpListenerThread.Start();
    }

    private void ListenForIncommingRequests() {
        try {
            tcpListener = new TcpListener(IPAddress.Parse(serverIp), port);
            tcpListener.Start();
            byte[] bytes = new byte[1024];
            Console.WriteLine("Server Started");
            while(true) {
                // Get a stream object for reading 
                using(NetworkStream stream = tcpListener.AcceptTcpClient().GetStream()) {
                    int length;
                    // Read incomming stream into byte arrary.                      
                    while((length = stream.Read(bytes, 0, bytes.Length)) != 0) {
                        byte[] incommingData = new byte[length];
                        Array.Copy(bytes, 0, incommingData, 0, length);
                        // Convert byte array to string message.                            
                        string clientMessage = Encoding.ASCII.GetString(incommingData);
                    }
                }
            }
        }
        catch(SocketException socketException) {
            Console.WriteLine("SocketException " + socketException.ToString());
        }
    }

So I want to use the result string clientMessage again as a return for my ControllerFunction. But how do I get the data there in a proper way?

Mark Baijens
  • 13,028
  • 11
  • 47
  • 73
  • You have to send message back. Probably wrap it with some kind of id to identify different messages. – FCin May 23 '18 at 12:51
  • I don't have control about the return messages. Therefore i can't add an id there. Otherwise I would have made a one server system directly. But I can be pretty sure that the response I get is the a response on the last send message. – Mark Baijens May 23 '18 at 12:54
  • So there are 3 servers? A sends to B and B sends reply to C? – Evk May 23 '18 at 12:57
  • Anyway, just setup a TcpListener and wait for a message and return it. – FCin May 23 '18 at 12:58
  • No Program A Sends a message (as a client) to the Server of Program B. Program B sends a message (as a client) to the server of Program A. – Mark Baijens May 23 '18 at 12:58
  • OK, but response comes to another process? Or sender and receiver of reply are in the same process? – Evk May 23 '18 at 12:59
  • Yes the server that listens to incoming request is always running and started with the start of the application. Multiple messages will be send and received while the application is used. But it's within the same windows process if that's what you mean. – Mark Baijens May 23 '18 at 13:02
  • [`Thread`s have been pretty much superseded by `Task` these days](https://stackoverflow.com/a/13429164/542251). you should pretty much never start a Thread these days as it lacks the support that Task has baked in – Liam May 23 '18 at 13:16
  • That said I'm not really sure what your asking here? – Liam May 23 '18 at 13:18
  • Still I don't get why you cannot send request and receive reply on the same tcp connection. It's bidirectional after all. – Evk May 23 '18 at 13:46
  • Because the other program doesn't work like that and I don't have control of that program. I wish I could but i'm stuck with this weird architecture. – Mark Baijens May 23 '18 at 13:48

1 Answers1

1

So what you need is to be able to wait for response coming from another place in your application (local server). Response will be fired there first. Local server should have an event you can subscribe to (OnMessage in my example). This event will forward result message to you.

Synchronization can be handled using TaskCompletionSource. You will create Task that you can use to obtain result synchronously or asynchronously.

Something like this:

public string ControllerFunction()
{           
    return ControllerFunctionTask().Result;
}

public Task<string> ControllerFunctionTask()
{            
    sendMessage("a message");

    var task = new TaskCompletionSource<string>();
    localServer.OnMessage = (message) =>
    {
        task.SetResult(message);
    };

    return task.Task;
}

As stated in comments, synchronous waiting for asynchronous Task may lead to deadlocks. This may happen when caller thread is context thread (UI, ASP). Therefore this should be better approach:

public async Task<string> ControllerFunction()
{           
    return await ControllerFunctionTask();
}

public Task<string> ControllerFunctionTask()
{            
    sendMessage("a message");

    var task = new TaskCompletionSource<string>();
    localServer.OnMessage = (message) =>
    {
        task.SetResult(message);
    };

    return task.Task;
}


OnMessage can be defined this way:

public event Action<string> OnMessage;

Then it will be called right after line where you get clientMessage string:

string clientMessage = Encoding.ASCII.GetString(incommingData);
if (OnMessage != null)
     OnMessage(clientMessage);
Tomas Chabada
  • 2,869
  • 1
  • 17
  • 18
  • 2
    `return ControllerFunctionTask().Result;` is a bad idea, your blocking on an async call. If your using async you need to either use a thread pool thread or make everything `async` – Liam May 23 '18 at 13:12
  • 2
    From the question it is not clear whether caller is context thread or thread pool thread. I know it can cause deadlocks if used in context threads (UI, ASP, ...). I updated my answer – Tomas Chabada May 23 '18 at 13:17
  • Looks like this is very helpfull. Can you tell me what `localServer.OnMessage` would look like? – Mark Baijens May 23 '18 at 13:48
  • 1
    public event Action OnMessage; I will update answer – Tomas Chabada May 23 '18 at 13:50