利用TcpListener和TcpClient类在同步方式下监听客户端连接、接受、发送数据时,在操作没有完成之前,一直处于阻塞状态,这对于接收、发送数据量不大的情况下,或者操作用时比较短的情况下是比较方便的。但对于执行完成时间较长的任务,例如传送大文件等,最好使用异步操作。
异步操作的最大优点是可以在一个操作没有完成之前同时进行其他的操作。.NET框架提供了一种成为AsyncCallBack(异步回调)的委托,该委托允许启动异步的功能,并在条件具备时,调用提供的回调方法(一种在操作或活动完成时,由委托自动调用的方法),然后在这个方法中完成并结束未完成的工作。
1、AsyncCallback委托
AsyncCallback委托用于引用异步操作完成时调用的回调方法。在异步操作方式下,由于程序可以在启动异步操作后继续执行其他代码,因此必须有一种机制,以保证该异步操作完成时能及时通知调用者。这种机制可以通过AsyncCakkback委托实现。
异步操作的每一个方法都有一个Begin...函数和一个End...函数。当程序调用Begin...方法时,系统会自动在线程池中创建对应的线程进行异步操作,从而保证调用方法和被调用方法同时执行,当线程池中的Begin...方法执行完毕时,会自动通过AsyncCallback委托调用Begin...函数的参数中指定的回调函数。
回调函数是在程序中事先定义好的,在回调函数中,通过End...函数获取Begin...函数的返回值和所有输入/输出参数,从而达到异步操作方式下完成参数传递的目的。
2、BeginAcceptTcpClient函数和EndAcceptTcpClient函数
在异步Tcp应用变成中,服务端可以使用TcpListener提供的BeginAcceptTcpClient函数开始接受新的额客户端连接请求。在这个方法中,系统自动利用线程池创建需要的线程,并在操作完成时利用异步回调机制调用提供给他的方法,同时返回相应的状态参数。例:
AsyncCallback callback = new AsyncCallback(AcceptTcpClientCallback); tcpListener.BeginAcceptTcpClient(callback,tcpListener);
上面的例子,当程序执行BeginAcceptTcpClient函数后,立即在线程池中创建需要的线程,同时在自动创建的线程中监听客户端连接请求。一旦接受了客户端连接请求,就自动通过委托调用提供给委托的方法AcceptTcpClientCallback函数,并通过tcpListener这个实例对象来返回状态信息。AcceptTcpClientCallback函数是自己定义的,定义格式为:
void AcceptTcpClientCallback(IAsyncResult ar) { //HFY::回调函数中执行的代码 //... TcpListener myListener = (TcpListener)ar.AsyncState; TcpClient client = myListener.EndAcceptTcpClient(ar); //... }
上面自己定义的回调函数中传递的参数必须是IAsyncResult类型的接口,它表示异步操作的状态。通过委托,系统会自动将该异步操作的状态信息从关联的BeginAcceptTcpClient函数传递到自定义的AcceptTcpClientCallback函数。
此外,注意:在回调代码中,必须调用EndAcceptTcpClient函数,完成客户端连接。程序执行完EndAcceptTcpClient函数后,会自动完成客户端的连接请求,并返回包含底层套接字的TcpClient对象,之后就可以利用这个对象与客户端通信了。
在默认情况下,程序执行BeginAcceptTcpClient函数后,在该函数返回状态信息之前,不会想同步TCP方式那样阻塞等待客户端连接,而是继续往下执行。如果我们希望在其返回状态信息之前阻塞当前线程的执行,可以调用ManualResetEvent对象的WaitOne函数。
3、BeginConnect函数和EndConnect函数
在异步TCP应用变成中,BeginConnect函数通过异步的方式向远程主机发出连接请求。BeginConnect函数在操作完成前不会阻塞,在程序中调用BeginConnect函数时,系统会自动用独立的线程来执行该方法,直到与远程主机连接成功或抛出一场nag。如果在调用BeginConnect函数之后想阻塞当前线程,也可以调用ManualResetEvent对象的WaitOne函数。
异步BeginConnect函数只有在调用了EndConnect函数之后才算执行完毕,因此程序中需要在提供给委托调用的方法中调用TcpClient对象的EndConnect函数。关键代码为:
AsyncCallback requestCallback = new AsyncCallback(RequestCallBack); tcpClient.BeginConnect("10.204.64.77",8088,requestCallback,tcpClient); //HFY::回调函数 void RequestCallBack(IAsyncResult ar) { //... tcpClient = (TcpClient)ar.AsyncState; //... tcpClient.EndConnect(ar); //... }
4、发送数据
在异步TCP编程中,如果本机已经和远程主机建立连接,就可以使用BeginWrite函数发送数据。BeginWrite函数用于向一个已经成功连接的套接字异步发送数据。在程序中调用BeginWrite函数后,系统会自动在内部产生的单独执行的线程中发送数据。
使用BeginWrite异步发送数据,程序必须创建实现AsyncCallback委托的回调函数,并将回调函数的名称传递给BeginWrite函数。在回调函数中,必须调用EndWrite函数,并在EndWrite后,系统会自动使用单独的线程来执行指定的回调函数,并在EndWrite上一直处于阻塞状态,直到NetworkStream对象发送请求的字节数或者引发异常。
5、接收数据
与发送数据类似,如果本机已经和远程主机建立连接,就可以使用BeginRead函数发送数据。BeginRead方法启动从传入网路缓冲区中异步读取数据的操作。在调用BeginRead函数后,系统自动在单独的执行线程中接受数据。
在程序中必须创建实现AsyncCallback委托的回调函数,并将其名称传递给BeginRead。一般情况下,我们希望在回调函数中获得所接收的数据,因此因创建小型的类或者结构来保存读取缓冲区,以及其他有用的信息。
在回调函数中,必须调用EndRead函数来完成读取操作。系统在执行BeginRead时,将一直等待知道数据接收完毕或者遇到错误,从而得到可用的字节数,再自动使用一个单独的线程来执行指定的回调函数,并阻塞EndRead,直到所提供的NetworkStream对象将可用的数据读取完毕,或者达到指定读取的字节数。