using System; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; namespace Player.Common.Sockets { /// <summary> /// Socket連接,雙向通信 /// </summary> public class TcpSocketConnection { #region 構造函數 /// <summary> /// 構造函數 /// </summary> /// <param name="socket">維護的Socket對象</param> /// <param name="server">維護此連接的服務對象</param> /// <param name="recLength">接受緩沖區大小</param> public TcpSocketConnection(Socket socket, TcpSocketServer server, int recLength) { _socket = socket; _server = server; _recLength = recLength; } #endregion #region 私有成員 private Socket _socket { get; } private bool _isRec { get; set; } = true; private TcpSocketServer _server { get; set; } = null; private bool _isClosed { get; set; } = false; private string _connectionId { get; set; } = Guid.NewGuid().ToString(); private ManualResetEvent allDone { get; set; } = new ManualResetEvent(false); /// <summary> /// 接收區大小,單位:字節 /// </summary> private int _recLength { get; set; } #endregion #region 外部接口 int headSize = 4;//包頭長度 固定4 byte[] surplusBuffer = null;//不完整的數據包,即用戶自定義緩沖區 /// <summary> /// 開始接受客戶端消息 /// </summary> public void StartRecMsg() { try { while (true) { allDone.Reset(); byte[] container = new byte[_recLength]; _socket.BeginReceive(container, 0, container.Length, SocketFlags.None, asyncResult => { try { //bytes 為系統緩沖區數據 //bytesRead為系統緩沖區長度 int bytesRead = _socket.EndReceive(asyncResult); if (bytesRead > 0) { if (surplusBuffer == null)//判斷是不是第一次接收,為空說是第一次 { surplusBuffer = new byte[bytesRead]; Array.Copy(container, 0, surplusBuffer, 0, bytesRead); } else { byte[] container1 = new byte[bytesRead]; Array.Copy(container, 0, container1, 0, bytesRead); surplusBuffer = surplusBuffer.Concat(container1).ToArray();//拼接上一次剩余的包,已經完成讀取每個數據包長度 } int haveRead = 0; //這里totalLen的長度有可能大於緩沖區大小的(因為 這里的surplusBuffer 是系統緩沖區+不完整的數據包) int totalLen = surplusBuffer.Length; while (haveRead <= totalLen) { //如果在N此拆解后剩余的數據包連一個包頭的長度都不夠 //說明是上次讀取N個完整數據包后,剩下的最后一個非完整的數據包 if (totalLen - haveRead < headSize) { byte[] byteSub = new byte[totalLen - haveRead]; //把剩下不夠一個完整的數據包存起來 Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead); surplusBuffer = byteSub; totalLen = 0; break; } //如果夠了一個完整包,則讀取包頭的數據 byte[] headByte = new byte[headSize]; Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//從緩沖區里讀取包頭的字節 int bodySize = BitConverter.ToInt32(headByte, 0);//從包頭里面分析出包體的長度 //這里的 haveRead=等於N個數據包的長度 從0開始;0,1,2,3....N //如果自定義緩沖區拆解N個包后的長度 大於 總長度,說最后一段數據不夠一個完整的包了,拆出來保存 if (haveRead + headSize + bodySize > totalLen) { byte[] byteSub = new byte[totalLen - haveRead]; Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead); surplusBuffer = byteSub; break; } if (bodySize < 2) { WebLogHelper.Error(LogType.SysLog, $"bytesRead:{bytesRead},bodySize:{bodySize}"); Close(); break; } //處理正常消息 byte[] recBytes = new byte[bodySize + headSize]; Array.Copy(surplusBuffer, haveRead, recBytes, 0, bodySize + headSize); //取出消息內容 HandleRecMsg?.Invoke(_server, this, recBytes); //依次累加當前的數據包的長度 haveRead = haveRead + headSize + bodySize; if (headSize + bodySize == bytesRead)//如果當前接收的數據包長度正好等於緩沖區長度,則待拼接的不規則數據長度歸0 { surplusBuffer = null;//設置空 回到原始狀態 totalLen = 0;//清0 } } } //開始下一輪接收 if (_isRec && IsSocketConnected()) { allDone.Set(); //StartRecMsg(); } } catch (ObjectDisposedException ) { } catch (Exception ex) { HandleException?.Invoke(ex); Close(); } }, null); allDone.WaitOne(); } } catch (Exception ex) { HandleException?.Invoke(ex); Close(); } } /// <summary> /// 發送數據 /// </summary> /// <param name="bytes">數據字節</param> public void Send(byte[] bytes) { try { _socket.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, asyncResult => { try { int length = _socket.EndSend(asyncResult); HandleSendMsg?.Invoke(_server, this, bytes); } catch (Exception ex) { HandleException?.Invoke(ex); } }, null); } catch (Exception ex) { HandleException?.Invoke(ex); } } /// <summary> /// 發送字符串(默認使用UTF-8編碼) /// </summary> /// <param name="msgStr">字符串</param> public void Send(string msgStr) { Send(Encoding.UTF8.GetBytes(msgStr)); } /// <summary> /// 發送字符串(使用自定義編碼) /// </summary> /// <param name="msgStr">字符串消息</param> /// <param name="encoding">使用的編碼</param> public void Send(string msgStr, Encoding encoding) { Send(encoding.GetBytes(msgStr)); } /// <summary> /// 連接標識Id /// 注:用於標識與客戶端的連接 /// </summary> public string ConnectionId { get { return _connectionId; } set { string oldConnectionId = _connectionId; _connectionId = value; _server?.SetConnectionId(this, oldConnectionId, value); } } /// <summary> /// 關閉當前連接 /// </summary> public void Close() { WebLogHelper.Error(LogType.SysLog, "自動關閉"); if (_isClosed) return; try { _isClosed = true; _server.RemoveConnection(this); _isRec = false; _socket.BeginDisconnect(false, (asyncCallback) => { try { _socket.EndDisconnect(asyncCallback); } catch (Exception ex) { HandleException?.Invoke(ex); } finally { _socket.Dispose(); } }, null); } catch (Exception ex) { WebLogHelper.Info(LogType.SysLog, "斷開連接報錯"); HandleException?.Invoke(ex); } finally { try { HandleClientClose?.Invoke(_server, this); } catch (Exception ex) { WebLogHelper.Info(LogType.SysLog, "調用關閉事件報錯"); HandleException?.Invoke(ex); } } } /// <summary> /// 判斷是否處於已連接狀態 /// </summary> /// <returns></returns> public bool IsSocketConnected() { return !((_socket.Poll(1000, SelectMode.SelectRead) && (_socket.Available == 0)) || !_socket.Connected); } #endregion #region 事件處理 /// <summary> /// 客戶端連接接受新的消息后調用 /// </summary> public Action<TcpSocketServer, TcpSocketConnection, byte[]> HandleRecMsg { get; set; } /// <summary> /// 客戶端連接發送消息后回調 /// </summary> public Action<TcpSocketServer, TcpSocketConnection, byte[]> HandleSendMsg { get; set; } /// <summary> /// 客戶端連接關閉后回調 /// </summary> public Action<TcpSocketServer, TcpSocketConnection> HandleClientClose { get; set; } /// <summary> /// 異常處理程序 /// </summary> public Action<Exception> HandleException { get; set; } #endregion } }