using linker.libs.extends; using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; namespace linker.libs.websocket { /// /// wensocket客户端 /// public sealed class WebSocketClient : IDisposable { private int bufferSize = 4 * 1024; private SocketAsyncEventArgs readEventArgs; private bool connected = false; private bool connecSuccess = false; /// /// 连接中,false表示失败,会关闭连接 /// public Func OnConnecting = (header) => { return true; }; /// /// 连接失败 /// public Action OnConnectFail { get; set; } = (msg) => { }; /// /// 已断开连接,未收到关闭帧 /// public Action OnDisConnectd = () => { }; /// /// 已连接 /// public Action OnOpen = (header) => { }; /// /// 已断开连接,收到关闭帧 /// public Action OnClose = () => { }; /// /// 文本数据 /// public Action OnMessage = (frame, messgae) => { }; /// /// 二进制数据 /// public Action> OnBinary = (frame, data) => { }; /// /// 控制帧 /// public Action OnControll = (frame) => { }; /// /// 非控制帧 /// public Action OnUnControll = (frame) => { }; /// /// /// public WebSocketClient() { handles = new Dictionary> { //直接添加数据 { WebSocketFrameInfo.EnumOpcode.Data,HandleAppendData}, //记录opcode并添加 { WebSocketFrameInfo.EnumOpcode.Text,HandleData}, { WebSocketFrameInfo.EnumOpcode.Binary,HandleData}, { WebSocketFrameInfo.EnumOpcode.Close,HandleClose}, { WebSocketFrameInfo.EnumOpcode.Ping,HandlePing}, { WebSocketFrameInfo.EnumOpcode.Pong,HandlePong}, }; } /// /// /// /// /// public void Connect(IPAddress ip, int port) { Connect(new IPEndPoint(ip, port)); } /// /// /// /// /// public void Connect(IPEndPoint ep) { if (connected) { throw new Exception("connected"); } var socket = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp); //socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); socket.KeepAlive(); AsyncServerUserToken token = new AsyncServerUserToken { TargetSocket = socket, }; SocketAsyncEventArgs connectEventArgs = new SocketAsyncEventArgs { UserToken = token, SocketFlags = SocketFlags.None, }; connectEventArgs.RemoteEndPoint = ep; connectEventArgs.Completed += Target_IO_Completed; if (!socket.ConnectAsync(connectEventArgs)) { TargetProcessConnect(connectEventArgs); } } private void Target_IO_Completed(object sender, SocketAsyncEventArgs e) { switch (e.LastOperation) { case SocketAsyncOperation.Connect: TargetProcessConnect(e); break; case SocketAsyncOperation.Receive: TargetProcessReceive(); break; default: break; } } private void TargetProcessConnect(SocketAsyncEventArgs connectEventArgs) { AsyncServerUserToken token = (AsyncServerUserToken)connectEventArgs.UserToken; try { if (connectEventArgs.SocketError == SocketError.Success) { token.SecWebSocketKey = WebSocketParser.BuildSecWebSocketKey(); WebsocketHeaderInfo header = new WebsocketHeaderInfo(); header.SetHeaderValue(WebsocketHeaderKey.SecWebSocketKey, token.SecWebSocketKey); byte[] connectData = WebSocketParser.BuildConnectData(header); token.TargetSocket.Send(connectData, SocketFlags.None); connected = true; BindTargetReceive(token); } else { OnConnectFail(connectEventArgs.SocketError.ToString()); } } catch (Exception ex) { if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Error(ex); OnConnectFail(ex.Message); } } private void BindTargetReceive(AsyncServerUserToken connectToken) { AsyncServerUserToken token = new AsyncServerUserToken { TargetSocket = connectToken.TargetSocket, SecWebSocketKey = connectToken.SecWebSocketKey, }; try { readEventArgs = new SocketAsyncEventArgs { UserToken = token, SocketFlags = SocketFlags.None, }; token.PoolBuffer = new byte[bufferSize]; readEventArgs.SetBuffer(token.PoolBuffer, 0, bufferSize); readEventArgs.Completed += Target_IO_Completed; if (token.TargetSocket.ReceiveAsync(readEventArgs) == false) { TargetProcessReceive(); } } catch (Exception ex) { if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Error(ex); } } private void TargetProcessReceive() { try { AsyncServerUserToken token = (AsyncServerUserToken)readEventArgs.UserToken; if (readEventArgs.BytesTransferred > 0 && readEventArgs.SocketError == SocketError.Success) { int offset = readEventArgs.Offset; int length = readEventArgs.BytesTransferred; ReadFrame(token, readEventArgs.Buffer.AsMemory(offset, length)); if (token.TargetSocket.Available > 0) { while (token.TargetSocket.Available > 0) { length = token.TargetSocket.Receive(readEventArgs.Buffer); if (length > 0) { ReadFrame(token, readEventArgs.Buffer.AsMemory(0, length)); } } } if (token.TargetSocket.Connected == false) { CloseClientSocket(); return; } if (token.TargetSocket.ReceiveAsync(readEventArgs) == false) { TargetProcessReceive(); } } else { CloseClientSocket(); } } catch (Exception ex) { CloseClientSocket(); if (LoggerHelper.Instance.LoggerLevel <= LoggerTypes.DEBUG) LoggerHelper.Instance.Error(ex); } } private void CloseClientSocket(SocketAsyncEventArgs e) { if (connected) { AsyncServerUserToken token = readEventArgs.UserToken as AsyncServerUserToken; token.Clear(); readEventArgs.Dispose(); OnDisConnectd(); } connected = false; connecSuccess = false; } private void CloseClientSocket() { CloseClientSocket(readEventArgs); } /// /// /// public void Close() { CloseClientSocket(); } private readonly Dictionary> handles; private void ReadFrame(AsyncServerUserToken token, Memory data) { if (connecSuccess) { if (token.FrameBuffer.Size == 0 && data.Length > 6) { if (WebSocketFrameInfo.TryParse(data, out token.FrameInfo)) { ExecuteHandle(token); if (token.FrameInfo.TotalLength == data.Length) { return; } token.FrameBuffer.AddRange(data.Slice(token.FrameInfo.TotalLength)); } else { token.FrameBuffer.AddRange(data); } } else { token.FrameBuffer.AddRange(data); } do { if (!WebSocketFrameInfo.TryParse(token.FrameBuffer.Data.Slice(token.FrameIndex), out token.FrameInfo)) { break; } if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin) { ExecuteHandle(token); token.FrameBuffer.RemoveRange(0, token.FrameInfo.TotalLength); token.FrameIndex = 0; } else { token.FrameBuffer.RemoveRange(token.FrameIndex, token.FrameInfo.TotalLength - token.FrameInfo.PayloadData.Length); token.FrameIndex += token.FrameInfo.PayloadData.Length; } } while (token.FrameBuffer.Size > 6); } else { HandleConnect(token, data); } } private void ExecuteHandle(AsyncServerUserToken token) { if (handles.TryGetValue(token.FrameInfo.Opcode, out Action action)) { action(token); } else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll3 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.UnControll7) { OnUnControll(token.FrameInfo); } else if (token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll11 && token.FrameInfo.Opcode >= WebSocketFrameInfo.EnumOpcode.Controll15) { OnControll(token.FrameInfo); } else { SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Unexpected); CloseClientSocket(); return; } } private void HandleData(AsyncServerUserToken token) { token.Opcode = token.FrameInfo.Opcode; HandleAppendData(token); } private void HandleAppendData(AsyncServerUserToken token) { if (token.FrameInfo.Fin == WebSocketFrameInfo.EnumFin.Fin) { if (token.Opcode == WebSocketFrameInfo.EnumOpcode.Text) { string str = token.FrameInfo.PayloadData.GetString(); OnMessage(token.FrameInfo, str); } else { OnBinary(token.FrameInfo, token.FrameInfo.PayloadData); } } } private void HandleClose(AsyncServerUserToken token) { SendFrameClose(WebSocketFrameInfo.EnumCloseStatus.Normal); CloseClientSocket(); OnClose(); } private void HandlePing(AsyncServerUserToken token) { SendFramePong(); } private void HandlePong(AsyncServerUserToken token) { } private void HandleConnect(AsyncServerUserToken token, Memory data) { WebsocketHeaderInfo header = WebsocketHeaderInfo.Parse(data); if (header.TryGetHeaderValue(WebsocketHeaderKey.SecWebSocketAccept, out string accept) == false || WebSocketParser.VerifySecWebSocketAccept(token.SecWebSocketKey, accept) == false) { OnConnectFail("Sec-WebSocket-Accept Invalid"); CloseClientSocket(); return; } if (header.StatusCode != HttpStatusCode.SwitchingProtocols) { OnConnectFail($"{(int)header.StatusCode},{header.StatusCode}"); CloseClientSocket(); return; } if (!OnConnecting(header)) { CloseClientSocket(); return; } connecSuccess = true; OnOpen(header); } public int SendRaw(byte[] buffer) { var socket = (readEventArgs.UserToken as AsyncServerUserToken).TargetSocket; return socket.Send(buffer, SocketFlags.None); } public int SendRaw(Memory buffer) { var socket = (readEventArgs.UserToken as AsyncServerUserToken).TargetSocket; return socket.Send(buffer.Span, SocketFlags.None); } public int SendFrame(WebSocketFrameRemarkInfo remark) { remark.Mask = WebSocketFrameInfo.EnumMask.Mask; remark.MaskData = WebSocketParser.BuildMaskKey(); var frame = WebSocketParser.BuildFrameData(remark, out int length); int res = SendRaw(frame.AsMemory(0, length)); WebSocketParser.Return(frame); return res; } public int SendFrameText(string txt) { return SendFrameText(txt.ToBytes()); } public int SendFrameText(byte[] buffer) { return SendFrame(new WebSocketFrameRemarkInfo { Opcode = WebSocketFrameInfo.EnumOpcode.Text, Data = buffer }); } public int SendFrameBinary(byte[] buffer) { return SendFrame(new WebSocketFrameRemarkInfo { Opcode = WebSocketFrameInfo.EnumOpcode.Binary, Data = buffer }); } public int SendFramePoing() { return SendRaw(WebSocketParser.BuildPingData()); } public int SendFramePong() { return SendRaw(WebSocketParser.BuildPongData()); } public int SendFrameClose(WebSocketFrameInfo.EnumCloseStatus status) { return SendFrame(new WebSocketFrameRemarkInfo { Opcode = WebSocketFrameInfo.EnumOpcode.Close, Data = ((ushort)status).ToBytes() }); } public void Dispose() { CloseClientSocket(); GC.Collect(); //GC.SuppressFinalize(this); } } public sealed class AsyncServerUserToken { public Socket TargetSocket { get; set; } /// /// 当前帧数据 /// public WebSocketFrameInfo FrameInfo = null; /// /// 当前帧的数据下标 /// public int FrameIndex { get; set; } = 0; /// /// 数据帧缓存 /// public ReceiveDataBuffer FrameBuffer { get; } = new ReceiveDataBuffer(); /// /// 当前帧的数据类型 /// public WebSocketFrameInfo.EnumOpcode Opcode { get; set; } public string SecWebSocketKey { get; set; } public byte[] PoolBuffer { get; set; } public void Clear() { TargetSocket?.SafeClose(); TargetSocket = null; PoolBuffer = Helper.EmptyArray; //GC.Collect(); //GC.SuppressFinalize(this); } } }