c# 网络异步编程

来源:互联网 发布:windows清理垃圾命令 编辑:程序博客网 时间:2024/05/17 03:47

            异步接收客户端连接BeginAcceptTcpClient

   像之前的listener.AcceptTcpClient();的方法,这个是同步方法,意思是当没有客户端连接的时候,这个方法就一直等待。

那么异步方法就是listener.BeginAcceptTcpClient()。这个方法不管有没有客户端连接。都继续执行下去,也就是

不会阻塞当前的线程。

使用异步方法接收客户连接的话,需要定义一个AsyncCallback委托方法,因为异步方法是采用通知的方式接收客户端连接

也就是当有一个客户端连接的时候,AsyncCallback委托的方法就会被执行。当然我这里同步异步只是举个例子,

不光是接收客户端连接有异步同步方法。客户连接也有异步BeginConnect方法。但是好像同步方法Connect不会等待啊,像之前的例子。

其实那也只是服务端没开启罢了。如果由于网络速度的原因,连接服务端比较慢的话。那么就可以看得出Connect它是在等待了。

再来看看AsyncCallback委托的定义:

public delegate void AsyncCallback(IAsyncResult ar);

使用异步连接话,那么我们就得定义一个上面那样的方法,IAsyncResult表示异步操作的状态,储存着一些信息。

如 ar.AsyncState储存着一个object,它通过BeginAcceptTcpClient方法的第二个参数state传进来的。

AcceptTcpClient和BeginAcceptTcpClient的使命都是一样的,获得一个客户端连接,如果有一个客户端连接了,那么这个方法也就结束了。AcceptTcpClient这个方法就直接返回客户端对象TcpClient,那么BeginAcceptTcpClient是通过调用EndAcceptTcpClient方法来获取TcpClient的。这个表明一个异步调用结束了。

异步调用结束是在一个客户端连接的时候。因为只有当有客户端连接的时候,回调函数才会被执行。也才会执行EndAcceptTcpClient

所以在回调函数中,调用EndAcceptTcpClient结束异步调用后,如果想再等待下一个客户端连接,必须再次调用BeginAcceptTcpClient方法。就像同步的一样,一个AcceptTcpClient对应着一个客户端,如果想连接第二个客户端,就得再次调用AcceptTcpClient。

来看一个例子吧,把上面的理论用例子来证明。

服务端代码:
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.NET;

using System.Net.Sockets;

namespace ServerConls

{

    public class Server

    {

        //这个方法被执行,就表明有一个客户端要连接了。

        public static void AcpClientCallback(IAsyncResult ar)

        {

            TcpListener listener = (TcpListener)ar.AsyncState;

            //调用对应的方法EndAcceptTcpClient,获得连接的客户端TcpClient

            TcpClient client=listener.EndAcceptTcpClient(ar);

            //输出客户端信息

            Console.WriteLine("一个客户端连接上了,客户端信息:{0}", client.Client.RemoteEndPoint);

            //再接收一个客户端连接

            AsyncCallback callback = new AsyncCallback(AcpClientCallback);

            listener.BeginAcceptTcpClient(callback, listener);

        }

        public static void Main()

        {

            //绑定IP,监听端口

            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });

            TcpListener listener = new TcpListener(ip, 9372);

            listener.Start();

            //委托方法

            AsyncCallback callback = new AsyncCallback(AcpClientCallback);

            //接收客户端连接,异步

            listener.BeginAcceptTcpClient(callback, listener);

            //循环输出一些信息

            int i = 0;

            while (true)

            {

                i++;

                Console.WriteLine("输出一些信息:{0}",i);

                //睡眠1.5秒

                System.Threading.Thread.Sleep(1500);

            }

        }

   

    }

     

}

客户端代码:
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Net;

using System.Net.Sockets;

namespace ClientConls

{

    class Program

    {

        static void Main(string[] args)

        {

            TcpClient client;

            //三个客户端连接

            for (int i = 0; i < 3; i++)

            {

                client = new TcpClient();

                client.Connect("localhost", 9372);

                //睡眠3秒

                System.Threading.Thread.Sleep(3000);

            }

        }

    }

}

这就是异步的好处,如果是同步的话,等待连接的时候,是不能再做其它事情的,也就不能一边输出信息,一边等待连接了。

异步读取数据

套路跟上面的差不多,定义一个委托方法,在里面调用对应的EndRead方法结束异步调用。直接看例子吧

服务端代码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Net;

using System.Net.Sockets;

namespace ServerConls

{

    public class Server

    {

        public static byte[] buffer = new byte[800];

        public static TcpClient client;

        // 客户端有数据发送过来,就会调用这个方法

        public static void ReadCallback(IAsyncResult ar)

        {

            NetworkStream Stream = (NetworkStream)ar.AsyncState;

            //结束异步读取方法,返回值是读取了多少字节

            int bytesRead = Stream.EndRead(ar);

            String msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);

            Console.WriteLine("\n从{0}上来发来信息:{1}", client.Client.RemoteEndPoint, msg);

            //这次读取BeginRead结束,继续下一次读取

            Stream.BeginRead(buffer, 0, 800, ReadCallback, Stream);

        }

        public static void Main()

        {

            //绑定IP,监听端口

            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });

            TcpListener listener = new TcpListener(ip, 9372);

            listener.Start();

            //等待一个客户端连接

            client = listener.AcceptTcpClient();

            Console.WriteLine("已经连接到一个客户端!");

            NetworkStream Stream = client.GetStream();

            //异步读取数据

            Stream.BeginRead(buffer, 0, 800, ReadCallback, Stream);

            byte[] sendBuffer;

            //发送数据

            while (true)

            {

                String msg = Console.ReadLine();

                sendBuffer = Encoding.Unicode.GetBytes(msg);

                Stream.Write(sendBuffer, 0, sendBuffer.Length);

            }

        }

    }

