0

I have been trying to setup a client-server application using StreamSockets in c#. I am able to initially connect to the server (ConnectAsync) and following to write and read the stream. If the client sends another stream to the server using the method WriteToServer the event on the server side is not being triggered (SocketListener_ConnectionReceived). I'm sending a xmlSerialized object "Message" to the server.

While debugging I don't receive any errors. On the client side though the after having used "WriteToServer" and moving forward to "ReadFromServer" the code gets obviously stuck in the line below since the server doesn't reply

            int bufferLength = inStream.ReadByte();

I hope that someone call help. I am honestly not sure what the issues is because the "Write"-method is being used the same way both times the client attempts to write to the server.

Client is a Windows 10 computer and Server a Raspberry pi running Windows 10 IoT.

The class inside the client application which handles the connection looks like this.

StreamSocket socket;
    HostName remoteHostName, localHostName;
    string serviceAddress = "1337";
    EndpointPair endPointPair;
    Boolean isConnected;
    Socket test;


    public Connection()
    {
        remoteHostName = new HostName("172.16.20.202"); // might have to change based on pi's ip address
        //remoteHostName = new HostName("172.16.20.4");
        localHostName = new HostName(getLocalIpAddress());

        endPointPair = new EndpointPair(localHostName, serviceAddress, remoteHostName, serviceAddress);

        socket = new StreamSocket();
        socket.Control.NoDelay = true;
        socket.Control.QualityOfService = SocketQualityOfService.LowLatency;


    }

    private string getLocalIpAddress()
    {
        var icp = NetworkInformation.GetInternetConnectionProfile();

        if (icp?.NetworkAdapter == null) return null;
        var hostname =
            NetworkInformation.GetHostNames()
                .SingleOrDefault(
                    hn =>
                        hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
                        == icp.NetworkAdapter.NetworkAdapterId);

        // the ip address
        return hostname?.CanonicalName;
    }

    public async Task StartConnection()
    {
        try
        {
            if (isConnected)
            {
                await socket.CancelIOAsync();
                socket.Dispose();
                socket = null;
                isConnected = false;
            }
            await socket.ConnectAsync(endPointPair);
            isConnected = true;
        }
        catch (Exception exc)
        {
            if (Windows.Networking.Sockets.SocketError.GetStatus(exc.HResult) == SocketErrorStatus.Unknown)
            {
                throw;
            }
            Debug.WriteLine("Connect failed with error: " + exc.Message);

            socket.Dispose();
            isConnected = false;
            socket = null;
            //return null;
        }
    }

    public async Task WriteToServer(Message msg)
    {
        try
        {
            using (DataWriter writer = new DataWriter(socket.OutputStream))
            {
                writer.WriteBytes(serialize(msg));
                await writer.StoreAsync();
                writer.DetachStream();
                writer.Dispose();
            }
        }
        catch (Exception exc)
        {
            Debug.WriteLine("Write failed with error: " + exc.Message);
        }

    }
    public async Task<Library.Message> ReadFromServer()
    {
        try
        {
            Stream inStream = socket.InputStream.AsStreamForRead();
            int bufferLength = inStream.ReadByte();
            byte[] serializedMessage = new byte[bufferLength];

            await inStream.ReadAsync(serializedMessage, 0, bufferLength);
            await inStream.FlushAsync();

            Library.Message incomingMessage;
            using (var stream = new MemoryStream(serializedMessage))
            {
                var serializer = new XmlSerializer(typeof(Library.Message));
                incomingMessage = (Library.Message)serializer.Deserialize(stream);
            }
            return incomingMessage;
        }
        catch (Exception exc)
        {
            Debug.WriteLine("Read failed with error: " + exc.Message);
            return null;
        }
    }

    private byte[] serialize(Message msg)
    {
        byte[] serializedMessage, returnArray;
        var serializer = new XmlSerializer(typeof(Library.Message));

        using (var stream = new MemoryStream())
        {
            serializer.Serialize(stream, msg);
            serializedMessage = stream.ToArray();
            stream.Dispose();
        }

        int bufferLength = serializedMessage.Length;

        returnArray = new byte[serializedMessage.Length + 1];
        serializedMessage.CopyTo(returnArray, 1);
        returnArray[0] = (byte)bufferLength;

        Debug.WriteLine("ClientReturnArrayLength: " + returnArray.Length);
        Debug.WriteLine("ClientFirstByte: " + returnArray[0]);
        return returnArray;
    }

