WebSocket 통신 구현 C++/C#

목차

0 미션 개요

1 서버와의 다양한 지속적인 통신 방법 비교

2 C++ 참조 링크:

3 C# 구현

서버 코드:

클라이언트 코드:

4개의 코드 통합

5 테스트 과정

1> mqtt 서버 시작

 2> WebSocket_Server 시작

 3> WebSocket_Client 시작

 4> 서버정보

0 미션 개요

아키텍처 형태:

1> Hololens*2 PC는 mqtt를 사용하여 세 가지 사이에서 통신합니다.

2> PC에서 개발하고, 2개의 홀로렌즈로부터 mqtt 메시지를 수신하고, 이를 웹소켓 서버(휴대용 단말기)로 전달합니다.

3> PC의 MQTT+WEBSOCKET(임시 직접 전달).

목표: mqtt의 메시지가 있다고 가정하고 이 메시지를 WebSocket 서버로 전달합니다.

1 서버와의 다양한 지속적인 통신 방법 비교

>>>>>>>>>>>> 전통적인 여론조사 긴 폴링 서버 전송 이벤트 웹소켓
브라우저 지원 거의 모든 최신 브라우저 거의 모든 최신 브라우저 파이어폭스 6+ 크롬 6+ 사파리 5+ 오페라 10.1+ IE 10+ Edge Firefox 4+ Chrome 4+ Safari 5+ Opera 11.5+
서버 부하 더 적은 CPU 리소스, 더 많은 메모리 리소스 및 대역폭 리소스 기존 폴링과 유사하지만 더 적은 대역폭을 사용합니다. 각 요청이 전송된 후 서버 연결을 끊을 필요가 없다는 점을 제외하면 긴 폴링과 유사합니다. 루프(장기 폴링)에서 기다릴 필요가 없으며 CPU 및 메모리 리소스는 클라이언트 수가 아닌 클라이언트 이벤트 수로 측정됩니다. 네 가지 방법 중 성능이 가장 좋습니다.
클라이언트 로드 더 많은 메모리 리소스와 요청 수를 차지합니다. 기존 여론조사와 유사합니다. 브라우저에서 기본적으로 구현되며 리소스를 거의 차지하지 않습니다. 동일한 서버 전송 이벤트입니다.
지연 실시간이 아니며 대기 시간은 요청 간격에 따라 다릅니다. 기존 여론조사와 동일합니다. 비실시간, 기본 지연은 3초이며 지연은 사용자 정의할 수 있습니다. 실시간.
구현 복잡성 매우 간단합니다. 서버 협력이 필요하며 클라이언트 구현은 매우 간단합니다. 서버 협력이 필요하며 클라이언트 구현은 처음 두 가지보다 훨씬 간단합니다. 소켓 프로그램 구현 및 추가 포트가 필요하며 클라이언트 구현이 간단합니다.

웹소켓에 대한 자세한 설명

http - WebSocket에 대한 자세한 설명 - 까다롭지 않은 프로그래머 - SegmentFault SiFou

2 C++ 참조 링크:

zaphoyd/websocketpp: C++ 웹소켓 클라이언트/서버 라이브러리(github.com)

컴퓨터에서 두 개의 cpp 파일인 example/echo_client 및 echo_server를 실행해 보십시오. 다른 파일을 시도했지만 실행에 실패했습니다.

결과: 연결에 성공했지만 사용자 정의된 메시지를 보낼 수 없습니다.

서버 스크린샷:

클라이언트 스크린샷:

다른 블로그 포스팅에서는 mqtt 정보를 받기 위해 C#을 사용했는데, 개발을 위해 C#으로 바꾸는 것을 고려했습니다.

링크: https://mp.csdn.net/mp_blog/creation/editor/new/129476972

3 C# 구현

서버 코드:

using System;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebSocketServer
{
    class WebSocket_Server
    {
        #region 避免重复显示
        static string lastMsg;
        static int lastCount;
        static int dupMsgCount = 0;
        static bool isFirst = true;
        #endregion
        static async Task Main(string[] args)
        {
            string localIp = "127.0.0.1"; // 默认本地IP地址
            string port = ":8080";
            foreach (IPAddress ip in Dns.GetHostAddresses(Dns.GetHostName()))
            {
                if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                {
                    localIp = ip.ToString();
                    break;
                }
            }
            Console.WriteLine("Server started at: " + localIp + port);

            HttpListener listener = new HttpListener();
            listener.Prefixes.Add("http://localhost:8080/");
            listener.Start();

            while (true)
            {
                HttpListenerContext context = await listener.GetContextAsync();
                if (context.Request.IsWebSocketRequest)
                {
                    WebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
                    WebSocket webSocket = webSocketContext.WebSocket;
                    Console.WriteLine("Client connected: " + context.Request.RemoteEndPoint.ToString());
                    await Echo(webSocket, context);
                }
                else
                {
                    context.Response.StatusCode = 400;
                    context.Response.Close();
                }
            }
        }

        private static async Task Echo(WebSocket webSocket, HttpListenerContext context)
        {
            byte[] buffer = new byte[1024];
            while (webSocket.State == WebSocketState.Open)
                try
                {
                    WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                    if (result.MessageType == WebSocketMessageType.Text)
                    {
                        string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
                        if (isFirst)
                        {
                            lastMsg = message;                    //将第一次进入函数体的消息初始化为message的内容
                            lastCount = 1;
                            isFirst = false;
                        }
                        //Console.WriteLine("Received: message - " + message);
                        //如果收到的信息重复,则不显示
                        if (string.Equals(lastMsg, message))
                        {
                            dupMsgCount++;
                        }
                        else if ((dupMsgCount != 1) && (dupMsgCount != 0))
                        {
                            lastCount = dupMsgCount;
                            //Console.WriteLine("Received: response - " + response + " " + dupMsgCount + " times. ");
                            Console.WriteLine("Received: message - " + lastMsg + " " + lastCount + " times. ");
                            dupMsgCount = 1;
                        }
                        lastMsg = message;
                        byte[] responseBuffer = Encoding.UTF8.GetBytes("Echo: " + message);//将收到的数据回响给客户端
                        await webSocket.SendAsync(new ArraySegment<byte>(responseBuffer), WebSocketMessageType.Text, true, CancellationToken.None);
                    }
                    else if (result.MessageType == WebSocketMessageType.Close)
                    {
                        Console.WriteLine("Client disconnected: " + context.Request.RemoteEndPoint.ToString());
                        await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error: " + ex.Message);
                }
        }
    }
}

클라이언트 코드:

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace WebSocketClient
{
    class WebSocket_Client
    {
        static async Task Main(string[] args)
        {
            ClientWebSocket webSocket = new ClientWebSocket();
            string targetURI = "ws://localhost:8080";
            await webSocket.ConnectAsync(new Uri(targetURI), CancellationToken.None);

            Console.WriteLine("Connected to server at: " + targetURI);


            byte[] buffer;
            string message;

            while (true)
            {
                Console.Write("Enter a message (type 'exit' to quit): ");
                message = Console.ReadLine();

                if (message.ToLower() == "exit")
                {
                    break;
                }

                buffer = Encoding.UTF8.GetBytes(message);
                await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);

                buffer = new byte[1024];
                WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                string response = Encoding.UTF8.GetString(buffer, 0, result.Count);
                Console.WriteLine("Received: " + response);
            }

            // Send a close message to the server before closing the connection
            await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
        }
    }
}

4개의 코드 통합

MQTT+WebSocket 클라이언트

using System;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using uPLibrary.Networking.M2Mqtt;
using uPLibrary.Networking.M2Mqtt.Messages;
using System.IO;

namespace csharpMQTT
{
    class CsharpMQTT_Com
    {
        static string _msg;
        #region 避免重复显示
        static string lastMsg;
        static int lastCount;
        static int dupMsgCount = 0;
        static bool isFirst = true;
        #endregion
        //MQTT部分
        static MqttClient ConnectMQTT(string broker, int port, string clientId, string username, string password)
        {
            MqttClient client = new MqttClient(broker, port, false, MqttSslProtocols.None, null, null);
            client.Connect(clientId, username, password);
            if (client.IsConnected)
            {
                Console.WriteLine("Connected to MQTT Broker. Client Id: " + clientId.ToString());
            }
            else
            {
                Console.WriteLine("Failed to connect");
            }
            return client;
        }

