工业和信息化部“十二五”规划教材普通高等学校“十二五”规划教材《C#网络应用编程》第3版第9章WCF和TCP应用编程2Ch9WCF和TCP应用编程9.1TCP应用编程概述9.2利用传统技术实现TCP应用编程9.3利用WCF实现TCP应用编程9.1TCP应用编程概述9.1.1TCP的特点9.1.2TCP应用编程的技术选择49.1.1TCP的特点TCP是TransmissionControlProtocol(传输控制协议)的简称,是TCP/IP体系中面向连接的运输层协议,在网络中提供双工和可靠的服务。特点:一对一通信。安全顺序传输。通过字节流收发数据。传输的数据无消息边界TCP是将数据组装为多个数据报以字节流的形式进行传输,因此可能会出现发送方单次发送的消息与接收方单次接收的消息不一致的现象。59.1.2TCP应用编程的技术选择有以下几种编写TCP应用程序的技术。1.用Socket类实现TCP通信过程中的所有细节全部通过自己编写的程序来控制。这种方式最灵活,无论是标准TCP协议,还是自定义的新协议,都可以用它去实现。但是,需要程序员编写的代码多。建议:定义一些新的协议或者对底层的细节进行更灵活的控制时使用此技术。2.用TcpClient和TcpListener以及多线程实现System.Net.Sockets命名空间下的TcpClient和TcpListener类是.NET框架对Socket进一步封装后的类,这是一种粗粒度的封装,在一定程度上简化了用Socket编写TCP程序的难度,但灵活性也受到一定的限制。9.1.2TCP应用编程的技术选择2.用TcpClient和TcpListener以及多线程实现(续)TCP数据传输过程中的监听和通信细节(比如消息边界问题)仍然需要程序员自己通过代码去解决。3.用TcpClient和TcpListener以及多任务实现用TcpClient和TcpListener以及基于任务的编程模型编写TCP应用程序时,不需要开发人员考虑多线程创建、管理以及负载平衡等实现细节,只需要将多线程看作是多个任务来实现即可。4.用WCF实现监听和无消息边界等问题均有WCF内部自动完成,程序员只需要考虑传输过程中的业务逻辑即可,而不需要再去考虑TCP通信过程中如何监听,也不需要考虑如何解决无消息边界等细节问题。另外,利用WCF还可以实现自定义的协议。在.NET框架4.0及更高版本中,第3种和第4种是建议的做法。在.NET框架3.5及更低版本中,只能用第1种和第2种方式实现。9.2利用传统技术实现TCP应用编程9.2.1TcpClient类和TcpListener类9.2.2基本用法示例9.2.1TcpClient类和TcpListener类为了简化网络编程的复杂度,.NET对套接字又进行了适当的封装,封装后的类就是System.Net.Sockets命名空间下的TcpListener类和TcpClient类。TcpListener类用于监听客户端连接请求。TcpClient类用于提供本地主机和远程主机的连接信息。1.TcpClient类TcpClient类位于System.Net.Socket命名空间下。该类提供的构造函数主要用于客户端编程,而服务器端程序是通过TcpListener对象的AcceptTcpClient方法得到TcpClient对象的,不需要在服务端创建对象。9.2.1TcpClient类和TcpListener类1.TcpClient的构造函数有以下重载形式。TcpClient()用不带参数的构造函数创建TcpClient对象时,系统会自动分配IP地址和端口号,例如:TcpClienttcpClient=newTcpClient();tcpClient.Connect();TcpClient(stringhostname,intport)自动为客户端分配IP地址和端口号,并自动与远程主机建立连接。例如:TcpClienttcpClient=newTcpClient();9.2.1TcpClient类和TcpListener类1.TcpClient的构造函数有以下重载形式。TcpClient(AddressFamilyfamily)这种构造函数创建的TcpClient对象也能自动分配本地IP地址和端口号,但是它使用AddressFamily枚举指定使用哪种网络协议(IPv4或者IPv6)。例如:TcpClienttcpClient=newTcpClient(AddressFamily.InterNetwork);tcpClient.Connect();TcpClient(stringhostname,intport)9.2.1TcpClient类和TcpListener类1.TcpClient的构造函数有以下重载形式。TcpClient(IPEndPointiep)该构造函数的参数iep用于指定本机(客户端)IP地址与端口号。当客户端有一个以上的IP地址时,如果程序员希望指定IP地址和端口号,可以使用这种方式。例如:IPAddress[]address=Dns.GetHostAddresses(Dns.GetHostName());IPEndPointiep=newIPEndPoint(address[0],51888);TcpClienttcpClient=newTcpClient(iep);tcpClient.Connect();9.2.1TcpClient类和TcpListener类2.TcpListener类TcpListener类用于在服务端监听和接收客户端传入的连接请求。该类的构造函数常用的有以下两种重载形式。TcpListener(IPEndPointiep)TcpListener(IPAddresslocalAddr,intport)第1种构造函数通过IPEndPoint类型的对象在指定的IP地址与端口监听客户端连接请求,iep包含了本机的IP地址与端口号。第2种构造函数直接指定本机IP地址和端口,并通过指定的本机IP地址和端口监听客户端传入的连接请求。9.2.1TcpClient类和TcpListener类2.TcpListener类在同步工作方式下,对应有Start方法、Stop方法、AcceptSocket方法和AcceptTcpClient方法。另外,与这些同步方法对应的异步方法都有Async后缀。(1)Start方法TcpListener对象的Start方法用于启动监听。publicvoidStart()publicvoidStart(intbacklog)(2)Stop方法TcpListener对象的Stop方法用于关闭TcpListener并停止监听请求,语法如下。publicvoidStop()9.2.1TcpClient类和TcpListener类2.TcpListener类(3)AcceptTcpClient方法AcceptTcpClient方法用于在同步阻塞方式下获取并返回一个封装了Socket的TcpClient对象,同时从传入的连接队列中移除该客户端的连接请求。得到该对象后,就可以通过该对象的GetStream方法生成NetworkStream对象,再利用NetworkStream对象与客户端通信。(4)AcceptSocket方法AcceptSocket方法用于在同步阻塞方式下获取并返回一个用来接收和发送数据的Socket对象,同时从传入的连接队列中移除该客户端的连接请求。9.2.1TcpClient类和TcpListener类3.用TcpListener和TcpClient编写TCP应用程序的一般步骤编写TCP服务端代码的一般步骤使用TcpClient类和TcpListener类编写TCP服务器端代码的一般步骤如下。1)创建一个TcpListener对象,然后调用该对象的Start方法在指定的端口进行监听。2)在单独的线程中,循环调用TcpListener对象的AcceptTcpClient方法接收客户端连接请求,并根据该方法返回的结果得到与该客户端对应的TcpClient对象。3)每得到一个新的TcpClient对象,就创建一个与该客户端对应的线程,然后通过该线程与对应的客户端通信。4)根据传送信息的情况确定是否关闭与客户端的连接。9.2.1TcpClient类和TcpListener类3.用TcpListener和TcpClient编写TCP应用程序的一般步骤(2)编写TCP客户端代码的一般步骤使用对套接字封装后的TcpClient类编写TCP客户端代码的一般步骤如下。1)利用TcpClient的构造函数创建一个TcpClient对象,并利用该对象与服务端建立连接。2)利用TcpClient对象的GetStream方法得到网络流,然后利用该网络流与服务端进行数据传输。3)创建一个线程监听指定的端口,循环接收并处理服务端发送过来的信息。4)完成通信工作后,向服务端发送关闭信息,并关闭与服务器的连接。9.2.1TcpClient类和TcpListener类4.解决TCP无消息边界问题的办法有以下几种解决TCP消息边界问题的办法。(1)发送固定长度的消息这种办法适用于消息长度固定的场合。(2)将消息长度与消息一起发送一般在每次发送的消息前面用4个字节表明本次消息的长度,然后将其和消息一起发送到对方;对方接收到消息后,首先从前4个字节获取实际的消息长度,再根据消息长度值依次接收发送方发送的数据。这种办法适用于任何场合。(3)使用特殊标记分隔消息这种办法主要用于消息本身不包含特殊标记的场合。9.2.2基本用法示例通过一个简化的群发聊天程序,说明TcpClient类和TcpListener类的基本用法。1.用多线程实现【例9-1】利用多线程以及TcpClient和TcpListener对象,编写一个简单的群发聊天程序,服务端和客户端运行效果如图9-1所示。9.2.2基本用法示例本例子的基本功能要求如下。任何一个客户,均可以与服务端通信。当客户端与服务端连接成功后,服务端要及时告知所有客户端该客户已登录,并将当前在线人数告知所有客户端。客户可以通过服务端群发聊天信息。不论客户何时退出程序,服务端都要做出正确判断,同时将该客户是否在线的情况告诉其他所有在线的客户。9.2.2基本用法示例(1)服务端编程User类,用于处理与某个客户通信的信息。Login命令格式:Login,用户名Logout格式:Logout,用户名Talk格式:Talk,对话信息CC类,用于处理所有客户。(2)客户端编程9.2.2基本用法示例2.用多任务实现用多任务实现时,即使开发人员对多线程、线程池以及资源冲突和负载平衡等所有技术实现细节不太熟悉,一样可以快速编写出实际的TCP应用程序,而且程序的健壮性比直接用多线程来实现要高得多。(1)服务端编程在服务端实现中,用多任务实现的代码与直接用多线程实现的代码主要区别有两点:一是监听窗口中btnStart_Click事件的处理代码不一样,多任务是利用基于任务的异步模式来实现的;二是User类的构造函数中处理方式不一样,在多任务实现中,每个客户都有一个对应的任务负责与该客户端通信。9.2.2基本用法示例2.用多任务实现(2)客户端编程在客户端实现中,该例子与例9-1的区别只有btnLogin_Click中的代码不一样。在该事件处理程序中,将创建多线程改为创建多任务即可。例如:privatevoidbtnLogin_Click(objectsender,RoutedEven