一共有两个类:TcpClient和Session
Session类负责数据的接受并向外发布事件,主要代码如下:
/// <summary> /// 构造函数 /// </summary> /// <param name="cliSock">会话使用的Socket连接</param> public Session(Socket cliSock) { Debug.Assert(cliSock != null); _cliSock = cliSock; _id = new SessionId((int)cliSock.Handle); byteRecvBuffer = new byte[DefaultBufferSize]; IsConnected = true; thrdRecvData = new Thread(NotifyData); thrdRecvData.IsBackground = true; thrdRecvData.Start(); //开始接受来自该客户端的数据 } public Session(Socket cliSock, DatagramResolver resolver) : this(cliSock) { this._resolver = resolver; } private void ReceiveData(IAsyncResult iar) { Socket client = (Socket)iar.AsyncState; try { //如果两次开始了异步的接收,所以当客户端退出的时候 //会两次执行EndReceive int recv = client.EndReceive(iar); if (recv == 0) { //正常的关闭 this.TypeOfExit = ExitType.NormalExit; IsConnected = false; if (SessionShutDown != null) { SessionShutDown(this, new NetEventArgs(this)); } return; } for (int i = 0; i < recv; i++) { listRecvByte.Add(byteRecvBuffer[i]); } //继续接收来自来客户端的数据 client.BeginReceive(byteRecvBuffer, 0, byteRecvBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveData), client); } catch (SocketException ex) { //客户端退出 if (10054 == ex.ErrorCode) { //客户端强制关闭 this.TypeOfExit = ExitType.ExceptionExit; IsConnected = false; if (SessionShutDown != null) { SessionShutDown(this, new NetEventArgs(this)); } //CloseClient(client, Session.ExitType.ExceptionExit); } } } private void NotifyData() { _cliSock.BeginReceive(byteRecvBuffer, 0, byteRecvBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveData), _cliSock); bool flag = true; List<byte> listBufferByte = new List<byte>(); if (!IsConnected) { thrdRecvData = null; } while (IsConnected || listRecvByte.Count > 0) { Thread.Sleep(1); int dataCount = listRecvByte.Count;//获取当前已接受byte数据的数量存到变量中,以防在代码执行过程中接受到的byte总数量会更改 if (dataCount > 0) { //先去除缓存中的数据,一边本次处理后发布数据的存储 listBufferByte.Clear(); //遍历当前以获取的byte,查找分隔符所在索引 for (int i = 0; i < dataCount; i++) { flag = true; listBufferByte.Add(listRecvByte[i]);//为若查找到分隔符之后,向外发布数据接受事件的数据参数做准备 if (listRecvByte[i] == _resolver.ByteEndTag[0])//找到了分隔符第一个byte的相等位置 { //循环分隔符,继续判断是否对应 for (int j = 1; j < _resolver.ByteEndTag.Length; j++) { //若已超出当前已接受数据的范围,则说明匹配失败 if (i + j > dataCount - 1) { flag = false; break; } //若分隔符后面的byte与接受数据的相应索引位置的数据不一致,则说明匹配失败 if (listRecvByte[i + j] != _resolver.ByteEndTag[j]) { flag = false; break; } } //若不匹配,继续遍历当前已接受数据 if (!flag) { continue; } //说明已匹配上分隔符,去除当前缓存中的尾部分隔符 listBufferByte.RemoveRange(listBufferByte.Count - 1, 1); //Need Deep Copy.因为需要保证多个不同报文独立存在 ICloneable copySession = (ICloneable)this; Session clientSession = (Session)copySession.Clone(); //发布数据 clientSession.Datagram = listBufferByte.ToArray(); //去除当前已接受数据中的上述已发布的数据片段,并包括分隔符的去除 listRecvByte.RemoveRange(0, i + _resolver.ByteEndTag.Length); //发布一个报文消息 if (ReceivedDatagram != null) { ReceivedDatagram(this, new NetEventArgs(clientSession)); } break; } } } } }
TcpClient主要代码如下:
/// <summary> /// 连接服务器 /// </summary> /// <param name="ip">服务器IP地址</param> /// <param name="port">服务器端口</param> public virtual void Connect(string ip, int port) { if (IsConnected) { //重新连接 Debug.Assert(_session != null); Close(); } Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint iep = new IPEndPoint(IPAddress.Parse(ip), port); newsock.BeginConnect(iep, new AsyncCallback(Connected), newsock); } /// <summary> /// 建立Tcp连接后处理过程 /// </summary> /// <param name="iar">异步Socket</param> protected virtual void Connected(IAsyncResult iar) { Socket socket = (Socket)iar.AsyncState; socket.EndConnect(iar); //创建新的会话 _session = new Session(socket, _resolver); _session.SessionShutDown += new NetEvent(_session_SessionShutDown); _session.ReceivedDatagram += new NetEvent(_session_ReceivedDatagram); _isConnected = true; //触发连接建立事件 if (ConnectedServer != null) { ConnectedServer(this, new NetEventArgs(_session)); } } void _session_ReceivedDatagram(object sender, NetEventArgs e) { this.ReceivedDatagram(sender, e); } void _session_SessionShutDown(object sender, NetEventArgs e) { this.DisConnectedServer(sender, e); }
问题是当Socket异步接受到数据,调用方法ReceiveData(IAsyncResult iar)的时候,会将在listRecvByte添加数据,这时候的Notify线程方法查看listRecvByte中是否有完整报文数据,若有,则提取数据并向外发布,但是当Notify方法运行到
if (ReceivedDatagram != null) { ReceivedDatagram(this, new NetEventArgs(clientSession)); }
的时候,有时候能成功触发事件,但有时候却发现ReceivedDatagram事件为null,但是我明明已经在TcpClient类中的Connected方法中已经订阅了事件
_session.SessionShutDown += new NetEvent(_session_SessionShutDown); _session.ReceivedDatagram += new NetEvent(_session_ReceivedDatagram);
为什么会出现这样事件订阅丢失的情况?