C#网络编程(二)

网络异步编程

            异步接收客户端连接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);这个语句的异常,然后处理。

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

  • 17
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bczheng1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值