You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

JavaScript与C# TCP通信:浏览器向C#服务器发消息实现方案

实现浏览器(JS/Angular)向C# TCP服务器发送消息

首先得明确一个核心限制:浏览器环境没有原生的TCP套接字API,没法直接发起纯TCP连接。最实用的方案是用WebSocket——它基于TCP协议构建,浏览器原生支持,而且C#可以轻松适配处理WebSocket连接。下面分步骤给出完整实现:

一、改造C# TCP服务器支持WebSocket(推荐)

先补全并优化你的代码,让它能处理WebSocket的握手流程和消息解析:

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Security.Cryptography;

class WebSocketTcpServer
{
    private static TcpListener server;

    static void Main(string[] args)
    {
        server = new TcpListener(IPAddress.Parse("127.0.0.1"), 476);
        server.Start();
        Console.WriteLine("WebSocket服务器已启动,等待客户端连接...");

        // 持续监听新连接
        while (true)
        {
            TcpClient client = server.AcceptTcpClient();
            Console.WriteLine("新客户端已接入");
            // 异步处理客户端,避免阻塞主线程
            _ = HandleClientAsync(client);
        }
    }

    private static async System.Threading.Tasks.Task HandleClientAsync(TcpClient client)
    {
        using (NetworkStream stream = client.GetStream())
        using (StreamReader reader = new StreamReader(stream))
        using (StreamWriter writer = new StreamWriter(stream) { AutoFlush = true })
        {
            // 读取WebSocket握手请求
            string handshakeLine = await reader.ReadLineAsync();
            // 跳过请求头的剩余行
            string headerLine;
            while (!string.IsNullOrEmpty(headerLine = await reader.ReadLineAsync())) ;

            if (handshakeLine?.Contains("Upgrade: websocket") == true)
            {
                // 生成握手响应所需的密钥
                string clientKey = ExtractWebSocketKey(handshakeLine);
                string responseKey = GenerateResponseKey(clientKey);

                // 发送握手响应
                await writer.WriteLineAsync("HTTP/1.1 101 Switching Protocols");
                await writer.WriteLineAsync("Upgrade: websocket");
                await writer.WriteLineAsync("Connection: Upgrade");
                await writer.WriteLineAsync($"Sec-WebSocket-Accept: {responseKey}");
                await writer.WriteLineAsync();

                Console.WriteLine("WebSocket握手完成,开始接收消息");

                byte[] buffer = new byte[1024];
                while (client.Connected)
                {
                    int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                    if (bytesRead <= 0) break;

                    // 解析WebSocket文本帧
                    string receivedMsg = DecodeWebSocketFrame(buffer, bytesRead);
                    Console.WriteLine($"收到浏览器消息: {receivedMsg}");

                    // 可选:回复消息给浏览器
                    string replyMsg = $"服务器已接收:{receivedMsg}";
                    byte[] replyFrame = EncodeWebSocketFrame(replyMsg);
                    await stream.WriteAsync(replyFrame, 0, replyFrame.Length);
                }
            }
        }

        client.Close();
        Console.WriteLine("客户端已断开连接");
    }

    // 从握手请求中提取Sec-WebSocket-Key
    private static string ExtractWebSocketKey(string handshakeLine)
    {
        string[] lines = handshakeLine.Split('\n');
        foreach (string line in lines)
        {
            if (line.StartsWith("Sec-WebSocket-Key:"))
            {
                return line.Split(':')[1].Trim();
            }
        }
        return string.Empty;
    }

