TCP应用编程简介
TCP是Transmission Control Protocol(传输控制协议)的简称,是TCP/IP体系中的面向连接的传输层协议,在网络中提供全双工的和可靠的服务。一旦双方建立了TCP连接,连接中的任何一方都能向对方发送数据并接收对方发来的数据。发送数据时,程序员可以编写程序不断地将数据流写入TCP的发送缓冲区中,然后TCP自动从发送缓冲区中取出一定量的数据流写入TCP的发送缓冲区中,然后TCP自动从发送缓冲区中取出一定量的数据,将其组成TCP报文段逐个发送给IP层,再通过IP层发送出去。接收端从IP层收到TCP报文段后,将其暂时保存在接收缓冲区中,这时程序员就可以编写程序依次读取接收缓冲区中的数据,从而达到通信的目的。
利用TCP开发应用程序时,.NET框架提供两种工作方式,一种是同步
(Synchronous)工作方式,一种是异步
(Asynchronous)工作方式。
同步工作方式是指利用TCP编写的程序执行到发送、接收或监听语句时,在未完成工作前不再继续下面的工作,即处于阻塞状态,直到该语句完成相应的工作后才继续执行下一条语句;异步工作则不论工作完成与否,都会继续往下执行。例如:接收数据时,在同步方式下,接收方执行到接收语句后将处于阻塞方式,只有接收到对方发来的数据后才继续执行下一条语句;而如果采用异步方式,则接收方执行到接收语句后,无论是否接收到对方的数据,程序都会继续执行。
与同步工作方式和异步工作方式对应,利用Socket类进行编程时,系统也提供有相应的方法,分别称为同步套接字编程和异步套接字编程。但是使用套接字编程比较复杂,涉及许多底层细节。为了简化套接字编程,.NET框架又专门提供了两个类:TcpClient类和TcpListener类。由于这两个类与套接字一样分别有各自的同步和异步工作方式及其对应的方法,在编程时,三个类都可以使用。因此为了简化起见,无论使用哪个类,我们都从工作方式上将其称为同步TCP和异步TCP,对应的编程方式就称为同步TCP编程和异步TCP编程。
[ 注意:这里的同步TCP与异步TCP仅仅是指工作方式,与线程间的同步不是一个概念。线程间的同步是指不同线程或不同线程使用的某些资源具有先后关联的关系,它决定着逻辑执行顺序。比如有A和B两个资源,实际应用中要求只有对资源A处理后才能处理资源B,就说资源A与资源B存在同步关系。如果执行顺序不正确就会引发错误。所以,线程间的同步关注的是一种逻辑关系。而同步TCP和异步TCP则仅仅指编程中采用哪种工作方式,即从执行到发送、接收或监听语句时,程序是否是继续住下执行这个角度来说的。]
从逻辑关系上看,无论是同步TCP编程,还是异步TCP编程,在实际应用中既可能要求不同线程间同步,也可能不要求同步。
异步TCP应用编程
利用TcpClient和TcpListener在同步方式下接收、发送数据及监听客户端连接时,在操作没有完成之前一直处于阻塞状态,这在接收、发送数据量不大或者操作用时较短的情况下是比较方便的。但是,对于那些完成时间可能较长(如传送大文件)的任务时,使用同步操作可能就会造成系统假死,这种情况下,最好的办法是使用异步操作。
异步操作方式
所谓异步操作方式,就是我们希望让某个工作开始以后,能在这个工作尚未完成的时候继续处理其他工作。
这里举一个是常生活的例子来说明:我们(主线程)安排A(子线程A)负责处理客人来访登记工作,在同步方式下,如果没有人来访,A就只能一直呆在某个房间等待,而不能从事其他工作,显然这种方式不利于并行处理。我们希望,没有人来访时,A不一定在这个房间等待,也可以到别处继续做其他工作,而把等待这个工作交给总控室人员完成。这个总控室就是Windows系统本身,总控室如何及时通知A呢?可以让A先告诉总控人员一个手机号F(callback需要的方法名F),以便有人来访时可以立即给A打电话(通过委托自动运行F),A接到通知后,再回来处理客人来访登记事务。
异步操作最大的优点是可以在一个操作没完成之前进行其他操作。.NET框架提供一种称为AsyncCallback(异步回调)的委托,该委托允许启动异步的功能,并在条件具备时调用提供的回调方法(是一种在操作或活动完成时由委托自动调用的方法),然后在这个方法中完成并结束未完成的工作。
在异步操作方式下,每个Begin方法都有一个匹配的End方法。编写程序时利用Begin方法开始执行异步操作,然后由委托在条件具备时调用End方法完成并结束异步操作。
TCP编程各类提供的异步操作方法
类 | 提供的方法 | 说明 |
TcpListener | BeginAcceptTcpClient | 开始一个异步操作接受一个传入的连接尝试 |
| EndAcceptTcpClient | 异步接受传入的连接,并创建新 TcpClient对像处理 |
TcpClient | BeginConnect | 开始一个对远程主机连接的异步请求 |
| EndConnect | 异步接受传入的连接尝试 |
Socket | BeginReceive | 开始从连接的Socket中异步接收数据 |
| EndReceive | 结束挂起的异步读取 |
| BeginSend | 将数据异步发送到连接的Socket |
| EndSend | 结束挂起的异步发送 |
EventWaitHandle类
在现实工作中,有很多工作是相互的,某些工作必须等待另一个工作完成后才能继续,这就是异步操作中的同步问题。在System.Threading命名空间中, 有一个EventWaitHandle类,用于在异步操作中控制线程的同步,即控制一个或多个线程继续执行或等待其他线程完成。EventWaitHandle类可以让操作系统通过发出信号完成多个线程间的同步,需要同步的线程可以先阻塞当前线程,然后根据Windows操作系统发出的信号,决定是继续阻塞还是等待其他工作完成,还是不再等待而直接继续执行。
EventWaitHandle类提供以下方法进行线程控制:
Reset方法:将信息状态设置为非终止状态,即不让操作系统发出信号,从而使那些只能接收到信号才能继续执行的线程处于阻塞状态。
Set方法:将事件状态设置为终止状态,这样的等待的线程将会收到信号,从而由等待转为继续。
WaitOne方法:阻塞当前线程,等待操作系统发出信号,直到收到信号才解除阻塞。
操作系统发出信号的方式有两种:
(1)发出一个信号,使某个等待信号的线程解除阻塞,继续执行。
(2)发出一个信号,使所有等待信号的线程全部解除阻塞,继续执行。
AsyncCallback委托
AsyncCallback委托用于引用异步操作完成时调用的 回调方法。在异步操作方式下,由于程序可以在启动异步操作后继续执行其他代码,因此必须有一种机制,以保证该异步操作完成时能及时通过调用者。可以通过AsyncCallback委托实现这种机制。
异步操作的每一个方法都有一个Begin...方法和End...方法,程序调用Begin...方法时,系统会自动在线程池中创建对应的线程进行异步操作,从而保证调用方和被调用方同时执行,当线程池中的Begin...方法执行完毕时,会自动通过AsyncCallback委托调用在Begin...方法的参数中指定的回调方法。
回调方法是在程序中事先定义的,在回调方法中,通过End...方法获取Begin...方法的返回值和所有输入/输出的参数,从而达到完成参数传递的目的。
TCP应用编程各种操作方法简介
BeginAcceptTcpClient与EndAcceptTcpClient
BeginAcceptTcpClient和EndAcceptTcpClient方法包含在System .Net.Sockets命名空间下的TcpListener类中。
在异步TCP应用编程中,服务端可以使用TcpListener类提供的BeginAcceptTcpClient方法开始接收新的客户端连接请求。在这个方法中,系统自动自用线程池创建需要的线程,并在操作完成时利用异步回调机制调用提供给它的方法,同时返回相应的状态参数。其方法原型为:
public IAsyncResult BeginAcceptTcpClient(AsyncCallback callback,Object state)
其中:参数1为AsyncCallback类型的委托;参数2为Object类型,用于将状态信息传递给委托提供的方法。
例如:AsyncCallback callback=new AsyncCallback(AcceptTcpClientCallback);
tcpListener.BeginAcceptTcpClient(callback,tcpListener);
程序执行BeginAcceptTcpClient方法后,立即在线程池中创建需要的线程,同时在创建的线程中监听客户的连接需求。一旦接受了客户端的连接请求,就自动通过委托执行相应的方法,并返回状态信息。例子中我们将此方法命名为AcceptTcpClientCallback,状态信息为TcpListener类型的实例tcpListener。
定义异步回调方法的格式:
void AcceptTcpClientCallback(IAsyncResult ar){ 回调代码 }
[ 注:回调方法中的参数只有一个,必须是实现报IAsyncResult类型的接口,它表示异步操作的状态。]
在回调方法中,必须调用EndAcceptTcpClient方法才能完成客户端连接,关键代码如下:
void AcceptTcpClientCallback(IAsyncResult ar){
......
TcpListener myListener=(TcpListener)ar.AsyncState;
TcpClient client=myListener.EndAcceptTcpClient(ar);
......
}
程序执行EndAcceptTcpClient方法后,会自动完成客户端连接请求,并返回包含底层套接字的TcpClient对象,接下来我们就可以利用这个对象与客户端进行通信了。
默认情况下,程序执行BeginAcceptTcpClient方法后,在返回状态信息前,不会像同步TCP方式那样阻塞线程等待用户连接,如果我们希望在返回状态信息前阻塞当前线程,就要调用ManualResetEvent对象的WaitOne方法。
BeginConnect与EndConnect
BeginConnect 方法和EndConnect方法包含在命名空间System. Net.Sockets下的TcpClient类和Socket类中,这里我们只讨论TcpClient类中的方法。
在异步TCP应用程序编程中,BeginConnect方法通过异步方式向远程主机发出连接请求,有三种重载形式,方法原型为:
public IAsyncResult BeginConnect(IPAddress address,int port,AsyncCallback requestCallback,Object state);
public IAsyncResult BeginConnect(IPAddress[] addresses,int port,AsyncCallback requestCallback,Object state);
public IAsyncResult BeginConnect(string host,int port,AsyncCallback requestCallback,Object state);
其中address为远程主机的IPAddress对象;port为远程主机端口号;requestCallback为AsyncCallback类型的委托;state为包含连接操作的相关信息,当操作完成时,此对象传递给requestCallback委托。
在BeginConnect方法操作完成前,调用该方法的线程不会阻塞,系统会自动调用独立的线程来执行该方法,直到与远程主机连接成功或抛出异常。如果希望在调用BeginConnect方法之后阻塞线程,可以调用ManualResetEvent对象的WaitOnet方法。
异步BeginConnect方法也只有在调用了EndConnect方法之后才算执行完毕,因此程序中需要在提供给requestCallback委托调用的方法中调用TcpClient对象的EndConnect方法。关键代码为:
......
AsyncCallback requestCallback=new AsyncCallback(RequestCallback);
tcpClient.BeginConnect(远程主机IP或域名,远程主机端口号,requestCallback,tcpClient);
......
void RequestCallback(IAsyncResult ar){
......
tcpClient=(TcpClient)ar.AsyncState;
client.EndConnect(ar);
.......
}