Click here to Skip to main content
15,881,882 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
how are you? I hope that you are good, I have a question, how can I do to the server consume multi clients? I put all my code

Server

C#
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace Consola
{
    internal class Program
    {
        static void Main(string[] args)
        {
            string ip = "127.0.0.1";
            int port = 8080;
            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
                        offset = 2;
                    ulong msglen = (ulong)(bytes[1] & 0b01111111);

                    if (msglen == 126)
                    {
                        // bytes are reversed because websocket will print them in Big-Endian, whereas
                        // BitConverter will want them arranged in little-endian on windows
                        msglen = BitConverter.ToUInt16(new byte[] { bytes[3], bytes[2] }, 0);
                        offset = 4;
                    }
                    else if (msglen == 127)
                    {
                        // To test the below code, we need to manually buffer larger messages — since the NIC's autobuffering
                        // may be too latency-friendly for this code to run (that is, we may have only some of the bytes in this
                        // websocket frame available through client.Available).
                        msglen = BitConverter.ToUInt64(new byte[] { bytes[9], bytes[8], bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2] }, 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 (ulong i = 0; i < msglen; ++i)
                            decoded[i] = (byte)(bytes[(ulong)offset + i] ^ masks[i % 4]);

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

                    Console.WriteLine();
                }
            }
        }
    }
}


Client

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <link href="Content/bootstrap.min.css" type="text/css" rel="stylesheet" />    
    <script src="Scripts/jquery-3.6.1.min.js" type="text/javascript"></script>    
</head>

<body>
    <div class="card">
        <div class="card-header">
            <h2>Prueba simple de WebSocket</h2>
        </div>
        <div class="card-body">
            <p>
                <textarea cols="60" rows="6" id="cajadetexto"></textarea>
            </p>
            <p>
                <button id="boton" class="btn btn-primary">Enviar</button>
            </p>
            <p>
                <div id="salida"></div>
            </p>
        </div>
    </div>

    <style type="text/css">
        textarea {
            vertical-align: bottom;
        }

        #salida {
            overflow: auto;
        }

        #salida > p {
            overflow-wrap: break-word;
        }

        #salida span {
            color: blue;
        }

        #salida span.error {
            color: red;
        }
    </style>

    <script type="text/javascript">
        $(document).ready(function () {
            const wsUri = "ws://127.0.0.1:8080/";
            const websocket = new WebSocket(wsUri);

            $(document).on("click", "#boton", onClickButton);

            websocket.onopen = (e) => {
                writeToScreen("CONNECTED");
                doSend("WebSocket rocks");
            };

            websocket.onclose = (e) => {
                writeToScreen("DISCONNECTED");
            };

            websocket.onmessage = (e) => {
                writeToScreen(`<span>RESPONSE: ${e.data}</span>`);
            };

            websocket.onerror = (e) => {
                writeToScreen(`ERROR: ${e.data}`);
            };

            function doSend(message) {
                writeToScreen(`SENT: ${message}`);
                websocket.send(message);
            }

            function writeToScreen(message) {
                $("#salida").append("<p>" + message + "</p>");
            }

            function onClickButton() {
                var text = $("#cajadetexto").val();

                text && doSend(text);
                $("#cajadetexto").val("");
                $("#cajadetexto").focus();
            }
        });
    </script>
</body>
</html>


I based my code on this link: https://github.com/mdn/content/blob/main/files/en-us/web/api/websockets_api/writing_websocket_server/index.md?plain=1

Thanks and I wait your answers.

What I have tried:

I tried implement a server that use multi clients without limited size, also I searched many sites about multi-client but I usually found examples of multi clients using web sockets in console application or in .net core when this code need to be developed with .net framework.

I failed in all my tries.
Posted
Updated 2-Jan-23 1:15am
Comments
adriancs 22-Dec-22 1:20am    
I have a portable ASP.NET web server called "CassiniDev", that performs this. It handled request and serves the page. If you're interested, u can go to it's github for the source code. here's the link to the github: https://github.com/adriancs2/CassiniDev
detectivejd 22-Dec-22 9:47am    
I saw your solution but I need to implement the web socket with web page as client part and multi client in a server, I don´t know how use threads to this situation with my current code.

A Socket is a point-to-point connection: one server, one client.
To support one server, multiple clients, you need multiple Sockets: one per client.
The easiest way is to setup each client as a separate thread which creates a socket and stes it to listen. When a client connects to it, you create a new thread with a new Socket for the next one, and so on. WHen the Socket disconnects, the thread is terminated as well.
 
Share this answer
 
Comments
detectivejd 22-Dec-22 8:30am    
Very well, how can i implement thread to multiple client connection?

Because I added a tcpclient list but i don´t know how implement it


string ip = "127.0.0.1";
int port = 8080;
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();
tcpClients.Add(client);
.....
I resolved the trouble, All that I needed it was encode the messages to that the server had hability of notify other client messages, also I had to create a client list to get all connected clients and detect the real time notifications from the frontend.
The frontend ended to relegated except in the backend (server) interaction.

I pass the code finished.

Server

C#
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Linq;

namespace Consola
{
    public class Program
    {
        private static List<Socket> clients = new List<Socket>();
        private static TcpListener tcpListener = new TcpListener(
            IPAddress.Parse("127.0.0.1"), 
            8080
        );
        public static void Main(string[] args)
        {
            tcpListener.Start();
            while(true)
            {
                Socket client = tcpListener.AcceptSocket();
                if (client.Connected)
                {
                    clients.Add(client);
                    Thread nuevoHilo = new Thread(() => Listeners(client));
                    nuevoHilo.Start();
                }                    
            }
        }