        static async Task Publish(MqttClient client, string topic)
        {
            int msg_count = 0;//自定义消息的内容
            while (true)
            {
                //System.Threading.Thread.Sleep(1 * 1000);
                await Task.Delay(TimeSpan.FromSeconds(1));
                string msg = "messages: " + msg_count.ToString();
                client.Publish(topic, System.Text.Encoding.UTF8.GetBytes(msg));//将msg通过话题topic发布
                Console.WriteLine("Send `{0}` to topic `{1}`", msg, topic);
                msg_count++;
                _msg = msg;

                _ = WebSocketClient_Main();
            }
        }

        static void Subscribe(MqttClient client, string topic)
        {
            client.MqttMsgPublishReceived += client_MqttMsgPublishReceived;//用于将clientMqttMsgPublishReceived方法添加到对象MqttMsgPublishReceived的事件中。当客户端接收到来自订阅主题的消息时,将调用clientMqttMsgPublishReceived方法。
            client.Subscribe(new string[] { topic }, new byte[] { MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE });
        }
        static void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
        {
            //sender是事件的发送者,即MqttClient对象,e是事件参数,即MqttMsgPublishEventArgs对象。
            //MqttMsgPublishEventArgs对象包含有关消息的信息:主题、内容、是否重复、质量等级、是否应被保留。
            string payload = System.Text.Encoding.Default.GetString(e.Message);
            Console.WriteLine("Received `{0}` from `{1}` topic", payload, e.Topic.ToString());
        }

        static async Task Main()
        {
            try
            {
                string broker = "broker.emqx.io";//默认的云端服务器地址
                //string broker = "115.156.168.35"; //本地服务器ip
                int port = 1883;
                string topic = "Csharp/mqtt";
                string clientId = Guid.NewGuid().ToString();//生成全局唯一标识符,用于标识客户端ID地址
                string username = "emqx";
                string password = "public";
                MqttClient client = ConnectMQTT(broker, port, clientId, username, password);
                Subscribe(client, topic);
                //异步方法,它不会阻塞当前线程。相反,它会返回一个Task对象,表示指定的时间过去后,操作将完成。在等待这个Task对象完成的过程中,线程可以执行其他操作。这使得程序更加响应,因为用户可以与程序进行交互,而不必等待Task.Delay返回。
                var cancellationTokenSource = new CancellationTokenSource();
                var publishTask = Task.Run(() => Publish(client, topic), cancellationTokenSource.Token);
                await publishTask;
                //Publish(client, topic);
            }
            catch (Exception ex)
            {
                Console.WriteLine("An error occurred: " + ex.Message);
            }
        }


        //WebSocket_Client部分
        static async Task WebSocketClient_Main()
        {
            //连接WebSocket服务器
            ClientWebSocket webSocket = new ClientWebSocket();
            string targetURI = "ws://localhost:8080";
            await webSocket.ConnectAsync(new Uri(targetURI), CancellationToken.None);
            Console.WriteLine("Connected to server at: " + targetURI);

            //发送和接收消息
            byte[] buffer;
            string message;
            while (true)
            {
                #region 注释的是原先手动输入信息内容的代码
                //Console.Write("Enter a message (type 'exit' to quit): ");
                //message = Console.ReadLine();
                //if (message.ToLower() == "exit")
                //{
                //    break;
                //}
                #endregion
                message = _msg;//将消息替换为mqtt处的消息
                if (isFirst) 
                {
                    lastMsg = message;                    //将第一次进入函数体的消息初始化为message的内容
                    lastCount = 1;
                    isFirst = false;                                      
                }
                buffer = Encoding.UTF8.GetBytes(message);
                await webSocket.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);

                buffer = new byte[1024];
                WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
                string response = Encoding.UTF8.GetString(buffer, 0, result.Count);//将从服务器端收到的数据存储
                //如果收到的信息重复,则不显示
                if (string.Equals(lastMsg, message))
                {
                    dupMsgCount++;
                }
                else if ((dupMsgCount != 1) && (dupMsgCount != 0))
                {
                    lastCount = dupMsgCount;
                    //Console.WriteLine("Received: response - " + response + " " + dupMsgCount + " times. ");
                    Console.WriteLine("Received: response - " + lastMsg + " " + lastCount + " times. ");//记录数据改变时,实际上已经是下一组数据了;所以要记录改变前的数据和次数,对外输出。
                    dupMsgCount = 1;
                }
                lastMsg = message;
            }