客户端代码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Net;

using System.Net.Sockets;

namespace ClientConls

{

    class Program

    {

        public static byte[] buffer = new byte[800];

        public static TcpClient client;

        //服务端有数据发送过来,就会执行这个方法。

        public static void ReadCallback(IAsyncResult ar)

        {

            NetworkStream Stream = (NetworkStream)ar.AsyncState;

            //结束异步读取方法,返回值是读取了多少字节

            int bytesRead = Stream.EndRead(ar);

            String msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);

            Console.WriteLine("\n从{0}上来发来信息:{1}", client.Client.RemoteEndPoint, msg);

            //这次读取BeginRead结束,继续下一次读取

            Stream.BeginRead(buffer, 0, 800, ReadCallback, Stream);

        }

        static void Main(string[] args)

        {

          //连接服务端

          client = new TcpClient();

          client.Connect("localhost", 9372);

          NetworkStream Stream = client.GetStream();

          //异步读取数据

          Stream.BeginRead(buffer, 0, 800, ReadCallback, Stream);

          byte[] sendBuffer;

          //发送数据

          while (true)

          {

              String msg = Console.ReadLine();

              sendBuffer = Encoding.Unicode.GetBytes(msg);

              Stream.Write(sendBuffer, 0, sendBuffer.Length);

          }

        }

    }

}

 客户端有服务端都是采用异步读取数据的,这样双方发送数据,就可以不按一定的顺序来,可以随时发送。如果是同步的话,那就得按固定的发送接收步骤来了。

异步读取数据的方式,很符合聊天软件的需要。

网络异常处理

网络编程异步处理是很有必要的,比如客户端连接服务端,如果服务端没启动的话,就会产生异常,程序就会非正常结束。如果用异常处理的话,可以规定客户端按自己方式来处理,比如服务端没有开启的话,给用户一个选择,是否重新连接,或者做其它的事,这样也不致于让程序就结束了。

我们先来看一个简单的例子,这个例子实现了当客户端关闭的时候,服务端会给出一个提示,提示客户端已关闭。

客户端:

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Net;

using System.Net.Sockets;

namespace ClientConls

{

    class Program

    {

        static void Main(string[] args)

        {

            TcpClient client = new TcpClient();

            //连接服务端

            client.Connect("localhost", 9372);

            Console.ReadLine();

            client.Close();

        }

    }

}

服务端:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Net;

using System.Net.Sockets;

namespace ServerConls

{

    class Program

    {

        static void Main(string[] args)

        {

            //绑定IP,监听端口

            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });

            TcpListener listener = new TcpListener(ip, 9372);

            listener.Start();

            TcpClient remoteClient = listener.AcceptTcpClient();

            Console.WriteLine("一个客户端连接了:{0}", remoteClient.Client.RemoteEndPoint);

            //获取数据流

            NetworkStream Stream = remoteClient.GetStream();

            byte[] buffer = new byte[800];

            while (true)

            {

                if (Stream.Read(buffer, 0, 800) == 0)

                {

                    Console.WriteLine("与客户端失去连接:{0}", remoteClient.Client.RemoteEndPoint);

                    break;

                }

            }

            Console.ReadLine();

         

        }

    }

}

上面的例子是在理想的状态下,先开启服务端,然后运行客户端。最后在客户端的控制台下窗口输入几个字符,执行client.Close();语句,那么服务端的Stream.Read就会返回0,从而给出与客户端失去连接的提示。

但如果直接关闭了客户端控制台窗口,服务端的Stream.Read就会产生异常,程序崩溃了。避免这个问题,当然得用异常处理try catch了。

看修改后的例子:

处理服务端异常,检查客户端连接状态

客户端代码同上,不变。

服务端代码:

  class Program

    {

        static void Main(string[] args)

        {

            //绑定IP,监听端口

            IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });

            TcpListener listener = new TcpListener(ip, 9372);

            listener.Start();

            TcpClient remoteClient = listener.AcceptTcpClient();

            Console.WriteLine("一个客户端连接了:{0}", remoteClient.Client.RemoteEndPoint);

            //获取数据流

            NetworkStream Stream = remoteClient.GetStream();

            byte[] buffer = new byte[800];

            while (true)

            {

                try

                {

                    if (Stream.Read(buffer, 0, 800) == 0)

                    {

                        Console.WriteLine("与客户端失去连接:{0}", remoteClient.Client.RemoteEndPoint);

                        break;

                    }

                }

                catch(Exception e)

                {

                    //Connected连接状态为假

                    if (remoteClient.Connected == false)

                        Console.WriteLine("异常中:与客户端失去连接:{0}", remoteClient.Client.RemoteEndPoint);

                    else

                        Console.WriteLine(e.Message);

                }

                 finally

                {

                    //释放资源

                    Stream.Dispose();

                     break;

                }

            }

            Console.ReadLine();

        }

}

那么用try catch解决服务端没开启的问题也是一样,捕捉client.Connect("localhost", 9372);这个语句的异常,然后处理。

另外关于等待客户端连接,读取数据什么的也可以用多线程来实现。这样灵活性就增加了许多。这里就不具体举例了,自己可以去琢磨。

0 0
原创粉丝点击