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