            //关闭WebSocket连接
            await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
        }
    }
}

5 테스트 과정

1> mqtt 서버 시작

20230523 emqx-5.0.25-windows-amd64.zip을 다운로드하고 위와 같은 방법으로 실행하면 경로에 문제가 있다는 메시지가 뜹니다. 아래 그림과 같이

솔루션을 찾을 수 없습니다. emqx-4.4.18-otp24.3.4.6-windows-amd64가 대신 사용되었습니다.

DOS 창을 실행하려면 bin 디렉터리를 입력하고 emqx start를 입력합니다. 4.x 버전은 프롬프트 없이 성공적으로 실행됩니다(5.x 버전에는 두 줄의 코드가 있습니다). 작업 관리자에 다음과 같이 추가 Erlang 항목이 있습니다. . 그것은 성공이다.

 2> WebSocket_Server 시작

클라이언트 연결 인터페이스를 기다리는 중:

 3> WebSocket_Client 시작

클라이언트가 성공적으로 시작되고 연결됩니다.

 4> 서버정보

 성공적인 연결 후 서버 인터페이스

클라이언트가 예기치 않게 종료되면 서버 연결이 끊어지지 않습니다.

6> 이후 그림과 같이 콘솔 출력 정보 통계를 만들기 위해 일부 내용이 수정되었습니다.

6 보충 지식

1.Mqtt 메시지 품질 수준

SpringBoot가 개발한 MQTT 프로토콜 메시지 품질 수준 분석_알면 알수록 모르는 것이 많다 블로그-CSDN 블로그

qos는 게시자와 구독자 간의 계약이 아니라 발신자와 수신자 간의 계약입니다.

즉, 게시자가 qos = 1인 메시지를 게시하는 경우 브로커가 메시지를 한 번 이상 수신할 수 있다는 것만 보장할 수 있으며, 해당 구독자가 메시지를 한 번 이상 수신할 수 있는지 여부도 해당 메시지에 따라 다릅니다. 가입자 및 브로커가 협상한 qos 수준.

qos가 1 또는 2이면 메시지는 messageId를 전달하지만 qos가 0이면 그렇지 않습니다.

qos = 0, 최대 1회 전달. 즉, 메시지를 보낸 후 바로 삭제되므로 나중에는 아무 것도 할 수 없습니다.
qos = 1, 최소 한 번 전달됩니다. 송신자가 메시지를 보낸 후 메시지를 저장하고 messageId로 수신자의 응답을 기다리고, 응답이 없으면 응답이 도착할 때까지 메시지를 다시 보내고 삭제합니다.
qos = 2, 정확히 하나의 전달.

1 메시지를 보낸 후 보낸 사람은 보낸 메시지를 저장하고 받는 사람의 응답을 기다리며, 응답이 없으면 다시 보냅니다.
2 수신자는 수신된 messageId를 기록하고 동일한 messageId를 가진 모든 후속 메시지는 삭제됩니다.
3 수신자는 messageId를 포함한 확인 메시지를 보내고 송신자의 응답을 기다린 후, 송신자가 응답하지 않으면 수신자는 확인 메시지를 반복해서 보냅니다.
4 발신자는 확인 메시지를 받은 후 보낸 메시지를 삭제하고 확인된 메시지를 messageId와 함께 보냅니다.
5 수신자는 확인 메시지를 수신한 후 확인 메시지를 삭제합니다.

7개의 다른 가능한 링크

WebSocket 통신은 C# 및 html js 파일을 사용하여 구현되며, 나중에 웹 페이지에서 변수를 꺼내는 방법을 고려해야 합니다 https://blog.csdn.net/Shuai_Sir/article/details/127364565?ops_request_misc=%257B% 2522request%255Fid% 2522%253A%2522168493215516800226547797%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D &request_id=16849321 5516800226547797&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~ first_rank_ecpm_v1~hot_rank-1 -127364565-null-null.142%5Ev87%5Econtrol_2,239%5Ev2%5Einsert_chatgpt&utm_term=c%23%20websocket%E9%80%9A%E8%AE%AF&spm=1018.2226.3001.4187

추천

출처blog.csdn.net/Suxiang1997/article/details/130681051