• 以异步的方式操作TCP/IP套接字——以异步方式实现简单的聊天室


    普通的TCP/IP开发方式大家都应该非常熟练,但在系统开发的时候往往会遇到问题。

    比如:在开发一个简单的聊天室的时候,一般情况下,Windows应用程序会处于同步方式运行,当监听的客户端越多,服务器的负荷将会越重,信息发送与接收都会受到影响。这时候,我们就应该尝试使用异步的TCP/IP通讯来缓解服务器的压力。

     

    下面以一个最简单的聊天室服务器端的例子来说明异步TCP/IP的威力,先开发一个ChatClient类作为客户管理的代理类,每当服务器接收到信息时,就会把信息处理并发送给每一个在线客户。

    void Main()
    {
             IPAddress ipAddress = IPAddress.Parse("127.0.0.1"
    );            //默认地址
             TcpListener tcpListener = new TcpListener(ipAddress,500
    );
             tcpListener.Start();
             while
    (isListen)                //以一个死循环来实现监听
             {
                    ChatClient chatClient = new
    ChatClient(tcpListener.AcceptTcpClient());    //调用一个ChatClient对象来实现监听

             }
             tcpListener.Stop();

    }

     

    ChatClient中存在着一个Hashtabel类的静态变量clients,此变量用来存贮在线的客户端信息,每当对一个客户端进行监听时,系统就生成一个ChatClient对象,然后在变量clients中加入此客户端的信息。在接收客户端信息时,信息会调用Receive(IAsyncResult async)方法,把接收到的信息发送给每一个在线客户。

    值得注意的是,每当接收到客户信息时,系统都会利用Stream.BeginRead()的方法去接收信息,然后把信息发送到每一个在线客户,这样做就可以利用异步的方式把信息进行接收,从而令主线程及早得到释放,提高系统的性能。

    public class ChatClient

    {
            private
    TcpClient _tcpClient;
            private byte
    [] byteMessage;
            private string _clientEndPoint;

            public volatile string message;
            public static Hashtable clients= new
    Hashtable();          //以此静态变量存处多个客户端地址
            
            public
    ChatClient(TcpClient tcpClient)
            {
                _tcpClient =
    tcpClient;
                _clientEndPoint =
    _tcpClient.Client.RemoteEndPoint.ToString();
                Console.WrtieLine("连接成功,客户端EndPoint为"+_clientEndPoint);

                ChatClient.clients.Add(_clientEndPoint, this
    );       //每创建一个对象,就会将客户端的ChatClient对象存入clients;

                byteMessage=new byte
    [_tcpClient.ReceiveBufferSize];

                lock (_tcpClient.GetStream())        //接收信息,使用lock避免数据冲突

                {

                     _tcpClient.GetStream().BeginRead(byteMessage, 0, _tcpClient.ReceiveBufferSize, new AsyncCallback(Receive), null);         

                  //就在此处使用异步的IO线程进行数据读取,这样每个一客户端的都处于一个IO线程中处理,使主线程及早得到释放

                  //这样做就缓解了服务器端压力。
                }
            }

            public void
    Receive(IAsyncResult iAsyncResult)
            {
                try

                {
                   
    int length;
                   
                    lock
    (_tcpClient.GetStream())    //信息接收,使用lock避免数据冲突
                    {
                         length=
    _tcpClient.GetStream().EndRead(iAsyncResult);
                    }
                    if (length < 1
    )
                    {
                         MessageBox.Show(_tcpClient.Client.RemoteEndPoint + "已经断线"
    );
                         clients.Remove(_tcpClient);
                         return
    ;
                    }

                    message=Encoding.Unicode.GetString(byteMessage,0
    ,length);
                    SendToEveryone(message);

                   //在此时我们可以在此处调用SendToEveryone方法,利用clients变量以Stream.Write方法为每个客户端发送信息。


                    lock
    (_tcpClient.GetStream())    //再次监听,使用lock避免数据冲突
                    {
                        _tcpClient.GetStream().BeginRead(byteMessage, 0, _tcpClient.ReceiveBufferSize, new AsyncCallback(Receive), null
    );

                       //再次调用Stream.BeginRead方法,以监听以下次客户的信息
                    }
                }
                catch
    (Exception ex)
                {
                     clients.Remove(_tcpClient);
                    _tcpClient.GetStream().Close();
                    _tcpClient.Close();
                }
            }

     

            //通过Send方法把信息转换成二进制数据,然后发送到客户端

             public void Send(string message)
             {
                try
               {
                  NetworkStream ns;
                  lock (_tcpClient.GetStream())
                  {
                     ns = _tcpClient.GetStream();
                  }
                  byte[] byteMessage = Encoding.ASCII.GetBytes(message);
                  ns.Write(byteMessage, 0, byteMessage.Length);
                  ns.Flush();
               }
               catch (Exception ex)
               {
                  MessageBox.Show(ex.Message);
               }
            }

            //由于客户端信息记录在HashTabel变量clients中,当信息接收后,就会通过此变量把信息发送给每一个在线客户。

            public void SendToEveryone(string message)
            {
                foreach (DictionaryEntry client in clients)
                {
                  ChatClient chatClient = (ChatClient)client.Value;
                  chatClient.Send(message);
                }
            }

    }

    测试结果:


    至于窗口的设计和客户端的设计在这里就省略不说,这里的目的只是要你了解服务器端多线程TCP/IP信息接收的原理。

    这个例子里,ChatClient类使用异步的IO线程进行数据读取,这样每个一客户端的都处于一个IO线程中处理,使主线程及早得到释放,这样做就缓解了服务器端压力。

    这时候你可以做一个测试,此聊天室在默认情况下可接受大约3000个客户端连接,仍然能够正常工作

     对 JAVA 开发有兴趣的朋友欢迎加入QQ群:174850571 共同探讨!
    对 .NET  开发有兴趣的朋友欢迎加入QQ群:162338858 共同探讨 !

    .NET基础篇

    以异步的方式操作TCP/IP套接字——以异步方式实现简单的聊天室
    合理使用“.NET扩展方法”来简化代码(例子:空值判断,利用扩展方法实现LINQ操作符ForEach)
    分部类和分部方法
    反射的奥妙
    利用泛型与反射更新实体(ADO.NET Entity Framework)






  • 相关阅读:
    输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
    输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
    输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
    输入一个链表,反转链表后,输出链表的所有元素。java实现
    少一点虚荣,多一点务实
    Mysql存储引擎__笔记
    osi七层模型和两主机传输过程:
    redis_笔记
    zookeeper_笔记
    rest和soap_笔记
  • 原文地址:https://www.cnblogs.com/leslies2/p/1935860.html
Copyright © 2020-2023  润新知