The server side looks like this

    public Task DispatcherPriority { get; private set; }

    public MainPage()
    {
        this.InitializeComponent();
        dispatcher = CoreWindow.GetForCurrentThread().Dispatcher;
        hostName = new HostName(getLocalIpAddress());
        clients = new List<Client>();
    }

    /// <summary>
    /// Gets the ip address of the host
    /// </summary>
    /// <returns></returns>
    private string getLocalIpAddress()
    {
        var icp = NetworkInformation.GetInternetConnectionProfile();

        if (icp?.NetworkAdapter == null) return null;
        var hostname =
            NetworkInformation.GetHostNames()
                .SingleOrDefault(
                    hn =>
                        hn.IPInformation?.NetworkAdapter != null && hn.IPInformation.NetworkAdapter.NetworkAdapterId
                        == icp.NetworkAdapter.NetworkAdapterId);

        // the ip address
        return hostname?.CanonicalName;
    }


    async void setupSocketListener()
    {

        if (socketListener != null)
        {
            await socketListener.CancelIOAsync();
            socketListener.Dispose();
            socketListener = null;
        }
        socketListener = new StreamSocketListener();
        socketListener.Control.QualityOfService = SocketQualityOfService.LowLatency;
        socketListener.ConnectionReceived += SocketListener_ConnectionReceived;
        await socketListener.BindServiceNameAsync("1337");
        listBox.Items.Add("server started.");

        clients.Clear();
    }

    private async void SocketListener_ConnectionReceived(StreamSocketListener sender, StreamSocketListenerConnectionReceivedEventArgs args)
    {
        HostName ip = args.Socket.Information.RemoteAddress;
        string port = args.Socket.Information.RemotePort;

        try
        {
            Stream inStream = args.Socket.InputStream.AsStreamForRead();
            int bufferLength = inStream.ReadByte();
            byte[] serializedMessage = new byte[bufferLength];

            await inStream.ReadAsync(serializedMessage, 0, bufferLength);
            await inStream.FlushAsync();

            Message incomingMessage;
            using (var stream = new MemoryStream(serializedMessage))
            {
                var serializer = new XmlSerializer(typeof(Message));
                incomingMessage = (Message)serializer.Deserialize(stream);
            }

            /// <summary>
            /// 1 = Connected
            /// 2 = SentNote
            /// 3 = Login
            /// </summary>
            switch (incomingMessage.Mode)
            {
                case 1:
                    onClientConnect(ip, port, incomingMessage.Username, args.Socket);
                    break;
                case 2:
                    onNoteReceived(incomingMessage);
                    break;
                case 3:
                    //handle login
                    break;
            }
        }
        catch (Exception msg)
        {
            Debug.WriteLine(msg);
        }

    }

    private async void onNoteReceived(Message msg)
    {
        foreach (var client in clients)
        {
            //if (client.Username != msg.Username)
            //{
            using (DataWriter writer = new DataWriter(client.Socket.OutputStream))
            {
                writer.WriteBytes(serialize(msg));
                await writer.StoreAsync();
                writer.DetachStream();
                writer.Dispose();
            }
            //}
        }
    }

    private void buttonStartServer_Click(object sender, RoutedEventArgs e)
    {
        setupSocketListener();
    }


    private async void notifyClients(string username)
    {
        Message msg = new Message();
        msg.Username = username;

        foreach (var client in clients)
        {
            //if (client.Username != msg.Username)
            //{
            using (DataWriter writer = new DataWriter(client.Socket.OutputStream))
            {
                writer.WriteBytes(serialize(msg));
                await writer.StoreAsync();
                writer.DetachStream();
                writer.Dispose();
            }
            //}
        }
    }

    private async void onClientConnect(HostName ip, string port, string username, StreamSocket socket)
    {
        clients.Add(new Client(ip, port, username, socket));

        await dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
        {
            listBox.Items.Add("User: " + username + " on IP " + ip + " is connected.");
        });

        notifyClients(username);
    }


    private byte[] serialize(Message msg)
    {
        byte[] serializedMessage, returnArray;
        var serializer = new XmlSerializer(typeof(Message));

        using (var stream = new MemoryStream())
        {
            serializer.Serialize(stream, msg);
            serializedMessage = stream.ToArray();
            stream.Dispose();
        }

        int bufferLength = serializedMessage.Length;

        returnArray = new byte[serializedMessage.Length + 1];
        serializedMessage.CopyTo(returnArray, 1);
        returnArray[0] = (byte)bufferLength;

        Debug.WriteLine("ClientReturnArrayLength: " + returnArray.Length);
        Debug.WriteLine("ClientFirstByte: " + returnArray[0]);
        return returnArray;
    }


}

