• .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

    1. public async Task ConnectToServer()
    2. {
    3. try
    4. {
    5. connection = new HubConnectionBuilder()
    6. .WithUrl("xxx/chathub")//
    7. .Build();
    8. connection.On("ReceiveMessage", async (user, message, clientIP, chatRoomId) =>
    9. {
    10. Console.WriteLine($"Received message from wsserver_sr : {user}: {message} |socketserver {clientIP}|{chatRoomId}");
    11. // 收到来自服务器的消息时,向客户端 C2 发送消息
    12. await SendMessageToClientC2(user, message, clientIP, chatRoomId);
    13. });
    14. await connection.StartAsync();
    15. }
    16. catch (Exception ex)
    17. {
    18. Console.WriteLine($"Error connecting to SignalR server: {ex.Message}" + Environment.NewLine);
    19. }
    20. }

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

    1. public async Task SendMessageToClientC2(string user, string message, string clientIP, string chatRoomId)
    2. {
    3. // 根据 chatRoomId 找到对应的客户端 C2,并发送消息
    4. if (chatRooms.ContainsKey(chatRoomId))
    5. {
    6. byte[] data = Encoding.ASCII.GetBytes($"{user} {message} |{clientIP}|{chatRoomId} ");
    7. foreach (var client in chatRooms[chatRoomId])
    8. {
    9. await client.GetStream().WriteAsync(data, 0, data.Length);
    10. }
    11. }
    12. else
    13. {
    14. Console.WriteLine($"Chat room {chatRoomId} does not exist" + Environment.NewLine);
    15. }
    16. }

    2.建立中转端Socket端口

    1. public void StartListening()
    2. {
    3. if (!listening)
    4. {
    5. try
    6. {
    7. int port = 10086; // 监听端口
    8. server = new TcpListener(IPAddress.Any, port);
    9. server.Start();
    10. listening = true;
    11. Console.WriteLine($"Server listening on port {port}" + Environment.NewLine);
    12. Task.Run(() => AcceptClients());
    13. }
    14. catch (Exception ex)
    15. {
    16. Console.WriteLine($"Error starting server: {ex.Message}" + Environment.NewLine);
    17. }
    18. }
    19. }

    3.向客户端发消息

    1. public async Task AcceptClients()
    2. {
    3. while (listening)
    4. {
    5. try
    6. {
    7. TcpClient client = await server.AcceptTcpClientAsync();
    8. Task.Run(() => HandleClient(client));
    9. }
    10. catch (Exception ex)
    11. {
    12. Console.WriteLine($"Error accepting client: {ex.Message}" + Environment.NewLine);
    13. }
    14. }
    15. }

    4.监听客户端连接

    1. public async Task AcceptClients()
    2. {
    3. while (listening)
    4. {
    5. try
    6. {
    7. TcpClient client = await server.AcceptTcpClientAsync();
    8. Task.Run(() => HandleClient(client));
    9. }
    10. catch (Exception ex)
    11. {
    12. Console.WriteLine($"Error accepting client: {ex.Message}" + Environment.NewLine);
    13. }
    14. }
    15. }

    5.处理客户端连接

    1. public async Task HandleClient(TcpClient client)
    2. {
    3. NetworkStream stream = client.GetStream();
    4. byte[] buffer = new byte[1024];
    5. int bytesRead;
    6. while (client.Connected)
    7. {
    8. try
    9. {
    10. bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
    11. string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
    12. ProcessMessage(message, client);
    13. }
    14. catch (Exception ex)
    15. {
    16. Console.WriteLine($"Error receiving message: {ex.Message}" + Environment.NewLine);
    17. break;
    18. }
    19. }
    20. // 客户端断开连接时,从聊天室中移除
    21. foreach (var chatRoomId in chatRooms.Keys)
    22. {
    23. if (chatRooms[chatRoomId].Contains(client))
    24. {
    25. chatRooms[chatRoomId].Remove(client);
    26. Console.WriteLine($"Client disconnected from chat room {chatRoomId}" + Environment.NewLine);
    27. break;
    28. }
    29. }
    30. }

    6.处理连接

    1. public async void ProcessMessage(string message, TcpClient client)
    2. {
    3. string[] parts = message.Split('|');
    4. if (parts.Length >= 2)
    5. {
    6. string command = parts[0];
    7. string chatRoomId = parts[1];
    8. switch (command)
    9. {
    10. case "JoinChatRoom":
    11. await JoinChatRoom(chatRoomId, client);
    12. break;
    13. case "SendMessageToChatRoomIP2":
    14. if (parts.Length >= 4)
    15. {
    16. string user = parts[2];
    17. string msg = parts[3];
    18. // await SendMessageToChatRoom(chatRoomId, user, msg);
    19. await SendMessageToChatRoomAddress(chatRoomId, user, msg, client);
    20. }
    21. break;
    22. default:
    23. Console.WriteLine($"Invalid command: {command}" + Environment.NewLine);
    24. break;
    25. }
    26. }
    27. }

    JoinChatRoom 异步调用服务端的JoinChatRoom 

    SendMessageToChatRoomIP2 异步调用SendMessage  

    7.运行

    本地运行

    发布到linux

    run  Server listening on port 10860

    netstat -tln查看端口 (tuln) 

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

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

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

    客户端实现

    1.连接Socket端

    1. private async void MainForm_Load(object sender, EventArgs e)
    2. {
    3. try
    4. {
    5. client = new TcpClient();
    6. await client.ConnectAsync("192.168.80.123", 10086);
    7. stream = client.GetStream();
    8. connected = true;
    9. // 连接成功后,调用 JoinChatRoom 方法加入聊天室
    10. await JoinChatRoom("room001");
    11. Task.Run(ReceiveMessages);
    12. }
    13. catch (Exception ex)
    14. {
    15. textBoxReceivedMessages.AppendText($"Error connecting to server: {ex.Message}" + Environment.NewLine);
    16. }
    17. }

     2.加入ChatRoom

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

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

    3.发送消息

    发送到Socket中转端

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

    4.接收消息

    1. private async Task ReceiveMessages()
    2. {
    3. byte[] buffer = new byte[1024];
    4. int bytesRead;
    5. while (connected)
    6. {
    7. try
    8. {
    9. bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
    10. string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
    11. DisplayMessage(message);
    12. }
    13. catch (Exception ex)
    14. {
    15. textBoxReceivedMessages.AppendText($"Error receiving message: {ex.Message}" + Environment.NewLine);
    16. }
    17. }
    18. }
    19. private void DisplayMessage(string message)
    20. {
    21. if (InvokeRequired)
    22. {
    23. Invoke(new Action<string>(DisplayMessage), message);
    24. return;
    25. }
    26. textBoxReceivedMessages.AppendText(message + Environment.NewLine);
    27. }

    5.点击事件

    Send

    1. private async void buttonSend_Click(object sender, EventArgs e)
    2. {
    3. if (!connected)
    4. {
    5. textBoxReceivedMessages.AppendText("Not connected to server." + Environment.NewLine);
    6. return;
    7. }
    8. string message = textBoxSendMessage.Text;
    9. if (!string.IsNullOrEmpty(message))
    10. {
    11. // 发送消息给中转端 C1,请求向聊天室发送消息
    12. await SendMessageToChatRoom("room001", message);
    13. textBoxSendMessage.Clear();
    14. textBoxReceivedMessages.AppendText("Sent message to chat room: room001" + Environment.NewLine);
    15. }
    16. else
    17. {
    18. textBoxReceivedMessages.AppendText("Please enter a message." + Environment.NewLine);
    19. }
    20. }

    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发送消息

    1. //...
    2. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    3. NSData * sendData = [@"JoinChatRoom|room001" dataUsingEncoding:NSUTF8StringEncoding];
    4. [self.socket writeData:sendData withTimeout:-1 tag:1];
    5. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    6. NSData * sendData = [@"SendMessageToChatRoomIP2|room001|userid001|hello" dataUsingEncoding:NSUTF8StringEncoding];
    7. [self.socket writeData:sendData withTimeout:-1 tag:2];
    8. [self.socket readDataWithTimeout:-1 tag:1];
    9. });
    10. });
    11. //...

    Run

  • 相关阅读:
    git 详解-提升篇
    消息队列Kafka、RocketMQ、RabbitMQ的优劣势比较
    浅谈RPC
    【算法】-- LintCode经典算法题理解动态规划
    低代码开发平台
    三、Maven-单一架构案例(搭建环境:辅助功能,业务功能:登录)
    Yum安装JDK11
    点云从入门到精通技术详解100篇-基于点云数据的机器人装焊 过程在线测量(中)
    Day30-Docker学习教程系列(二)-Docker架构
    044_第三代软件开发-保存PDF
  • 原文地址:https://blog.csdn.net/lmnotlm/article/details/137872212