        private static void Listeners(Socket client)
        {
            Console.WriteLine("Client:" + client.RemoteEndPoint + " now connected to server.");
            NetworkStream stream = new NetworkStream(client);

            while (true)
            {
                while (!stream.DataAvailable) ;
                while (client.Available < 3) ; // match against "get"

                byte[] bytes = new byte[client.Available];
                stream.Read(bytes, 0, bytes.Length);
                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
                {
                    var text = DecodeMessage(bytes);

                    Console.WriteLine("{0}", text);
                        
                    var otherClients = clients.Where(
                            c => c.RemoteEndPoint != client.RemoteEndPoint
                        ).ToList();

                    if (otherClients.Count > 0)
                    {
                        foreach (var cli in otherClients)
                        {
                            var sendMessage = EncodeMessageToSend(text);
                            cli.Send(sendMessage);
                        }
                    }

                    Console.WriteLine();
                }
            }
        }

        private static string DecodeMessage(byte[] bytes)
        {
            var secondByte = bytes[1];
            var dataLength = secondByte & 127;
            var indexFirstMask = 2;
            if (dataLength == 126)
                indexFirstMask = 4;
            else if (dataLength == 127)
                indexFirstMask = 10;

            var keys = bytes.Skip(indexFirstMask).Take(4);
            var indexFirstDataByte = indexFirstMask + 4;

            var decoded = new byte[bytes.Length - indexFirstDataByte];
            for (int i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
            {
                decoded[j] = (byte)(bytes[i] ^ keys.ElementAt(j % 4));
            }

            return Encoding.UTF8.GetString(decoded, 0, decoded.Length);
        }

        private static byte[] EncodeMessageToSend(string message)
        {
            byte[] response;
            byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
            byte[] frame = new byte[10];

            var indexStartRawData = -1;
            var length = bytesRaw.Length;

            frame[0] = (byte)129;
            if (length <= 125)
            {
                frame[1] = (byte)length;
                indexStartRawData = 2;
            }
            else if (length >= 126 && length <= 65535)
            {
                frame[1] = (byte)126;
                frame[2] = (byte)((length >> 8) & 255);
                frame[3] = (byte)(length & 255);
                indexStartRawData = 4;
            }
            else
            {
                frame[1] = (byte)127;
                frame[2] = (byte)((length >> 56) & 255);
                frame[3] = (byte)((length >> 48) & 255);
                frame[4] = (byte)((length >> 40) & 255);
                frame[5] = (byte)((length >> 32) & 255);
                frame[6] = (byte)((length >> 24) & 255);
                frame[7] = (byte)((length >> 16) & 255);
                frame[8] = (byte)((length >> 8) & 255);
                frame[9] = (byte)(length & 255);

                indexStartRawData = 10;
            }

            response = new byte[indexStartRawData + length];

            int i, reponseIdx = 0;

            //Add the frame bytes to the reponse
            for (i = 0; i < indexStartRawData; i++)
            {
                response[reponseIdx] = frame[i];
                reponseIdx++;
            }

            //Add the data bytes to the response
            for (i = 0; i < length; i++)
            {
                response[reponseIdx] = bytesRaw[i];
                reponseIdx++;
            }

            return response;
        }
    }
}


Client

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <link href="Content/bootstrap.min.css" type="text/css" rel="stylesheet" />
    <script src="Scripts/jquery-3.6.1.min.js" type="text/javascript"></script>
</head>

<body>
    <div class="card">
        <div class="card-header">
            <h2>Prueba multi cliente con WebSocket</h2>
        </div>
        <div class="card-body">
            <p>
                <textarea cols="60" rows="6" id="cajadetexto"></textarea>
            </p>
            <p>
                <button id="boton" class="btn btn-primary">Enviar</button>
            </p>
            <p>
                <div id="salida"></div>
            </p>
        </div>
    </div>

    <style type="text/css">
        textarea {
            vertical-align: bottom;
        }

        #salida {
            overflow: auto;
        }

        #salida > p {
            overflow-wrap: break-word;
        }

        #salida span {
            color: blue;
        }

        #salida span.error {
            color: red;
        }
    </style>

    <script type="text/javascript">
        $(document).ready(function () {
            const wsUri = "ws://127.0.0.1:8080/";
            const websocket = new WebSocket(wsUri);

            $(document).on("click", "#boton", onClickButton);

            websocket.onopen = (e) => {
                writeToScreen("CONNECTED");
            };

            websocket.onclose = (e) => {
                writeToScreen("DISCONNECTED");
            };

            websocket.onmessage = (e) => {
                writeToScreen("<span>RESPONSE: " + e.data + "</span>");
            };

            websocket.onerror = (e) => {
                writeToScreen(`ERROR: ${e.data}`);
            };

            function doSend(message) {
                writeToScreen(`SENT: ${message}`);
                websocket.send(message);
            }

            function writeToScreen(message) {
                $("#salida").append("<p>" + message + "</p>");
            }

            function onClickButton() {
                var text = $("#cajadetexto").val();

                text && doSend(text);
                $("#cajadetexto").val("");
                $("#cajadetexto").focus();
            }
        });
    </script>
</body>
</html>


Thanks for all the support. Greetings.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900