none
让人头疼的Socket断开Disconnect方法,前面是基于SocketAsyncEventArgs的操作。 RRS feed

  • 问题

  • 我做了一个以TCP客户端方式连接到服务的异步类,期望它能够高效的运行,可是连接、发送、接收都很顺利,在断开的时候却是愁坏了我。
    当我使用Disconnect或者DisconnectAsync方法的时候,为了使对象能够重用,设定了重用属性为true。但是当我想要断开的时候,却半天都没有断开。
    代码送上,大家看看为什么。

    public interface ITCPAsyncClient
        {
            void Connect();
            void Disconnect();
            void Send(byte[] sendBytes);
            void SetBuffer(byte[] buffer, int offset, int buffersize);
            event ConnectionEventHandler Received;
            event ConnectionEventHandler Connected;
            event ConnectionEventHandler Disconnected;
            event ConnectionEventHandler OutPutString;
        }
    
        public delegate void ConnectionEventHandler(object sender, EventArgs e);
    
        /// <summary>
        /// 这个类的目标是,生成一个socket,一个readEventArg,一个writeEventArg,并为他们分配缓冲区。
        /// 以TCP客户端的方式对外进行连接,响应发送接收等操作。连接断开等操作。全部使用异步操作。
        /// </summary>
        public class TCPAsyncClient : ITCPAsyncClient
        {
            Socket m_socket;
            SocketAsyncEventArgs m_readEventArg;
            SocketAsyncEventArgs m_writeEventArg;
            object m_userToken;
            int m_key;
            int m_buffersize;
    
            bool m_bIsConnect = false;
            /// <summary>
            /// 构造函数,初始化各个变量,一个读取参数,一个写入参数,共用一个UserToken,
            /// </summary>
            public TCPAsyncClient()
            {
                // 初始化过程,初始化Socket,EventArg,UserToken
                m_socket = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                m_userToken = new object();
                m_readEventArg = new SocketAsyncEventArgs();
                m_readEventArg.UserToken = m_userToken;
                m_readEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                m_writeEventArg = new SocketAsyncEventArgs();
                m_writeEventArg.UserToken = m_userToken;
                m_writeEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                m_buffersize = -1;
            }
    
            /// <summary>
            /// 设置远程地址,分配的Key
            /// </summary>
            /// <param name="serverEndPoint">远程地址</param>
            /// <param name="key">分配的Key</param>
            public TCPAsyncClient(IPEndPoint serverEndPoint, int key)
                : this()
            {
                m_readEventArg.RemoteEndPoint = serverEndPoint;
                m_writeEventArg.RemoteEndPoint = serverEndPoint;
                this.m_key = key;
            }
    
            void IO_Completed(object sender, SocketAsyncEventArgs e)
            {
                switch (e.LastOperation)
                {
                    case SocketAsyncOperation.Connect:
                        ProcessConnect(e);
                        break;
                    case SocketAsyncOperation.Receive:
                        ProcessReceive(e);
                        break;
                    case SocketAsyncOperation.Send:
                        break;
                    case SocketAsyncOperation.Disconnect:
                        ProcessDisconnect(e);
                        break;
                    default:
                        OnOutPutString(new ConnectionEventArgs("some thing wrong!", null, null));
                        break;
                }
            }
    
            /// <summary>
            /// 设置缓冲区,读写共用缓冲区,一个使用前面,一个使用后面,资源回收的时候不用放弃,一次设置多次使用
            /// 设置过程中,如果offset大于buffer的lenght,会抛出异常,如果buffersize加offset大于buffer的lenght,也会抛出异常
            /// </summary>
            /// <param name="buffer">缓冲区的对象</param>
            /// <param name="offset">偏移量,该值设定从给定缓冲区的那个位置开始使用缓冲区</param>
            /// <param name="buffersize">分配给本类的实例对象的缓冲区大小</param>
            public void SetBuffer(byte[] buffer, int offset, int buffersize)
            {
                if (buffer == null)
                    throw new ArgumentNullException("buffer", "缓冲区不可分配空");
                if (offset > buffer.Length)
                    throw new ArgumentOutOfRangeException("offset", "偏移量超出了缓冲区的大小");
                if (offset + buffersize > buffer.Length)
                    throw new ArgumentOutOfRangeException("buffersize", "分配大小与偏移量之和超出了缓冲区的大小");
                m_buffersize = buffersize;
                m_readEventArg.SetBuffer(buffer, offset, buffersize / 2);
                m_writeEventArg.SetBuffer(buffer, offset + buffersize / 2, buffersize - buffersize / 2);
            }
    
            /// <summary>
            /// 连接到设定的服务端,需要设定buffer和远程地址,否则抛出异常
            /// </summary>
            public void Connect()
            {
                if (m_bIsConnect)
                {
                    throw new Exception("Has Connected");
                }
                m_bIsConnect = true;
    
                if (m_readEventArg.RemoteEndPoint == null)
                {
                    throw new Exception("尚未设置远程IP地址");
                }
                if (m_readEventArg.Buffer == null || m_writeEventArg.Buffer == null)
                {
                    throw new Exception("尚未设置缓冲区");
                }
                // 开始连接,到目标服务器
                //m_socket = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                try
                {
                    bool willRaiseEvent = m_socket.ConnectAsync(m_readEventArg);
                    if (!willRaiseEvent)
                    {
                        ProcessConnect(m_readEventArg);
                    }
                }
                catch (Exception ex)
                {
                    OnOutPutString(new ConnectionEventArgs("连接失败", null, null));
                    OnOutPutString(new ConnectionEventArgs(ex.Message, null, null));
                }
            }
    
            /// <summary>
            /// 处理完成事件,对外触发连接事件,自身启动异步接收。如果连接失败,则触发断开过程
            /// </summary>
            /// <param name="EventArg">连接完成参数,对应ConnectAsync的参数</param>
            private void ProcessConnect(SocketAsyncEventArgs EventArg)
            {
                // 启动异步接收。
                // 发出连接事件。
                if (EventArg.SocketError == SocketError.Success)
                {
                    OnConnect(EventArg);
                    bool willRaiseEvent = m_socket.ReceiveAsync(m_readEventArg);
                    if (!willRaiseEvent)
                    {
                        ProcessReceive(m_readEventArg);
                    }
                }
                else
                {
                    Disconnect(1);
                    m_bIsConnect = false;
                }
            }
    
            /// <summary>
            /// 处理完成接收事件
            /// </summary>
            /// <param name="e">接收参数,来自ReceiveAsync</param>
            private void ProcessReceive(SocketAsyncEventArgs e)
            {
                // check if the remote host closed the connection
                //// 检查远程主机是否关闭连接
                //if (!m_bIsConnect)
                //    return;
                try
                {
                    if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
                    {
                        OnReceive(e);
    
                        // 清空缓冲区
                        OnOutPutString(new ConnectionEventArgs("再次启动对该客户端的数据接收", null, null));
                        bool willRaiseEvent = m_socket.ReceiveAsync(e);
                        if (!willRaiseEvent)
                        {
                            ProcessReceive(e);
                        }
                    }
                    else
                    {
                        OnOutPutString(new ConnectionEventArgs("服务端断开连接", null, null));
                        Disconnect(1);
                    }
                }
                catch (Exception ex)
                {
                    OnOutPutString(new ConnectionEventArgs("接收异常:" + ex.Message, null, null));
                    Disconnect(1);
                }
            }
    
            /// <summary>
            /// 断开连接,无论是主动还是被动,都会关联到这里。
            /// 如果发生异常很有可能多次
            /// </summary>
            public void Disconnect()
            {
                if (!m_bIsConnect)
                    throw new Exception("Don't Connected");
                Disconnect(1);
            }
    
            private void Disconnect(int key)
            {
                if (!m_bIsConnect)
                    return;
                // 处理断开,先将socket关闭ShutDown,然后再断开,Disconnect,使用参数true,以保证可以重用
                m_bIsConnect = false;
                try
                {
                    //m_socket.Shutdown(SocketShutdown.Both);
                    //m_writeEventArg.DisconnectReuseSocket = true;
                    //bool willRaiseEvent = m_socket.DisconnectAsync(m_writeEventArg);
                    //if (!willRaiseEvent)
                    //{
                    //    ProcessDisconnect(m_writeEventArg);
                    //}
                    m_socket.LingerState.Enabled = true;
                    m_socket.LingerState.LingerTime = 1;
                    m_socket.Shutdown(SocketShutdown.Both);
                    m_socket.Disconnect(true);
                    string ip = "";
                    if (m_readEventArg.RemoteEndPoint != null)
                    {
                        ip = ((IPEndPoint)m_readEventArg.RemoteEndPoint).Address.ToString() + ":"
                            + ((IPEndPoint)m_readEventArg.RemoteEndPoint).Port.ToString();
                    }
                    OnDisconnect(new ConnectionEventArgs(ip, this.m_key.ToString(), null));
                }
                catch (Exception ex)
                {
                    OnOutPutString(new ConnectionEventArgs(ex.Message, null, null));
                }
            }
    
            private void ProcessDisconnect(SocketAsyncEventArgs e)
            {
                string ip = "";
                if (e.RemoteEndPoint != null)
                {
                    ip = ((IPEndPoint)e.RemoteEndPoint).Address.ToString() + ":"
                        + ((IPEndPoint)e.RemoteEndPoint).Port.ToString();
                }
                OnDisconnect(new ConnectionEventArgs(ip, this.m_key.ToString(), null));
            }
    
            /// <summary>
            /// 发送数据,将来源数据发送到已连接的服务端
            /// </summary>
            /// <param name="sendBytes">要发送的数据</param>
            public void Send(byte[] sendBytes)
            {
                if (!m_bIsConnect)
                    throw new Exception("Don't Connected");
                if (!m_socket.Connected)
                    throw new Exception("Don't Connected");
                if (m_writeEventArg.Buffer == null)
                    throw new ArgumentNullException("buffer", "缓冲区尚未设置");
                if (sendBytes.Length > m_writeEventArg.Count)
                    throw new ArgumentOutOfRangeException("sendBytes", "发送字节数超出缓冲区");
                for (int i = 0; i < sendBytes.Length; i++)
                {
                    m_writeEventArg.Buffer[i + m_writeEventArg.Offset] = sendBytes[i];
                }
                m_writeEventArg.SetBuffer(m_writeEventArg.Offset, sendBytes.Length);
                bool willRaiseEvent = m_socket.SendAsync(m_writeEventArg);
                if (!willRaiseEvent)
                {
                    ProcessSend(m_writeEventArg);
                }
            }
    
            private void ProcessSend(SocketAsyncEventArgs m_writeEventArg)
            {
                // throw new NotImplementedException();
                // 如果发送失败,报到断开连接
                if (m_writeEventArg.SocketError != SocketError.Success)
                {
                    Disconnect();
                }
            }
    
            public event ConnectionEventHandler Received;
            public event ConnectionEventHandler Connected;
            public event ConnectionEventHandler Disconnected;
            public event ConnectionEventHandler OutPutString;
    
            /// <summary>
            /// 触发接收事件,实际上参数是SocketAsyncEventArgs,包含了所有的数据和节点信息
            /// </summary>
            /// <param name="e"></param>
            protected void OnReceive(EventArgs e)
            {
                if (Received != null)
                {
                    SocketAsyncEventArgs saea = e as SocketAsyncEventArgs;
    
                    string text = "";
                    if (saea.RemoteEndPoint != null)
                    {
                        text = ((IPEndPoint)saea.RemoteEndPoint).Address.ToString() + ":"
                            + ((IPEndPoint)saea.RemoteEndPoint).Port.ToString();
                    }
                    byte[] recvBytes = new byte[saea.BytesTransferred];
                    for (int i = 0; i < recvBytes.Length; i++)
                    {
                        recvBytes[i] = saea.Buffer[i + saea.Offset];
                    }
                    Received(this, new ConnectionEventArgs(text, this.m_key.ToString(), recvBytes));
                }
            }
    
            /// <summary>
            /// 触发连接完成事件,该事件包含连接的远程地址和为本地分配的Key
            /// </summary>
            /// <param name="e">来自ProcessConnect的参数</param>
            protected void OnConnect(EventArgs e)
            {
                if (Connected != null)
                {
                    SocketAsyncEventArgs saEArg = e as SocketAsyncEventArgs;
                    string ip = "";
                    if (saEArg.RemoteEndPoint != null)
                    {
                        ip = ((IPEndPoint)saEArg.RemoteEndPoint).Address.ToString() + ":"
                            + ((IPEndPoint)saEArg.RemoteEndPoint).Port.ToString();
                    }
    
                    Connected(this, new ConnectionEventArgs(ip, this.m_key.ToString(), null));
                }
            }
    
            protected void OnDisconnect(EventArgs e)
            {
                if (Disconnected != null)
                    Disconnected(this, e);
            }
    
            protected void OnOutPutString(EventArgs e)
            {
                if (OutPutString != null)
                    OutPutString(this, e);
            }
        }
    
        public class ConnectionEventArgs : EventArgs
        {
            // 输出字符串
            public string Text { get; private set; }
            // 事件来源者的Key
            public string Key { get; private set; }
            // 事件需要输出的字节
            public byte[] Bytes { get; private set; }
    
            public ConnectionEventArgs(string text, string key, byte[] bytes)
            {
                Text = text;
                Key = key;
                Bytes = bytes;
            }
        }

    2012年2月29日 8:17

