0

Using a .net console app, I've created a websocket server. And a separate app for the client end.

I've used this webpage to create the server side code (https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_server). So my main function looks like this:

    public static void Main()
    {
        string ip = "127.0.0.1";
        int port = 80;
        var server = new TcpListener(IPAddress.Parse(ip), port);

        server.Start();
        Console.WriteLine("Server has started on {0}:{1}, Waiting for a connection...", ip, port);

        TcpClient client = server.AcceptTcpClient();
        Console.WriteLine("A client connected.");

        NetworkStream stream = client.GetStream();

        // enter to an infinite cycle to be able to handle every change in stream
        while (true)
        {
            while (!stream.DataAvailable) ;
            while (client.Available < 3) ; // match against "get"

            byte[] bytes = new byte[client.Available];
            stream.Read(bytes, 0, client.Available);
            string s = Encoding.UTF8.GetString(bytes);

            if (Regex.IsMatch(s, "^GET", RegexOptions.IgnoreCase))
            {
                Console.WriteLine("=====Handshaking from client=====\n{0}", s);

                // 1. Obtain the value of the "Sec-WebSocket-Key" request header without any leading or trailing whitespace
                // 2. Concatenate it with "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (a special GUID specified by RFC 6455)
                // 3. Compute SHA-1 and Base64 hash of the new value
                // 4. Write the hash back as the value of "Sec-WebSocket-Accept" response header in an HTTP response
                string swk = Regex.Match(s, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim();
                string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
                byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create()
                    .ComputeHash(Encoding.UTF8.GetBytes(swka));
                string swkaSha1Base64 = Convert.ToBase64String(swkaSha1);

                // HTTP/1.1 defines the sequence CR LF as the end-of-line marker
                byte[] response = Encoding.UTF8.GetBytes(
                    "HTTP/1.1 101 Switching Protocols\r\n" +
                    "Connection: Upgrade\r\n" +
                    "Upgrade: websocket\r\n" +
                    "Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n");

                stream.Write(response, 0, response.Length);
            }
            else
            {
                bool fin = (bytes[0] & 0b10000000) != 0,
                    mask = (bytes[1] & 0b10000000) !=
                           0; // must be true, "All messages from the client to the server have this bit set"

                int opcode = bytes[0] & 0b00001111, // expecting 1 - text message
                    msglen = bytes[1] - 128, // & 0111 1111
                    offset = 2;

                if (msglen == 126)
                {
                    // was ToUInt16(bytes, offset) but the result is incorrect
                    msglen = BitConverter.ToUInt16(new byte[] {bytes[3], bytes[2]}, 0);
                    offset = 4;
                }
                else if (msglen == 127)
                {
                    Console.WriteLine("TODO: msglen == 127, needs qword to store msglen");
                    // i don't really know the byte order, please edit this
                    // msglen = BitConverter.ToUInt64(new byte[] { bytes[5], bytes[4], bytes[3], bytes[2], bytes[9], bytes[8], bytes[7], bytes[6] }, 0);
                    // offset = 10;
                }

                if (msglen == 0)
                    Console.WriteLine("msglen == 0");
                else if (mask)
                {
                    byte[] decoded = new byte[msglen];
                    byte[] masks = new byte[4]
                        {bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]};
                    offset += 4;

                    for (int i = 0; i < msglen; ++i)
                        decoded[i] = (byte) (bytes[offset + i] ^ masks[i % 4]);

                    string text = Encoding.UTF8.GetString(decoded);
                    Console.WriteLine("{0}", text);
                }
                else
                    Console.WriteLine("mask bit not set");

                Console.WriteLine();
            }
        }
    }

My client side console app uses the Websocket-Sharp library (https://github.com/sta/websocket-sharp/). The main logic for it is:

        using (var ws = new WebSocketSharp.WebSocket("ws://localhost"))
        {
            ws.OnMessage += (sender, e) => Console.WriteLine("Server says: " + e.Data);

            ws.Connect();

            for (var i = 0; i < 1000; i++)
            {
                ws.Send($"hello {i}");
                Console.WriteLine($"hello {i}");
                //Thread.Sleep(2000);
            }
        }

The client successfully sends each of the 1000 messages but on the server side, it does not receive all the messages. There are huge gaps in the messages (i.e. they are not out of order, messages are missing).

Does anyone know why this would happen? Why are messages disappearing?

millie
  • 2,642
  • 10
  • 39
  • 58
  • Slightly confused `while (client.Available < 3)` surely you want >3 ? – BugFinder Oct 30 '19 at 11:53
  • Maybe the messages are delivered two at a time? Have you inspected decoded `text` for all cases so that it's only one message each time it is called? Also, since you're using two console apps here, have you considered [gRPC](https://grpc.io) ([QuickStart](https://www.grpc.io/docs/quickstart/csharp/)) – fredrik Oct 30 '19 at 11:53
  • You are also not saving the received bytes in case it is not the entire message. So if you read more than 3 chars, but less than the message length that message will be thrown. You need to buffer the recieved data until a complete packet has been receieved. – fredrik Oct 30 '19 at 12:00
  • You should use a sniffer to see what you are really seeing. There are a few issues to consider 1) Is server continuously sending data or only sending data when commanded. You may need to flush first data which may be a partial message 2) You are checking in Regex for the ^ (beginning of line) which may not work if the return character is not 0x0C, 0x0A. The '\n' in windows. A Linux machine may be sending only the 0xoA. 3) You may not be getting the entire receive message in one chunk. So code should be able to handle multiple chunks. – jdweng Oct 30 '19 at 12:02
  • There are some working WebSocket server implementations out there that integrate nicely with C# and provide flawless WebSocket connectivity. Fleck is one (although it has some minor issues). There are several others – Martin Oct 30 '19 at 12:02
  • @jdweng From section 2.2 of the [HTTP specification](https://tools.ietf.org/html/rfc2616): `HTTP/1.1 defines the sequence CR LF as the end-of-line marker for all protocol elements except the entity-body`. – fredrik Oct 30 '19 at 12:08
  • I suspect the sending messages are being combined together so on the receive side you are getting multiple messages in one chunk. You code is looking for one GET when the message can contain multiple GETS so you receive count appears to be less than the send count. – jdweng Oct 30 '19 at 12:37

0 Answers0