WebSocket C#服务器端+VUE客户端
WebSocket C#服务器端
先定义一个基类
注:用于和WinSocket融合。当然不用也是可以的
clsSocket
using System; using System.Collections.Generic; using System.Text; namespace CtiService { abstract class clsSocket { //属性 public string Name; //方法 public abstract void m_SendData(string Data); public abstract void m_Close(); public abstract bool m_IsConnected(); public abstract bool m_IsClientNull();//界面强制登陆的client为null //事件定义 //收到消息 public delegate void LineReceivedEventHandler(clsSocket sender, string Data); public LineReceivedEventHandler LineReceivedEvent; public abstract event LineReceivedEventHandler LineReceived; //收消息时发生错误 public delegate void LineReceivedErrorEventHandler(clsSocket sender, string Data); public LineReceivedErrorEventHandler LineReceivedErrorEvent; public abstract event LineReceivedErrorEventHandler LineReceivedError; //客户端断开连接 public delegate void DisconnectedEventHandler(clsSocket sender); public DisconnectedEventHandler DisconnectedEvent; public abstract event DisconnectedEventHandler Disconnected; //一个新连接 public delegate void NewConnectionEventHandler(); public NewConnectionEventHandler NewConnectionEvent; public abstract event NewConnectionEventHandler NewConnection; } }
clsWebSocket
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Net.Sockets; using System.Security.Cryptography; using WebSocket; using System.Net; //using System.Collections; using myClass; namespace CtiService { class clsWebSocket : clsSocket { //private Logger logger; //private string name; //public string Name //{ // get { return name; } // set { name = value; } //} private Boolean isDataMasked; public Boolean IsDataMasked { get { return isDataMasked; } set { isDataMasked = value; } } public Socket ConnectionSocket; private int MaxBufferSize; private string Handshake; private string New_Handshake; public byte[] receivedDataBuffer; private byte[] FirstByte; private byte[] LastByte; private byte[] ServerKey1; private byte[] ServerKey2; //public event NewConnectionEventHandler NewConnection; //public event DataReceivedEventHandler DataReceived; //public event DisconnectedEventHandler Disconnected; #region 委托,事件 //收到消息 public override event LineReceivedEventHandler LineReceived { add { LineReceivedEvent = (LineReceivedEventHandler)System.Delegate.Combine(LineReceivedEvent, value); } remove { LineReceivedEvent = (LineReceivedEventHandler)System.Delegate.Remove(LineReceivedEvent, value); } } ////收消息时发生错误 public override event LineReceivedErrorEventHandler LineReceivedError { add { LineReceivedErrorEvent = (LineReceivedErrorEventHandler)System.Delegate.Combine(LineReceivedErrorEvent, value); } remove { LineReceivedErrorEvent = (LineReceivedErrorEventHandler)System.Delegate.Remove(LineReceivedErrorEvent, value); } } ////WebSocket中客户端断开连接 public override event DisconnectedEventHandler Disconnected { add { DisconnectedEvent = (DisconnectedEventHandler)System.Delegate.Combine(DisconnectedEvent, value); } remove { DisconnectedEvent = (DisconnectedEventHandler)System.Delegate.Remove(DisconnectedEvent, value); } } //一个新连接 public override event NewConnectionEventHandler NewConnection { add { NewConnectionEvent = (NewConnectionEventHandler)System.Delegate.Combine(NewConnectionEvent, value); } remove { NewConnectionEvent = (NewConnectionEventHandler)System.Delegate.Remove(NewConnectionEvent, value); } } #endregion public clsWebSocket() { //logger = new Logger(); MaxBufferSize = 1024*100; receivedDataBuffer = new byte[MaxBufferSize]; FirstByte = new byte[MaxBufferSize]; LastByte = new byte[MaxBufferSize]; FirstByte[0] = 0x00; LastByte[0] = 0xFF; Handshake = "HTTP/1.1 101 Web Socket Protocol Handshake" + Environment.NewLine; Handshake += "Upgrade: WebSocket" + Environment.NewLine; Handshake += "Connection: Upgrade" + Environment.NewLine; Handshake += "Sec-WebSocket-Origin: " + "{0}" + Environment.NewLine; Handshake += string.Format("Sec-WebSocket-Location: " + "ws://{0}:4141/chat" + Environment.NewLine, this.getLocalmachineIPAddress()); Handshake += Environment.NewLine; New_Handshake = "HTTP/1.1 101 Switching Protocols" + Environment.NewLine; New_Handshake += "Upgrade: WebSocket" + Environment.NewLine; New_Handshake += "Connection: Upgrade" + Environment.NewLine; New_Handshake += "Sec-WebSocket-Accept: {0}" + Environment.NewLine; New_Handshake += Environment.NewLine; } public override bool m_IsConnected() { if (this.ConnectionSocket == null)//界面强制登陆的client为null { return false; } //client.Client.Connected //client.Client.Poll return ConnectionSocket.Connected; } //界面强制登陆的client为null public override bool m_IsClientNull() { if (this.ConnectionSocket == null)//界面强制登陆的client为null { return true; } else { return false; } } /// <summary> /// 断开连接 /// </summary> public override void m_Close() { try { ConnectionSocket.Close(); } catch (Exception ex) { clsLogHelper.m_CreateErrorLogTxt("WebSocket m_Close: " + Name, "断开连接", ex.Message); } } /// <summary> /// 发送消息 /// </summary> /// <param name="Data">消息内容</param> public override void m_SendData(string strMsg) { try { if (!ConnectionSocket.Connected == true)//如果连接断开则不再发送信息 { clsLogHelper.m_CreateErrorLogTxt("WebSocket m_SendData: " + Name, "如果连接断开则不再发送信息",""); return; } //两种协议 if (this.IsDataMasked)//是否数据屏蔽 { clsDataFrame dr = new clsDataFrame(strMsg); this.ConnectionSocket.Send(dr.GetBytes()); } else { this.ConnectionSocket.Send(FirstByte); this.ConnectionSocket.Send(Encoding.UTF8.GetBytes(strMsg)); this.ConnectionSocket.Send(LastByte); } } catch (Exception ex) { clsLogHelper.m_CreateErrorLogTxt("WebSocket m_SendData: " + Name + "," + strMsg, "发送消息", ex.Message); } } private void m_Read(IAsyncResult status) { if (!ConnectionSocket.Connected) return; string messageReceived = string.Empty; clsDataFrame dr = new clsDataFrame(receivedDataBuffer); try { if (!this.isDataMasked) { // Web Socket protocol: messages are sent with 0x00 and 0xFF as padding bytes System.Text.UTF8Encoding decoder = new System.Text.UTF8Encoding(); int startIndex = 0; int endIndex = 0; // Search for the start byte while (receivedDataBuffer[startIndex] == FirstByte[0]) startIndex++; // Search for the end byte endIndex = startIndex + 1; while (receivedDataBuffer[endIndex] != LastByte[0] && endIndex != MaxBufferSize - 1) endIndex++; if (endIndex == MaxBufferSize - 1) endIndex = MaxBufferSize; // Get the message messageReceived = decoder.GetString(receivedDataBuffer, startIndex, endIndex - startIndex); } else { messageReceived = dr.Text; } if ((messageReceived.Length == MaxBufferSize && messageReceived[0] == Convert.ToChar(65533)) || messageReceived.Length == 0) { //logger.Log("接受到的信息 [\"" + string.Format("logout:{0}",this.name) + "\"]"); if (DisconnectedEvent != null)//WebSocket中客户端断开连接 DisconnectedEvent(this); } else { //接受到的信息 if (this.LineReceivedEvent != null) { //logger.Log("接受到的信息 [\"" + messageReceived + "\"]"); this.LineReceivedEvent(this, messageReceived); } Array.Clear(receivedDataBuffer, 0, receivedDataBuffer.Length); ConnectionSocket.BeginReceive(receivedDataBuffer, 0, receivedDataBuffer.Length, 0, new AsyncCallback(m_Read), null); } } catch(Exception ex) { //logger.Log(ex.Message); //logger.Log("Socket连接将会被终止。"); if (DisconnectedEvent != null)//Socket连接将会被终止 DisconnectedEvent(this); myClass.clsLogHelper.m_CreateErrorLogTxt("WebSocket Read: " + Name, "读取异步信息流", ex.Message); //this.m_Close(); Thread.CurrentThread.Abort(); } } private void BuildServerPartialKey(int keyNum, string clientKey) { string partialServerKey = ""; byte[] currentKey; int spacesNum = 0; char[] keyChars = clientKey.ToCharArray(); foreach (char currentChar in keyChars) { if (char.IsDigit(currentChar)) partialServerKey += currentChar; if (char.IsWhiteSpace(currentChar)) spacesNum++; } try { currentKey = BitConverter.GetBytes((int)(Int64.Parse(partialServerKey) / spacesNum)); if (BitConverter.IsLittleEndian) Array.Reverse(currentKey); if (keyNum == 1) ServerKey1 = currentKey; else ServerKey2 = currentKey; } catch { if (ServerKey1 != null) Array.Clear(ServerKey1, 0, ServerKey1.Length); if (ServerKey2 != null) Array.Clear(ServerKey2, 0, ServerKey2.Length); } } private byte[] BuildServerFullKey(byte[] last8Bytes) { //try //{ byte[] concatenatedKeys = new byte[16]; Array.Copy(ServerKey1, 0, concatenatedKeys, 0, 4); Array.Copy(ServerKey2, 0, concatenatedKeys, 4, 4); Array.Copy(last8Bytes, 0, concatenatedKeys, 8, 8); // MD5 Hash System.Security.Cryptography.MD5 MD5Service = System.Security.Cryptography.MD5.Create(); return MD5Service.ComputeHash(concatenatedKeys); //} //catch (Exception ex) //{ // clsLogHelper.m_CreateErrorLogTxt("WebSocket BuildServerFullKey: ", "", ex.Message); //} } public void ManageHandshake(IAsyncResult status) { try { string header = "Sec-WebSocket-Version:"; int HandshakeLength = (int)status.AsyncState; byte[] last8Bytes = new byte[8]; System.Text.UTF8Encoding decoder = new System.Text.UTF8Encoding(); String rawClientHandshake = decoder.GetString(receivedDataBuffer, 0, HandshakeLength); Array.Copy(receivedDataBuffer, HandshakeLength - 8, last8Bytes, 0, 8); //现在使用的是比较新的Websocket协议 if (rawClientHandshake.IndexOf(header) != -1) { this.isDataMasked = true; string[] rawClientHandshakeLines = rawClientHandshake.Split(new string[] { Environment.NewLine }, System.StringSplitOptions.RemoveEmptyEntries); string acceptKey = ""; foreach (string Line in rawClientHandshakeLines) { Console.WriteLine(Line); if (Line.Contains("Sec-WebSocket-Key:")) { acceptKey = ComputeWebSocketHandshakeSecurityHash09(Line.Substring(Line.IndexOf(":") + 2)); } } New_Handshake = string.Format(New_Handshake, acceptKey); byte[] newHandshakeText = Encoding.UTF8.GetBytes(New_Handshake); ConnectionSocket.BeginSend(newHandshakeText, 0, newHandshakeText.Length, 0, HandshakeFinished, null); return; } string ClientHandshake = decoder.GetString(receivedDataBuffer, 0, HandshakeLength - 8); string[] ClientHandshakeLines = ClientHandshake.Split(new string[] { Environment.NewLine }, System.StringSplitOptions.RemoveEmptyEntries); ///logger.Log("新的连接请求来自" + ConnectionSocket.LocalEndPoint + "。正在准备连接 ..."); // Welcome the new client foreach (string Line in ClientHandshakeLines) { //logger.Log(Line); if (Line.Contains("Sec-WebSocket-Key1:")) BuildServerPartialKey(1, Line.Substring(Line.IndexOf(":") + 2)); if (Line.Contains("Sec-WebSocket-Key2:")) BuildServerPartialKey(2, Line.Substring(Line.IndexOf(":") + 2)); if (Line.Contains("Origin:")) try { Handshake = string.Format(Handshake, Line.Substring(Line.IndexOf(":") + 2)); } catch { Handshake = string.Format(Handshake, "null"); } } // Build the response for the client byte[] HandshakeText = Encoding.UTF8.GetBytes(Handshake); byte[] serverHandshakeResponse = new byte[HandshakeText.Length + 16]; byte[] serverKey = BuildServerFullKey(last8Bytes); Array.Copy(HandshakeText, serverHandshakeResponse, HandshakeText.Length); Array.Copy(serverKey, 0, serverHandshakeResponse, HandshakeText.Length, 16); //logger.Log("发送握手信息 ..."); ConnectionSocket.BeginSend(serverHandshakeResponse, 0, HandshakeText.Length + 16, 0, HandshakeFinished, null); //logger.Log(Handshake); } catch (Exception ex) { clsLogHelper.m_CreateErrorLogTxt("WebSocket ManageHandshake: " + status.ToString(), "", ex.Message); } } public static String ComputeWebSocketHandshakeSecurityHash09(String secWebSocketKey) { const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; String secWebSocketAccept = String.Empty; // 1. Combine the request Sec-WebSocket-Key with magic key. String ret = secWebSocketKey + MagicKEY; // 2. Compute the SHA1 hash SHA1 sha = new SHA1CryptoServiceProvider(); byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret)); // 3. Base64 encode the hash secWebSocketAccept = Convert.ToBase64String(sha1Hash); return secWebSocketAccept; } private void HandshakeFinished(IAsyncResult status) { ConnectionSocket.EndSend(status); ConnectionSocket.BeginReceive(receivedDataBuffer, 0, receivedDataBuffer.Length, 0, new AsyncCallback(m_Read), null); if (this.NewConnectionEvent != null) this.NewConnectionEvent(); } private IPAddress getLocalmachineIPAddress() { string strHostName = Dns.GetHostName(); IPHostEntry ipEntry = Dns.GetHostEntry(strHostName); foreach (IPAddress ip in ipEntry.AddressList) { //IPV4 if (ip.AddressFamily == AddressFamily.InterNetwork) return ip; } return ipEntry.AddressList[0]; } } }
frmMain 窗体中调用方式
private int intWebSocketPort = 5011;// WebSocket 监听端口 默认:5011 private Socket WebSocketListener; private System.Threading.Thread WebSocketListenerThread;
delegate void RecTcpMessage(clsSocket myAgentTcp, string strData);
在 窗体 Load 事件中 开始监听
//WebSocket this.WebSocketListenerThread = new System.Threading.Thread(new System.Threading.ThreadStart(m_WebSocketDoListen)); this.WebSocketListenerThread.Start();
在窗体关闭事件中 调用关闭监听
private void m_StopDoListen() { if (this.WebSocketListener != null) { this.WebSocketListener.Close(); } }
开始监听 函数
注:这句 System.Threading.Thread.Sleep(100); 非常重要
如果网页不能在iframe中固定,需要刷新时(尤其是VUE这种单页不断刷新整页时) 线程等待100 毫秒 太短了,网页刷新后则不能再正确连接
解决方法: 将 100 改为 3000 在Vue上亲测没问题
#region WebSocket 监听 private void m_WebSocketDoListen() { try { Char char1 = Convert.ToChar(65533); this.WebSocketListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); this.WebSocketListener.Bind(new IPEndPoint(this.IPV4, this.intWebSocketPort)); this.WebSocketListener.Listen(500);//ConnectionsQueueLength while (true) { Socket sc = this.WebSocketListener.Accept(); if (sc != null) { //System.Threading.Thread.Sleep(100); System.Threading.Thread.Sleep(3000); clsWebSocket socketConn = new clsWebSocket(); socketConn.ConnectionSocket = sc; //socketConn.NewConnection += new NewConnectionEventHandler(socketConn_NewConnection); socketConn.LineReceived += new clsSocket.LineReceivedEventHandler(m_OnLineReceived);//收到消息 socketConn.LineReceivedError += new clsSocket.LineReceivedErrorEventHandler(m_OnLineReceivedError);//收到消息时出错 socketConn.Disconnected += new clsSocket.DisconnectedEventHandler(m_Disconnected);//WebSocket中客户端断开连接 socketConn.ConnectionSocket.BeginReceive(socketConn.receivedDataBuffer, 0, socketConn.receivedDataBuffer.Length, 0, new AsyncCallback(socketConn.ManageHandshake), socketConn.ConnectionSocket.Available);; } } } catch (Exception ex)// { myClass.clsLogHelper.m_CreateErrorLogTxt("WebScript StartServer", "出错", ex.Message.ToString()); } } #endregion
收到消息 和 连接异常 和 WebSocket中断 回调函数
注:这里我用的是窗体及多线程,所以当从 WebSocket线程中 回到窗体线程 时需要用到 委托
//在线用户接收信息时出错 private void m_OnLineReceivedError(clsSocket myAgentTcp, string strData) { try { RecTcpMessage dh = new RecTcpMessage(m_RecTcpMessageError); this.Invoke(dh, new object[] { myAgentTcp, strData }); } catch (System.Exception ex) { clsLogHelper.m_CreateErrorLogTxt("frmMain m_OnLineReceivedError", myAgentTcp.Name.ToString() + "," + strData, ex.Message.ToString()); } } private void m_RecTcpMessageError(clsSocket myAgentTcp, string strData) { } //事件, 接收信息 private void m_OnLineReceived(clsSocket myAgentTcp, string strData) { try { RecTcpMessage dh = new RecTcpMessage(m_RecTcpMessage); this.Invoke(dh, new object[] { myAgentTcp, strData }); } catch (System.Exception ex) { clsLogHelper.m_CreateErrorLogTxt("frmMain m_OnLineReceived", myAgentTcp.Name.ToString() + "," + strData, ex.Message.ToString()); } } //WebSocket中客户端主动 断开连接 private void m_Disconnected(clsSocket myAgentTcp) { } private void m_RecTcpMessage(clsSocket myAgentTcp, string strData) { } #endregion
获取IP地址
注:现在大多服务器都是 双网卡。所以要用于websocket的Ip需要指定
#region 获取IP地址 private IPAddress m_GetIPV4Address() { string strHostName = Dns.GetHostName(); IPHostEntry ipEntry = Dns.GetHostEntry(strHostName); foreach (IPAddress ip in ipEntry.AddressList) { //IPV4 if (ip.ToString() == "192.168.214.1") { return ip; } //if (ip.AddressFamily == AddressFamily.InterNetwork) // return ip; } return ipEntry.AddressList[0]; } #endregion
VUE客户端
代码量有点大,我只列出用到的地方
path:"ws://192.168.214.1:5011/chat/", //WebSocket服务器地址
websock: null, //websock 连接对象 页面级
<script> import { mapState } from "vuex"; export default { name: '', props: {}, data() { return { pageContentData: [], activeIndex: '0', currentPage4: 1, path:"ws://192.168.214.1:5011/chat/", websock: null, total: 20 } },
created() { this.initWebSocket();//初始化 连接websocket }, destroyed() {this.websock.close() //离开路由 关闭网页或刷新 之后断开websocket连接 },
具体函数实现
methods: { initWebSocket(){ //初始化weosocket const wsuri = "ws://192.168.214.1:5011/chat/"; console.log("打开一个websocket " +wsuri); this.websock = new WebSocket(wsuri); this.websock.onmessage = this.websocketonmessage; this.websock.onopen = this.websocketonopen; this.websock.onerror = this.websocketonerror; this.websock.onclose = this.websocketclose; }, websocketonopen(){ //连接建立之后执行send方法发送数据 //let actions = {"test":"12345"}; //this.websocketsend(JSON.stringify(actions)); let strMsg="@&l_login:1005,1005,1005"; this.websocketsend(strMsg); console.log("连接建立成功 发 " +strMsg); }, websocketonerror(){//连接建立失败重连 this.initWebSocket(); }, websocketonmessage(e){ //数据接收 //const redata = JSON.parse(e.data); console.log("数据接收 " +e.data); }, websocketsend(Data){//数据发送 this.websock.send(Data); }, websocketclose(e){ //关闭 console.log('断开连接',e); }, }