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 } } |