socket粘包处理
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 } } |
标签: .Net
恋花望月轻轻唱 听雨随风悄悄追