.NET 基于Socket中转WebSocket

前言

针对IOS App Proxy Server无法直连WebSocket,建立 Socket中转端。

WebSocket

WebSocket 端用于实现实时通信功能。

WebSocket 端通过 WebSocket 协议与中转端通信,中转端可以通过 WebSocket 或其他传输协议与 WebSocket 端建立连接,收发消息。

定义 SendMessageToChatRoomIP 和 JoinChatRoom,用于处理来自客户端的消息和连接事件。

中转端 C1 (Socket Server)

中转端 C1 是一个独立的应用程序,用于连接WebSocket端,建立Socket服务。

中转端 C1 的主要功能是接收来自客户端 C2 的消息,并将其转发给 WebSocket 端,或者接收来自 WebSocket 端的消息,并转发给客户端 C2。

客户端 C2 x N

客户端 C2 ,主要与中转端连接 发送数据,接收数据。

在客户端 C2 中,连接到中转端 C1、发送消息给中转端 C1、接收来自中转端 C1 的消息。

调用关系为:

客户端 C2 通过与中转端 C1 的通信,向 WebSocket 端发送消息或接收来自 WebSocket 端的消息。

中转端 C1 接收来自客户端 C2 的消息,并将其转发给 WebSocket 端,或者接收来自 WebSocket 端的消息,并转发给客户端 C2。

中转端实现

1.连接WebSocket

javascript 复制代码
      public async Task ConnectToServer()
      {
          try
          {
              connection = new HubConnectionBuilder()
                  .WithUrl("xxx/chathub")//
                  .Build();

              connection.On<string, string, string, string>("ReceiveMessage", async (user, message, clientIP, chatRoomId) =>
              {
                  Console.WriteLine($"Received message from wsserver_sr : {user}: {message} |socketserver {clientIP}|{chatRoomId}");

                  // 收到来自服务器的消息时,向客户端 C2 发送消息
                  await SendMessageToClientC2(user, message, clientIP, chatRoomId);
              });

              await connection.StartAsync();
          }
          catch (Exception ex)
          {
              Console.WriteLine($"Error connecting to SignalR server: {ex.Message}" + Environment.NewLine);
          }
      }

收到消息时发送给对应客户端

cs 复制代码
       public async Task SendMessageToClientC2(string user, string message, string clientIP, string chatRoomId)
       {
           // 根据 chatRoomId 找到对应的客户端 C2,并发送消息
           if (chatRooms.ContainsKey(chatRoomId))
           {
               byte[] data = Encoding.ASCII.GetBytes($"{user}  {message}  |{clientIP}|{chatRoomId} ");
               foreach (var client in chatRooms[chatRoomId])
               {
                   await client.GetStream().WriteAsync(data, 0, data.Length);
               }
           }
           else
           {
               Console.WriteLine($"Chat room {chatRoomId} does not exist" + Environment.NewLine);
           }
       }

2.建立中转端Socket端口

cs 复制代码
   public void StartListening()
   {
       if (!listening)
       {
           try
           {
               int port = 10086; // 监听端口
               server = new TcpListener(IPAddress.Any, port);
               server.Start();
               listening = true;
               Console.WriteLine($"Server listening on port {port}" + Environment.NewLine);

               Task.Run(() => AcceptClients());
           }
           catch (Exception ex)
           {
               Console.WriteLine($"Error starting server: {ex.Message}" + Environment.NewLine);
           }
       }
   }

3.向客户端发消息

javascript 复制代码
        public async Task AcceptClients()
        {
            while (listening)
            {
                try
                {
                    TcpClient client = await server.AcceptTcpClientAsync();
                    Task.Run(() => HandleClient(client));
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Error accepting client: {ex.Message}" + Environment.NewLine);
                }
            }
        }

4.监听客户端连接

cs 复制代码
   public async Task AcceptClients()
   {
       while (listening)
       {
           try
           {
               TcpClient client = await server.AcceptTcpClientAsync();
               Task.Run(() => HandleClient(client));
           }
           catch (Exception ex)
           {
               Console.WriteLine($"Error accepting client: {ex.Message}" + Environment.NewLine);
           }
       }
   }

5.处理客户端连接

cs 复制代码
    public async Task HandleClient(TcpClient client)
    {
        NetworkStream stream = client.GetStream();
        byte[] buffer = new byte[1024];
        int bytesRead;

        while (client.Connected)
        {
            try
            {
                bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                ProcessMessage(message, client);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error receiving message: {ex.Message}" + Environment.NewLine);
                break;
            }
        }

        // 客户端断开连接时,从聊天室中移除
        foreach (var chatRoomId in chatRooms.Keys)
        {
            if (chatRooms[chatRoomId].Contains(client))
            {
                chatRooms[chatRoomId].Remove(client);
                Console.WriteLine($"Client disconnected from chat room {chatRoomId}" + Environment.NewLine);
                break;
            }
        }
    }

6.处理连接

cs 复制代码
        public async void ProcessMessage(string message, TcpClient client)
        {
            string[] parts = message.Split('|');
            if (parts.Length >= 2)
            {
                string command = parts[0];
                string chatRoomId = parts[1];

                switch (command)
                {
                    case "JoinChatRoom":
                        await JoinChatRoom(chatRoomId, client);
                        break;
                    case "SendMessageToChatRoomIP2":
                        if (parts.Length >= 4)
                        {
                            string user = parts[2];
                            string msg = parts[3];
                            //  await SendMessageToChatRoom(chatRoomId, user, msg);
                            await SendMessageToChatRoomAddress(chatRoomId, user, msg, client);
                        }
                        break;
                    default:
                        Console.WriteLine($"Invalid command: {command}" + Environment.NewLine);
                        break;
                }
            }
        }