    // 生成WebSocket握手响应密钥
    private static string GenerateResponseKey(string clientKey)
    {
        const string webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
        string combinedKey = clientKey + webSocketGuid;
        byte[] sha1Hash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(combinedKey));
        return Convert.ToBase64String(sha1Hash);
    }

    // 解码WebSocket文本帧
    private static string DecodeWebSocketFrame(byte[] buffer, int length)
    {
        int opcode = buffer[0] & 0x0F;
        if (opcode != 1) throw new NotSupportedException("当前仅支持文本消息");

        int payloadLength = buffer[1] & 0x7F;
        int offset = 2;

        if (payloadLength == 126)
        {
            payloadLength = BitConverter.ToInt16(new byte[] { buffer[3], buffer[2] }, 0);
            offset += 2;
        }
        else if (payloadLength == 127)
        {
            throw new NotSupportedException("暂不支持超大消息");
        }

        // 提取掩码并解码 payload
        byte[] mask = new byte[4];
        Array.Copy(buffer, offset, mask, 0, 4);
        offset += 4;

        byte[] payloadBytes = new byte[payloadLength];
        for (int i = 0; i < payloadLength; i++)
        {
            payloadBytes[i] = (byte)(buffer[offset + i] ^ mask[i % 4]);
        }

        return Encoding.UTF8.GetString(payloadBytes);
    }

    // 编码WebSocket文本帧
    private static byte[] EncodeWebSocketFrame(string message)
    {
        byte[] payloadBytes = Encoding.UTF8.GetBytes(message);
        int payloadLength = payloadBytes.Length;

        using (MemoryStream ms = new MemoryStream())
        {
            // 写入帧头:FIN + 文本帧标识
            ms.WriteByte((byte)0x81);

            // 写入 payload 长度
            if (payloadLength <= 125)
            {
                ms.WriteByte((byte)payloadLength);
            }
            else if (payloadLength <= 65535)
            {
                ms.WriteByte(126);
                byte[] lengthBytes = BitConverter.GetBytes((ushort)payloadLength);
                Array.Reverse(lengthBytes);
                ms.Write(lengthBytes, 0, 2);
            }
            else
            {
                throw new NotSupportedException("暂不支持超大消息");
            }

            // 写入消息内容
            ms.Write(payloadBytes, 0, payloadLength);
            return ms.ToArray();
        }
    }
}

二、前端(JavaScript/Angular)实现WebSocket客户端

原生JavaScript版本

// 建立WebSocket连接
const socket = new WebSocket('ws://127.0.0.1:476');

// 连接成功回调
socket.onopen = () => {
    console.log('已成功连接到服务器');
    // 发送测试消息
    socket.send('这是来自浏览器的消息');
};

// 接收服务器回复
socket.onmessage = (event) => {
    console.log('收到服务器回复:', event.data);
};

// 连接关闭回调
socket.onclose = () => {
    console.log('与服务器的连接已关闭');
};

// 错误处理
socket.onerror = (error) => {
    console.error('WebSocket连接出错:', error);
};

Angular组件版本

import { Component, OnInit, OnDestroy } from '@angular/core';

@Component({
    selector: 'app-websocket-client',
    template: `
        <div class="chat-box">
            <input type="text" [(ngModel)]="inputMsg" placeholder="输入消息后发送">
            <button (click)="sendMsg()" [disabled]="!inputMsg">发送</button>
            <div class="reply-area">
                <p>服务器回复: {{serverReply}}</p>
            </div>
        </div>
    `,
    styles: [`
        .chat-box { margin: 20px; }
        input { padding: 8px; width: 300px; }
        button { padding: 8px 16px; margin-left: 10px; }
        .reply-area { margin-top: 15px; color: #2c3e50; }
    `]
})
export class WebSocketClientComponent implements OnInit, OnDestroy {
    private socket!: WebSocket;
    inputMsg = '';
    serverReply = '';

    ngOnInit(): void {
        // 初始化WebSocket连接
        this.socket = new WebSocket('ws://127.0.0.1:476');

        this.socket.onopen = () => {
            console.log('Angular客户端已连接到服务器');
        };

        this.socket.onmessage = (event) => {
            this.serverReply = event.data;
        };

        this.socket.onerror = (error) => {
            console.error('WebSocket连接异常:', error);
        };

        this.socket.onclose = () => {
            console.log('连接已断开');
        };
    }

    sendMsg(): void {
        if (this.socket.readyState === WebSocket.OPEN) {
            this.socket.send(this.inputMsg);
            this.inputMsg = '';
        } else {
            alert('连接未就绪,请稍后重试');
        }
    }

    ngOnDestroy(): void {
        // 组件销毁时关闭连接
        if (this.socket) {
            this.socket.close();
        }
    }
}

额外说明

  • 如果非要用纯TCP而不是WebSocket,浏览器环境下需要借助WebAssembly编译的TCP库(比如基于libwebsockets的WASM版本),但这种方案复杂度极高,仅适合特殊场景。
  • 若前端和服务器不在同一域名/端口,需要在C#服务器的握手阶段添加跨域相关响应头,避免浏览器的跨域限制。
  • 生产环境建议使用成熟的WebSocket库(比如C#的WebSocketSharp或ASP.NET Core内置的WebSockets组件),减少手动解析帧的工作量。

内容的提问来源于stack exchange,提问作者Yedidya kfir

火山引擎 最新活动