using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.Contracts; using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; using System.IO; namespace Rocky.Net { public class AsyncTcpListener { #region Fields public event ErrorEventHandler Error; public event EventHandler Started; public event EventHandler Stopped; public event EventHandler<TcpEventArgs> SessionFull; public event EventHandler<TcpEventArgs> SessionStart; public event EventHandler<TcpEventArgs> SessionSent; public event EventHandler<TcpEventArgs> SessionReceived; public event EventHandler<TcpEventArgs> SessionEnd; private volatile bool _isListening; private ushort _maxSession; private IPEndPoint _boundEndPoint; private Socket _sock; private ConcurrentDictionary<int, SessionState> _sessions; private BufferSegment _bufferManager; #endregion #region Properties public bool IsListening { get { return _isListening; } } public IPEndPoint BoundEndPoint { get { return _boundEndPoint; } } public Socket Server { get { return _sock; } } public ICollection<SessionState> Clients { get { return _sessions.Values; } } #endregion #region Constructor /// <summary> /// 构造函数 /// </summary> /// <param name="endpoint">服务器端监听的地址和端口号</param> /// <param name="maxClient">服务器容纳Session的最大数</param> /// <param name="bufferSize">服务器每个Session的缓冲区大小</param> public AsyncTcpListener(IPEndPoint endpoint, ushort maxClient = 100, BufferSizeof? bufferSize = null) { Contract.Requires(endpoint != null); _maxSession = maxClient; _boundEndPoint = endpoint; int lev = (int)(_maxSession * 0.3); _sessions = new ConcurrentDictionary<int, SessionState>(lev, _maxSession); if (bufferSize != null) { _bufferManager = new BufferSegment((int)bufferSize.Value, _maxSession * 2); } } #endregion #region EventMethods protected virtual void OnError(ErrorEventArgs e) { if (Error != null) { Error(this, e); } } protected virtual void OnStarted(EventArgs e) { _isListening = true; if (Started != null) { Started(this, e); } } protected virtual void OnStopped(EventArgs e) { _isListening = false; if (Stopped != null) { Stopped(this, e); } } protected virtual void OnSessionFull(TcpEventArgs e) { if (SessionFull != null) { SessionFull(this, e); } } protected virtual void OnSessionStart(TcpEventArgs e) { if (SessionStart != null) { SessionStart(this, e); } } protected virtual void OnSessionSent(TcpEventArgs e) { if (SessionSent != null) { SessionSent(this, e); } } protected virtual void OnSessionReceived(TcpEventArgs e) { if (SessionReceived != null) { SessionReceived(this, e); } } protected virtual void OnSessionEnd(TcpEventArgs e) { if (SessionEnd != null) { SessionEnd(this, e); } } #endregion #region PublicMethods public void Start() { Contract.Requires(!this.IsListening, "服务已运行"); _sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); _sock.Bind(_boundEndPoint); _sock.Listen(_maxSession); _sock.BeginAccept(AcceptCallback, _sock); OnStarted(EventArgs.Empty); } public void Stop() { Contract.Requires(this.IsListening, "服务已停止"); //这个条件语句,一定要在关闭所有客户端以前 OnStopped(EventArgs.Empty); //关闭连接,否则客户端会认为是强制关闭 if (_sock.Poll(-1, SelectMode.SelectRead)) { _sock.Shutdown(SocketShutdown.Both); } foreach (var session in _sessions.Values) { session.Abandon(); } _sessions.Clear(); _sock.Close(); } public bool TryGet(int sessionID, out SessionState session) { return _sessions.TryGetValue(sessionID, out session); } public IAsyncResult Send(SessionState session) { Contract.Requires(this.IsListening, "服务已停止"); var buffer = session.GetBuffer(FileAccess.Write); return session.Client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallback, session); } public void Abandon(SessionState session) { Contract.Requires(this.IsListening, "服务已停止"); SessionState current; if (_sessions.TryRemove(session.SessionID, out current)) { OnSessionEnd(new TcpEventArgs(session)); current.Abandon(); } } #endregion #region ProtectedMethods protected virtual void AcceptCallback(IAsyncResult ar) { if (!_isListening) { return; } Socket serverSock = (Socket)ar.AsyncState; try { Socket clientSock = serverSock.EndAccept(ar); if (_sessions.Count >= _maxSession) { var session = new SessionState(clientSock, null); OnSessionFull(new TcpEventArgs(session)); //Must do it 服务器满了,必须关闭新来的客户端连接 session.Abandon(); } else { var session = new SessionState(clientSock, _bufferManager); _sessions.GetOrAdd(session.SessionID, session); //开始接受来自该客户端的数据 var buffer = session.GetBuffer(FileAccess.Read); clientSock.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, session); OnSessionStart(new TcpEventArgs(session)); } _sock.BeginAccept(AcceptCallback, _sock); } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } protected virtual void ReceiveCallback(IAsyncResult ar) { SessionState session = (SessionState)ar.AsyncState; Socket client = session.Client; try { //因为两次开始异步接收,所以当客户端退出的时候会执行两次EndReceive int recv = client.EndReceive(ar); SessionState current; //正常关闭 if (!_sessions.TryGetValue(session.SessionID, out current) || recv == 0) { goto abandon; } var e = session.GetSyncArgs(FileAccess.Read); client.ReceiveSync(e, false); if (e.IsShutdown) { goto abandon; } OnSessionReceived(new TcpEventArgs(session)); var buffer = session.GetBuffer(FileAccess.Read); client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, session); return; abandon: session.Status = StatusType.NormalExit; Abandon(session); } catch (SocketException ex) { //客户端强制关闭 if (ex.ErrorCode == 10054) { session.Status = StatusType.ExceptionExit; Abandon(session); return; } OnError(new ErrorEventArgs(ex)); } catch (ObjectDisposedException ex) { //当调用Abandon()时,会结束数据接收,但是数据接收处理中会调用int recv = client.EndReceive(ar);就访问了Abandon()已经处置的对象 //这里的实现不够优雅 if (ex != null) { ex = null; //DoNothing; } } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } protected virtual void SendCallback(IAsyncResult ar) { SessionState session = (SessionState)ar.AsyncState; Socket client = session.Client; try { long sent = client.EndSend(ar); if (sent < 1L) { return; } var e = session.GetSyncArgs(FileAccess.Write); sent = client.SendSync(e, false); OnSessionSent(new TcpEventArgs(session)); } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } #endregion } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.Contracts; using System.Net; using System.Net.Sockets; using System.IO; namespace Rocky.Net { public class AsyncTcpClient : Disposable { #region Fields public event ErrorEventHandler Error; public event EventHandler<TcpEventArgs> Connected; public event EventHandler<TcpEventArgs> Sent; public event EventHandler<TcpEventArgs> Received; public event EventHandler<TcpEventArgs> Disconnected; private bool _isConnected; private SessionState _session; #endregion #region Properties public bool IsConnected { get { return _isConnected; } } public SessionState Session { get { return _session; } } #endregion #region Constructor public AsyncTcpClient() { _session = new SessionState(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)); } protected override void DisposeInternal(bool disposing) { if (disposing) { Session.Abandon(); _isConnected = false; } Connected = null; Sent = null; Received = null; Disconnected = null; } #endregion #region EventMethods protected virtual void OnError(ErrorEventArgs e) { if (Error != null) { Error(this, e); } } protected virtual void OnConnected(TcpEventArgs e) { _isConnected = true; if (Connected != null) { Connected(this, e); } } protected virtual void OnSent(TcpEventArgs e) { if (Sent != null) { Sent(this, e); } } protected virtual void OnReceived(TcpEventArgs e) { if (Received != null) { Received(this, e); } } protected virtual void OnDisconnected(TcpEventArgs e) { _isConnected = false; if (Disconnected != null) { Disconnected(this, e); } } #endregion #region PublicMethods public IAsyncResult Connect(string host) { Contract.Requires(!this.IsConnected, "已连接服务器"); base.CheckDisposed(); var ipe = SocketHelper.ParseEndPoint(host); return _session.Client.BeginConnect(ipe, ConnectCallback, _session.Client); } public IAsyncResult Disconnect() { Contract.Requires(this.IsConnected, "未连接服务器"); base.CheckDisposed(); _session.Client.Shutdown(SocketShutdown.Both); return _session.Client.BeginDisconnect(true, DisconnectCallback, _session.Client); } public IAsyncResult Send() { Contract.Requires(this.IsConnected, "未连接服务器"); base.CheckDisposed(); var buffer = _session.GetBuffer(FileAccess.Write); return _session.Client.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, SendCallback, _session.Client); } #endregion #region ProtectedMethods protected virtual void ConnectCallback(IAsyncResult ar) { Socket sock = (Socket)ar.AsyncState; try { sock.EndConnect(ar); var buffer = _session.GetBuffer(FileAccess.Read); _session.Client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, _session.Client); OnConnected(new TcpEventArgs(_session)); } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } protected virtual void DisconnectCallback(IAsyncResult ar) { Socket sock = (Socket)ar.AsyncState; try { sock.EndDisconnect(ar); OnDisconnected(new TcpEventArgs(_session)); } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } protected virtual void ReceiveCallback(IAsyncResult ar) { Socket remote = (Socket)ar.AsyncState; try { int recv = remote.EndReceive(ar); if (recv == 0) { goto disconnected; } var e = _session.GetSyncArgs(FileAccess.Read); remote.ReceiveSync(e, false); if (e.IsShutdown) { goto disconnected; } OnReceived(new TcpEventArgs(_session)); var buffer = _session.GetBuffer(FileAccess.Read); remote.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, remote); return; disconnected: _session.Status = StatusType.NormalExit; OnDisconnected(new TcpEventArgs(_session)); } catch (SocketException ex) { if (ex.ErrorCode == 10054) { _session.Status = StatusType.ExceptionExit; OnDisconnected(new TcpEventArgs(_session)); return; } OnError(new ErrorEventArgs(ex)); } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } protected virtual void SendCallback(IAsyncResult ar) { Socket remote = (Socket)ar.AsyncState; try { long sent = remote.EndSend(ar); if (sent < 1L) { return; } var e = _session.GetSyncArgs(FileAccess.Write); sent = remote.SendSync(e, false); OnSent(new TcpEventArgs(_session)); } catch (Exception ex) { OnError(new ErrorEventArgs(ex)); } } #endregion } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.Contracts; using System.Net; using System.Net.Sockets; using System.IO; namespace Rocky.Net { /// <summary> /// 建议使用BinaryWriter/BinaryReader 来包装Stream /// </summary> public class SessionState : Disposable { #region Fields private readonly BufferSegment BufferManager; private bool _reuseSocket; private Socket _sock; private byte[] _sendLength, _receiveLength; private MemoryStream _sendStream, _receiveStream; private SocketSyncArgs _sendArgs, _receiveArgs; #endregion #region Properties public int SessionID { get { return _sock.Handle.ToInt32(); } } public Socket Client { get { return _sock; } } public StatusType Status { get; set; } #endregion #region Constructor protected internal SessionState(Socket client, BufferSegment bufferManager = null) { Contract.Requires(client != null); _sock = client; int length = sizeof(int); _sendLength = new byte[length]; _receiveLength = new byte[length]; if (bufferManager == null) { _reuseSocket = true; _sendStream = new MemoryStream(); _receiveStream = new MemoryStream(); } else { BufferManager = bufferManager; _sendStream = (BufferedMemoryStream)BufferManager.Take(); _receiveStream = (BufferedMemoryStream)BufferManager.Take(); } _sendArgs = new SocketSyncArgs(); _receiveArgs = new SocketSyncArgs(); } protected override void DisposeInternal(bool disposing) { if (disposing) { //1.Call shutdown with how=SD_SEND. //2.Call recv until zero returned, or SOCKET_ERROR. //3.Call closesocket. if (_sock.Connected) { _sock.Shutdown(SocketShutdown.Send); } if (_reuseSocket) { _sock.Disconnect(true); } else { _sock.Close(); } } _sendStream.Dispose(); _receiveStream.Dispose(); _receiveArgs = _sendArgs = null; } public void Abandon() { this.Dispose(); } #endregion #region Methods public bool Equals(SessionState obj) { return this.SessionID == obj.SessionID; } /// <summary> /// 使用Socket对象的Handle值作为HashCode,它具有良好的线性特征. /// </summary> /// <returns></returns> public override int GetHashCode() { return this.SessionID; } public override string ToString() { return string.Format("SessionID:{0};Remote={1}", this.SessionID, _sock.RemoteEndPoint); } internal byte[] GetBuffer(FileAccess access) { switch (access) { case FileAccess.Read: return _receiveLength; case FileAccess.Write: var e = this.GetSyncArgs(access); var pack = e.GetPackets()[1]; _sendLength.SetBytes(0, (int)pack.ContentLength); return _sendLength; default: throw new NotSupportedException("FileAccess.ReadWrite"); } } internal SocketSyncArgs GetSyncArgs(FileAccess access) { long length; switch (access) { case FileAccess.Read: length = _receiveLength.ToInt32(0); _receiveStream.Position = 0L; _receiveStream.SetLength(length); _receiveArgs.SetPackets(_receiveStream, length); return _receiveArgs; case FileAccess.Write: length = _sendStream.Position; if (length == 0L) { throw new InvalidOperationException("空包"); } _sendStream.Position = 0L; _sendStream.SetLength(length); _sendArgs.SetPackets(_sendStream, length); return _sendArgs; default: throw new NotSupportedException("FileAccess.ReadWrite"); } } public Stream GetStream(FileAccess access) { base.CheckDisposed(); long length; switch (access) { case FileAccess.Read: length = _receiveStream.Position; _receiveStream.Position = 0L; _receiveStream.SetLength(length); return _receiveStream; case FileAccess.Write: //扩充buffer _sendStream.Position = 0L; _sendStream.SetLength(-1L); return _sendStream; default: throw new NotSupportedException("FileAccess.ReadWrite"); } } #endregion } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Rocky.Net { public enum StatusType { /// <summary> /// 正常 /// </summary> Normal, /// <summary> /// 超时,待移除 /// </summary> Timeout, /// <summary> /// 正常断开 /// </summary> NormalExit, /// <summary> /// 异常断开 /// </summary> ExceptionExit } }
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Diagnostics.Contracts; namespace Rocky.Net { /// <summary> /// 网络通讯事件参数,包含了激发该事件的会话对象 /// </summary> public class TcpEventArgs : EventArgs { public SessionState Session { get; private set; } public TcpEventArgs(SessionState session) { Contract.Requires(session != null); this.Session = session; } } }