This is the message class which I am using to send over the network.

    public string Username { get; set; }

    /// <summary>
    /// 1 = startConnection
    /// 2 = SendNote
    /// 3 = Login
    /// </summary>
    public int Mode { get; set; }
    public Piano PianoNote { get; set; }
    public string Instrument { get; set; }

    public Message()
    {

    }

}

public enum Piano { a1, a1s, b1, c1 };

**Edit: **

Message framing:

byte[] prefix = BitConverter.GetBytes(serializedMessage.Length);
returnArray = new byte[serializedMessage.Length + prefix.Length];
prefix.CopyTo(returnArray, 0);
serializedMessage.CopyTo(returnArray, prefix.Length);

Reading the message:

byte[] prefix = new byte[4];
await inStream.ReadAsync(prefix, 0, 4);
int bufferLength = BitConverter.ToInt32(prefix, 0);

Half-Open: Instead of reading synchronous I switched to async reading the first 4 bytes as seen above.

1 Answers1

0

I am able to initially connect to the server (ConnectAsync) and following to write and read the stream. If the client sends another stream to the server using the method WriteToServer the event on the server side is not being triggered (SocketListener_ConnectionReceived).

Take a good look at those names. You're calling ConnectAsync once and then WriteToServer twice, and only seeing SocketListener_ConnectionReceived once. There's only once connection, so yes, ConnectionReceived would only trigger once.

That's just scratching the surface, though. There's a few other very subtle issues wrong with this code.

One that sticks out is a lack of proper message framing, as I describe on my blog. You're using a single-byte length prefix, so on the wire it's OK (though limited to 256 bytes, which doesn't go far with XML). But the reading of the messages is incorrect; in particular, Stream.ReadAsync may read between 1 and bufferLength bytes, or it may return 0.

Another problem is that it's subject to the half-open problem, as I describe on my blog. In particular, int bufferLength = inStream.ReadByte(); will block indefinitely in a half-open situation. You should only use asynchronous methods for all network streams, and periodically write while waiting for data to arrive.

In summary, I strongly recommend that you use self-hosted SignalR instead of raw sockets. Raw socket programming is extremely difficult to do correctly, especially because incorrect code often happens to work correctly in a local environment.

Community
  • 1
  • 1
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Thank you Stephen for your detailed answer. Also your blog has a lot of useful information. Unfortunately, SignalR is not an option for me. Is it intended that the `SocketListener_ConnectionReceived` is only being called once? I mean, now that you are pointing it our it totally makes sense to me. I was thinking that the socket would stay "open" meaning once the connection is established you can read and write as needed and afterwards dispose the socket. How would you go about this? The `socketlistener` doesn't seem to have an event besides `ConnectionReceived` for incoming data. – Jan-Niklas Schneider May 13 '16 at 08:38
  • As for the message framing and half-open problem, I realize what the issue is there and can fix that. – Jan-Niklas Schneider May 13 '16 at 09:03
  • @Jan-NiklasSchneider: Sure, the socket can stay open, but you'll only get one `ConnectionReceived` event - when the connection is received. You won't get another event when data comes in; you just have to read from the stream all the time. – Stephen Cleary May 13 '16 at 12:39
  • @Jan-NiklasSchneider: Did you follow the link on self-hosted SignalR? It's specifically about self-hosting on Raspberries. – Stephen Cleary May 13 '16 at 12:40
  • SignalR is not an option since I am running Windows IoT on the Pi. Sorry for the late reply btw. I have changed my code in a way that I am continuously reading the stream on the server as well as the client with a `while(true)` I'll post an update of my code. Thank you for your help! – Jan-Niklas Schneider May 20 '16 at 09:42