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);这个语句的异常,然后处理。

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

前 言 6 第1章 进程、线程与网络协议 7 1.1 进程和线程 7 1.1.1 Process类 7 1.1.2 Thread类 9 1.1.3 在一个线程中操作另一个线程的控件 13 1.2 IP地址与端口 15 1.2.1 TCP/IP网络协议 16 1.2.2 IPAddress类与Dns类 17 1.2.3 IPHostEntry类 17 1.2.4 IPEndPoint类 17 1.3 套接字 19 1.3.1 Socket类 20 1.3.2 面向连接的套接字 21 1.3.3 无连接的套接字 23 1.4 网络流 24 1.5 习题1 25 第2章 TCP应用编程 27 2.1 同步TCP应用编程 28 2.1.1 使用套接字发送和接收数据 28 2.1.2 使用NetworkStream对象发送和接收数据 30 2.1.3 TcpClient与TcpListener类 31 2.1.4 解决TCP协议的无消息边界问题 33 2.2 利用同步TCP编写网络游戏 34 2.2.1 服务器端编程 34 2.2.2 客户端编程 49 2.3 异步TCP应用编程 66 2.3.1 EventWaitHandle类 67 2.3.2 AsyncCallback委托 69 2.3.3 BeginAcceptTcpClient方法和EndAcceptTcpClient方法 70 2.3.4 BeginConnect方法和EndConnect方法 70 2.3.5 发送数据 71 2.3.6 接收数据 72 2.4 异步TCP聊天程序 73 2.4.1 服务器端设计 73 2.4.2 客户端设计 79 2.5 习题2 83 第3章 UDP应用编程 84 3.1 UDP协议基础知识 84 3.2 UDP应用编程技术 84 3.2.1 UdpClient类 84 3.2.2 发送和接收数据的方法 86 3.3 利用UDP协议进行广播和组播 90 3.3.1 通过Internet实现群发功能 90 3.3.2 在Internet上举行网络会议讨论 96 3.4 习题3 101 第4章 P2P应用编程 102 4.1 P2P基础知识 102 4.2 P2P应用举例 104 4.3 习题4 114 第5章 SMTP与POP3应用编程 115 5.1 通过应用程序发送电子邮件 115 5.1.1 SMTP协议 115 5.1.2 发送邮件 116 5.2 利用同步TCP接收电子邮件 120 5.2.1 POP3工作原理 121 5.2.2 邮件接收处理 123 5.3 习题5 127 第6章 网络数据加密与解密 128 6.1 对称加密 128 6.2 不对称加密 133 6.3 通过网络传递加密数据 136 6.4 Hash算法与数字签名 152 6.5 习题6 153 第7章 三维设计与多媒体编程 154 7.1 简单的3D设计入门 154 7.2 DirectX基础知识 160 7.2.1 左手坐标系与右手坐标系 160 7.2.2 设备 160 7.2.3 顶点与顶点缓冲 161 7.2.4 Mesh对象 162 7.2.5 法线 162 7.2.6 纹理与纹理映射 162 7.2.7 世界矩阵、投影矩阵与视图矩阵 162 7.2.8 背面剔除 164 7.3 Primitive 164 7.4 Mesh 171 7.5 灯光与材质 177 7.6 音频与视频 187 7.7 直接使用SoundPlayer类播放WAV音频文件 191 7.8 习题7 193 第8章 上机实验指导 194 8.1 实验一 简单网络聊天系统 194 8.2 实验二 网络呼叫应答提醒系统 195 8.3 实验三 文件数据加密与解密 199
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值