全部回复

  • 附上命令行模式的测试程序:

    class Program
        {
            static void Main(string[] args)
            {
                new MainClass().Run();
            }
        }
    
        class MainClass
        {
            ITCPAsyncClient asyncClient;
            public MainClass()
            {
                IPEndPoint ip = new IPEndPoint(IPAddress.Parse("164.70.6.63"),9002);
                asyncClient = new TCPAsyncClient(ip, 1);
                asyncClient.Connected += new ConnectionEventHandler(DoEventThings);
                asyncClient.Received += new ConnectionEventHandler(DoEventThings);
                asyncClient.Disconnected +=new ConnectionEventHandler(DoEventThings);
                asyncClient.OutPutString += new ConnectionEventHandler(DoEventThings);
                byte[] buffer = new byte[1024];
                asyncClient.SetBuffer(buffer, 0, 1024);
            }
    
            void DoEventThings(object sender, EventArgs e)
            {
                //throw new NotImplementedException();
                string show = "";
                ConnectionEventArgs cnEArg = e as ConnectionEventArgs;
                if (cnEArg.Text != null)
                    show += cnEArg.Text + " ";
                if (cnEArg.Key != null)
                    show += cnEArg.Key + ":";
                if(cnEArg.Bytes!= null)
                    for (int i = 0; i < cnEArg.Bytes.Length; i++)
                    {
                        show += cnEArg.Bytes[i].ToString("X2") + " ";
                    }
                Console.WriteLine(show);
            }
    
            public void Run()
            {
                asyncClient.Connect();
    
                string input = "";
                while (true)
                {
                    Console.WriteLine("输入要发送的数据或者输入d回车断开连接");
                    input = Console.ReadLine();
                    if (input.ToLower() == "d")
                    {
                        asyncClient.Disconnect();
                        Console.WriteLine("看到断开提示后,输入c重新连接或者q退出程序");
                        input = Console.ReadLine();
                        if (input.ToLower() == "c")
                        {
                            asyncClient.Connect();
                            continue;
                        }
                        else
                        {
                            break;
                        }
                    }
                    else
                    {
                        if (input.Length == 0)
                            continue;
                        asyncClient.Send(Encoding.Default.GetBytes(input));
                    }
                }
            }
        }

    2012年2月29日 8:18
  • 没有人愿意试一下吗?大神们!
    2012年3月1日 2:54
  • Hi Arnu,

    关于Socket的问题,您可以尝试System.NET论坛:

    Network Class Library (System.Net)
    http://social.msdn.microsoft.com/Forums/en-us/ncl/threads/

    谢谢您的理解!


    Bob Shen [MSFT]
    MSDN Community Support | Feedback to us

    2012年3月2日 7:41
    版主