websocket与C# socket相互通信
web端代码就是js代码,C#有两种方式:使用第三方库,如Fleck,使用C#原生socket编程实现
web端:
<!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>下发网站上文件到学生机</title> <script type="text/javascript"> function callDesktopReceiveFile(button) { var ws = null; if (button.innerHTML == '下发') { button.innerHTML = '取消'; try { if (ws) ws.close(); } catch(e) { console.log(e) } ws = new WebSocket('ws://127.0.0.1:14567'); //监听是否连接成功 ws.onopen = function () { console.log('ws连接状态[成功]:' + ws.readyState); ws.send("content:receiveFile;url:http://127.0.0.1:5500/2023CQGKMNT.dat;"); // ws.close(); console.log('发送消息'); }; // 接听服务器发回的信息并处理展示 ws.onmessage = function (e) { console.log('接收到来自服务器的消息:' + e.data); // 如果服务器在业务上主动关闭连接,则此处无需再关闭 // if (e.data === 'roger') { // ws.close(); // } }; // 监听连接关闭事件 ws.onclose = function () { // 监听整个过程中websocket的状态 console.log('ws连接状态[关闭]:' + ws.readyState); }; // 监听并处理error事件 ws.onerror = function (error) { console.log(error); }; } else if (button.innerHTML == '取消') { try { if (ws) ws.close(); } catch(e) { console.log(e) } button.innerHTML = '下发'; } } </script> </head> <body> <div> <table border="1" cellspacing="0"> <tr> <th>试卷号</th> <th>试卷名称</th> <th>描述</th> <th>操作</th> </tr> <tr> <td>JCLX01</td> <td>基础练习一</td> <td>建账、会计期间设置、部门职员设置、银行账户设置、科目设置等</td> <td><button id="btnDownload" onclick="callDesktopReceiveFile(this)">下发</button></td> </tr> <tr> <td>JCLX02</td> <td>基础练习二</td> <td>建账、会计期间设置、部门职员设置、银行账户设置、科目设置等</td> <td><button id="btnDownload" onclick="callDesktopReceiveFile(this)">下发</button></td> </tr> </table> </div> </body> </html>
C#端
方式一:使用第三方库Fleck
方式二:使用C#原生socket编程自行实现
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Threading; using U8FileTransfer.TcpHelper; using System.Net.Sockets; using System.Net; using System.Security.Cryptography; namespace CodeExperiment { public partial class Form1 : Form { public Form1() { InitializeComponent(); Thread thread = new Thread(websocketListen); thread.IsBackground = true; thread.Start(); } /// <summary> /// 解析客户端数据包,防止乱码 /// </summary> /// <param name="recBytes">服务器接收的数据包</param> /// <param name="recByteLength">有效数据长度</param> /// <returns></returns> private static string AnalyticData(byte[] recBytes, int recByteLength) { if (recByteLength < 2) { return string.Empty; } bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧 if (!fin) { return string.Empty;// 超过一帧暂不处理 } bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码 if (!mask_flag) { return string.Empty;// 不包含掩码的暂不处理 } int payload_len = recBytes[1] & 0x7F; // 数据长度 byte[] masks = new byte[4]; byte[] payload_data; if (payload_len == 126) { Array.Copy(recBytes, 4, masks, 0, 4); payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]); payload_data = new byte[payload_len]; Array.Copy(recBytes, 8, payload_data, 0, payload_len); } else if (payload_len == 127) { Array.Copy(recBytes, 10, masks, 0, 4); byte[] uInt64Bytes = new byte[8]; for (int i = 0; i < 8; i++) { uInt64Bytes[i] = recBytes[9 - i]; } UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0); payload_data = new byte[len]; for (UInt64 i = 0; i < len; i++) { payload_data[i] = recBytes[i + 14]; } } else { Array.Copy(recBytes, 2, masks, 0, 4); payload_data = new byte[payload_len]; Array.Copy(recBytes, 6, payload_data, 0, payload_len); } for (var i = 0; i < payload_len; i++) { payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]); } return Encoding.UTF8.GetString(payload_data); } /// <summary> /// 打包服务器数据,防止乱码 /// </summary> /// <param name="message">数据</param> /// <returns>数据包</returns> private static byte[] PackData(string message) { byte[] contentBytes = null; byte[] temp = Encoding.UTF8.GetBytes(message); if (temp.Length < 126) { contentBytes = new byte[temp.Length + 2]; contentBytes[0] = 0x81; contentBytes[1] = (byte)temp.Length; Array.Copy(temp, 0, contentBytes, 2, temp.Length); } else if (temp.Length < 0xFFFF) { contentBytes = new byte[temp.Length + 4]; contentBytes[0] = 0x81; contentBytes[1] = 126; contentBytes[2] = (byte)(temp.Length & 0xFF); contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF); Array.Copy(temp, 0, contentBytes, 4, temp.Length); } else { // 暂不处理超长内容 } return contentBytes; } /// <summary> /// 客户端消息结构化 /// message参数格式为多个key:value键值对通过分号拼接组成,示例: /// content:download_and_send;file-url:https://www.a.com/a.zip; /// /// </summary> /// <param name="message"></param> /// <returns></returns> private Dictionary<string, string> ProcessRemoteMessage(string message) { Dictionary<string, string> dic = new Dictionary<string, string>(); if (message.Substring(message.Length - 1, 1) == ";") { message = message.Substring(0, message.Length - 1); } string[] strs = message.Split(';'); if (strs.Length > 0) { Console.WriteLine("- - - - - - - - - - - - - - - - - - -"); foreach (string s in strs) { Console.WriteLine(s); string[] split = s.Split(new char[] { ':' }, 2); Console.WriteLine("[" + split[0] + "][" + split[1] + "]"); dic.Add(split[0], split[1]); } Console.WriteLine("- - - - - - - - - - - - - - - - - - -"); } return dic; } private void websocketListen() { Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); EndPoint endPoint = new IPEndPoint(IPAddress.Any, 14567);//监听端口 server.Bind(endPoint); server.Listen(10); // 排队等待连接最大数量10 // 监听多个客户端连接 while (true) { Socket client = server.Accept(); Console.WriteLine("有客户端连上来了"); //接收客户端发来的HTTP-Header消息 byte[] bytes = new byte[1024]; int len = client.Receive(bytes); string strMessage = Encoding.UTF8.GetString(bytes, 0, len); Console.WriteLine(strMessage); //获取Sec-WebSocket-Key,为握手做准备 string[] strings = strMessage.Split('\n'); string strSecWebSocketKey = ""; foreach (var item in strings) { string[] strings1 = item.Split(':'); if (strings1[0] == "Sec-WebSocket-Key") { strSecWebSocketKey = strings1[1].Trim(); } } //生成服务端Sec-WebSocket-Accept,迎合客户端的握手请求 byte[] secKeyBytes = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(strSecWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); string secKey = Convert.ToBase64String(secKeyBytes); // 发送给客户端完成握手(会触发Websocket的open()回调函数),针对websocket必须使用以下header string strHeader = ""; strHeader += "HTTP/1.1 101 Switching Protocols" + Environment.NewLine; strHeader += "Upgrade: websocket" + Environment.NewLine; strHeader += "Connection: Upgrade" + Environment.NewLine; strHeader += "Sec-WebSocket-Accept: " + secKey + Environment.NewLine + Environment.NewLine; client.Send(Encoding.UTF8.GetBytes(strHeader)); string remoteFileUrl = null; bool clientClose = false; // 循环接收websocket发来的消息实现双方交流 while (!clientClose) { //接收客户端发来的消息 byte[] bytes2 = new byte[1024]; int len2 = client.Receive(bytes2); string strMessage2 = AnalyticData(bytes2, len2); if (strMessage2.Length > 0) { Console.WriteLine("客户端发来消息:{0}", strMessage2); Dictionary<string, string> messageDic = ProcessRemoteMessage(strMessage2); string content = null; messageDic.TryGetValue("content", out content); Console.WriteLine("message content:" + content); if (content == "receiveFile") { messageDic.TryGetValue("url", out remoteFileUrl); client.Send(PackData("roger")); Console.WriteLine("remoteFileUrl: " + remoteFileUrl); Console.WriteLine("to do invoke download."); // 关闭连接 client.Shutdown(SocketShutdown.Both); client.Close(); clientClose = true; } } else { Console.WriteLine("客户端关闭了连接"); client.Shutdown(SocketShutdown.Both); client.Close(); } } } } } }
websocket关闭机制
通信双方都可以主动关闭连接,不管谁关闭连接,对方都能收到消息。
client端关闭连接,server端能收到内容长度为0的消息。
server端关闭连接,client端会触发onclose事件。