v7.3.2018.0614   重构高性能资源池,减少GC压力,增加线程池,让异步任务得到平等竞争CPU的机会
大石头 编写于 2018-06-14 17:56:44
X
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using NewLife.Collections;
using NewLife.Net.Common;

namespace NewLife.Net.Sockets
{
    /// <summary>网络事件参数</summary>
    public class NetEventArgs : SocketAsyncEventArgs, /*ISafeStackItem,*/ IDisposable
    {
        #region 属性
        static Int32 _gid;

        // ID变大后,可能达到最大值,然后变为-1,再变为0,所以不用担心
        private Int32 _ID = ++_gid;
        /// <summary>编号</summary>
        public Int32 ID { get { return _ID; } set { _ID = value; } }

        private Int32 _Times;
        /// <summary>使用次数</summary>
        private Int32 Times { get { return _Times; } set { _Times = value; } }

        private ISocket _Socket;
        /// <summary>当前对象的使用者,默认就是从对象池<see cref="Pool"/>中借出当前网络事件参数的那个SocketBase。
        /// 比如,如果是Server程序,那么它往往就是与客户端通讯的那个Socket(TcpSession)。
        /// 在TcpServer中,它就是TcpSession。
        /// </summary>
        public ISocket Socket { get { return _Socket; } set { _Socket = value; } }

        private ISocketClient _Session;
        /// <summary>Socket会话</summary>
        public ISocketClient Session { get { return _Session; } set { _Session = value; } }

        private Exception _Error;
        /// <summary>异常信息</summary>
        public Exception Error { get { return _Error; } set { _Error = value; } }

        /// <summary>远程IP终结点</summary>
        public IPEndPoint RemoteIPEndPoint { get { return base.RemoteEndPoint as IPEndPoint; } set { base.RemoteEndPoint = value; } }

        private Boolean _Cancel;
        /// <summary>是否取消后续操作</summary>
        public Boolean Cancel { get { return _Cancel; } set { _Cancel = value; } }
        #endregion

        #region 构造
        /// <summary>析构</summary>
        ~NetEventArgs()
        {
            Dispose(false);
        }

        void IDisposable.Dispose()
        {
            Dispose(true);
        }

        Boolean disposed;
        void Dispose(Boolean disposing)
        {
            if (disposed) return;
            disposed = true;

            if (disposing) GC.SuppressFinalize(this);

            //XTrace.WriteLine("{0}被抛弃!{1} {2}", ID, LastOperation, RemoteIPEndPoint);

            //! 清空缓冲区,这一点非常非常重要,内部有个重叠数据对象,挂在一个全局对象池上,它会Pinned住数据缓冲区,这里必须清空被Pinned住的缓冲区
            SetBuffer(0);

            // 断开所有资源的链接
            _buffer = null;

            _Socket = null;
            _Session = null;
            _Error = null;

            base.Dispose();
        }
        #endregion

        #region 缓冲区
        /// <summary>采用弱引用,及时清理不再使用的内存,避免内存泄漏。</summary>
        private WeakReference<Byte[]> _buffer;

        /// <summary>设置缓冲区。
        /// 为了避免频繁分配内存,可以为每一个事件参数固定挂载一个缓冲区,达到重用的效果。
        /// 但是对于TcpServer的Accept来说,不能设置缓冲区,否则客户端连接的时候不会触发Accept事件,
        /// 而必须等到第一个数据包的到来才触发,那时缓冲区里面同时带有第一个数据包的数据。
        /// 
        /// 所以,考虑把缓冲内置一份到外部,进行控制。
        /// </summary>
        /// <param name="size"></param>
        internal void SetBuffer(Int32 size)
        {
            // 销毁时,使用中是无法释放的
            if (disposed) return;

            if (size > 0)
            {
                // 没有缓冲区,或者大小不相同时,重新设置
                if (Buffer == null || Count != size)
                {
                    // 没有缓冲区,或者大小不够时,重新分配
                    Byte[] _buf = _buffer == null ? null : _buffer.Target;
                    if (_buf == null || _buf.Length < size) _buffer = _buf = new Byte[size];

                    SetBuffer(_buf, 0, size);
                }
            }
            else
            {
                // 事件内有缓冲区时才清空,不管它多长,必须清空
                if (Buffer != null) SetBuffer(null, 0, 0);
            }
        }
        #endregion

        #region 对象池
        private static ObjectPool<NetEventArgs> _Pool;
        /// <summary>套接字事件参数池。静态,所有实例共享使用</summary>
        public static ObjectPool<NetEventArgs> Pool { get { return _Pool ?? (_Pool = new ObjectPool<NetEventArgs>() { Max = 1000 }); } }

        private static BufferPool bpool = new BufferPool(2000, 1500);

        /// <summary>从池里拿一个对象。回收原则参考<see cref="Push"/></summary>
        /// <returns></returns>
        public static NetEventArgs Pop()
        {
            var e = Pool.Pop();
            bpool.Pop(e);
            e.Times++;

            return e;
        }

        /// <summary>把对象归还到池里</summary>
        /// <remarks>
        /// 网络事件参数使用原则:
        /// 1,得到者负责回收(通过方法参数得到)
        /// 2,正常执行时自己负责回收,异常时顶级或OnError负责回收
        /// 3,把回收责任交给别的方法
        /// 4,事件订阅者不允许回收,不允许另作他用
        /// </remarks>
        /// <param name="e"></param>
        public static void Push(NetEventArgs e)
        {
            if (e == null) return;

            e.Error = null;
            e.UserToken = null;
            e.Socket = null;
            e.Session = null;
            e.AcceptSocket = null;
            e.RemoteEndPoint = null;

            // 清空缓冲区,避免事件池里面的对象占用内存
            //e.SetBuffer(0);
            bpool.Push(e);

            Pool.Push(e);
        }
        #endregion

        #region 辅助
        /// <summary>从接收缓冲区拿字符串,UTF-8编码</summary>
        /// <returns></returns>
        public String GetString(Encoding encoding = null)
        {
            if (Buffer == null || Buffer.Length < 1 || BytesTransferred < 1) return null;

            if (encoding == null) encoding = Encoding.UTF8;
            return encoding.GetString(Buffer, Offset, BytesTransferred);
        }

        /// <summary>从接收缓冲区获取一个流,该流可用于读取已接收数据,写入数据时向远端发送数据。该流应该在持有事件参数期内使用,否则可能产生冲突。</summary>
        /// <returns></returns>
        public Stream GetStream()
        {
            if (Buffer == null || Buffer.Length < 1 || BytesTransferred < 1) return null;

            Stream ms = new MemoryStream(Buffer, Offset, BytesTransferred);
            return new SocketStream(AcceptSocket, ms, RemoteEndPoint);
        }

        /// <summary>将接收缓冲区中的数据写入流</summary>
        /// <param name="stream"></param>
        public void WriteTo(Stream stream)
        {
            if (Buffer == null || Buffer.Length < 1 || BytesTransferred < 1) return;

            stream.Write(Buffer, Offset, BytesTransferred);
        }

        /// <summary>已重载。</summary>
        /// <returns></returns>
        public override string ToString()
        {
            // 不要取字符串,那样影响效率
            //return String.Format("[{0}]{1}", LastOperation, GetString());

            if (Error != null)
                return String.Format("[{0}]{1} {2}", ID, LastOperation, Error.Message);
            else if (SocketError != SocketError.Success)
                return String.Format("[{0}]{1} {2}", ID, LastOperation, SocketError);
            else
                return String.Format("[{0}]{1} BytesTransferred={2}", ID, LastOperation, BytesTransferred);
        }
        #endregion
    }
}