JoinChatRoom 异步调用服务端的JoinChatRoom

SendMessageToChatRoomIP2 异步调用SendMessage

7.运行

本地运行

发布到linux

run Server listening on port 10860

netstat -tln查看端口 (tuln)

也可以优化封装成WinForm程序,收发消息可视化。

点击启动socket端口调用 1.连接websocket 2.监听socket

也可以封装成系统服务,与进程一样藏在后台运行。

客户端实现

1.连接Socket端

cs 复制代码
   private async void MainForm_Load(object sender, EventArgs e)
   {
       try
       {
           client = new TcpClient();
           await client.ConnectAsync("192.168.80.123", 10086); 
           stream = client.GetStream();
           connected = true;

           // 连接成功后,调用 JoinChatRoom 方法加入聊天室
           await JoinChatRoom("room001");

           Task.Run(ReceiveMessages);
       }
       catch (Exception ex)
       {
           textBoxReceivedMessages.AppendText($"Error connecting to server: {ex.Message}" + Environment.NewLine);
       }
   }

2.加入ChatRoom

JoinChatRoom中转到Server端再加入聊天室

cs 复制代码
   private async Task JoinChatRoom(string chatRoomId)
        {
            // 发送消息给中转端 C1,请求加入聊天室
            byte[] data = Encoding.ASCII.GetBytes($"JoinChatRoom|{chatRoomId}|J");
            await stream.WriteAsync(data, 0, data.Length);
        }

3.发送消息

发送到Socket中转端

cs 复制代码
     
        private async Task SendMessageToChatRoom(string chatRoomId, string message)
        {
            // 发送消息给中转端 C1,请求向指定聊天室发送消息
            byte[] data = Encoding.ASCII.GetBytes($"SendMessageToChatRoomIP2|{chatRoomId}|client2m|{message}");
            await stream.WriteAsync(data, 0, data.Length);
        }

        

4.接收消息

cs 复制代码
private async Task ReceiveMessages()
        {
            byte[] buffer = new byte[1024];
            int bytesRead;

            while (connected)
            {
                try
                {
                    bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                    string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
                    DisplayMessage(message);
                }
                catch (Exception ex)
                {
                    textBoxReceivedMessages.AppendText($"Error receiving message: {ex.Message}" + Environment.NewLine);
                }
            }
        }

        private void DisplayMessage(string message)
        {
            if (InvokeRequired)
            {
                Invoke(new Action<string>(DisplayMessage), message);
                return;
            }

            textBoxReceivedMessages.AppendText(message + Environment.NewLine);
        }

     

5.点击事件

Send

cs 复制代码
   private async void buttonSend_Click(object sender, EventArgs e)
        {
            if (!connected)
            {
                textBoxReceivedMessages.AppendText("Not connected to server." + Environment.NewLine);
                return;
            }

            string message = textBoxSendMessage.Text;

            if (!string.IsNullOrEmpty(message))
            {
                // 发送消息给中转端 C1,请求向聊天室发送消息
                await SendMessageToChatRoom("room001", message);
                textBoxSendMessage.Clear();
                textBoxReceivedMessages.AppendText("Sent message to chat room: room001"  + Environment.NewLine);
            }
            else
            {
                textBoxReceivedMessages.AppendText("Please enter a message." + Environment.NewLine);
            }
        }

6.运行

服务端启动

Server listening on port 10086

服务端发收发消息到中转端

Sent message to chat room: room001

client2m hihihi|socketclient 192.168.80.123 |::ffff:183.7.113.105|room001

中转端拿到客户端信息->WebSocket端->中转端接收->客户端接收

Client joined chat room room001

Received message from WSSserver_sr : client2m: hihihi|socketclient 192.168.80.123 |socketserver:::ffff:183.7.113.105|room001

客户端也可以用IOS SOCKET5发送消息

Swift 复制代码
//...         
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                    NSData * sendData = [@"JoinChatRoom|room001" dataUsingEncoding:NSUTF8StringEncoding];
                    [self.socket writeData:sendData withTimeout:-1 tag:1];
                    
                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                        NSData * sendData = [@"SendMessageToChatRoomIP2|room001|userid001|hello" dataUsingEncoding:NSUTF8StringEncoding];
                        [self.socket writeData:sendData withTimeout:-1 tag:2];
                        [self.socket readDataWithTimeout:-1 tag:1];
                    });
                });

//...

Run

相关推荐
EdisonZhou19 分钟前
大模型应用开发初探 : 通用函数调用Planner
aigc·.net·.net core
时光追逐者1 小时前
分享6个.NET开源的AI和LLM相关项目框架
人工智能·microsoft·ai·c#·.net·.netcore
Java资深爱好者12 小时前
VB.NET中如何利用ASP.NET进行Web开发
前端·asp.net·.net
Lingbug15 小时前
.Net日志组件之NLog的使用和配置
后端·c#·.net·.netcore
那个那个鱼1 天前
.NET 框架版本年表
开发语言·c#·.net
小乖兽技术1 天前
fo-dicom,第一个基于.NET Standard 2.0 开发的DICOM开源库
.net·fo-dicom
nigture1 天前
.NET全局静态可访问IServiceProvider(支持Blazor)
.net·ioc·di·rougamo
脚步的影子2 天前
.NET 6.0 + WPF 使用 Prism 框架实现导航
.net·wpf
wo63704313 天前
[Visual Stuidio 2022使用技巧]2.配置及常用快捷键
c#·.net·wpf