第七章基于TCP的Socket通信.ppt
3 基于TCP的Socket通信3.1 Socket类与ServerSocket类3.2 简单服务器程序3.3 简单客户端程序3.4 基于多线程的服务器程序3.5 基于多线程的客户端程序,基于TCP的Socket通信,基于TCP的Socket通信是通过指定IP地址和port 号,采用C/S模式建立TCP协议下的两个通信进程间的连接,实现可靠的双向通信。Java中:实现客户端套接字的Socket类;实现服务器端套接字的ServerSocket类。,3.1 Socket类与ServerSocket类,1Socket通信原理网络中基于Socket通信的两个进程间建立连接时,会将其中一个进程作为客户端,而另一个进程作为服务器端。(1)基于Socket通信的C/S模型使用ServerSocket类和Socket类实现通信的C/S模型如图3-1所示。,(1)基于Socket通信的C/S模型(续一),图3-1 使用ServerSocket类和Socket类实现通信的C/S模型,基于Socket通信的基本算法:S1:用一致的端口分别创建Socket类和ServerSocket类对象;S2:服务器端ServerSocket类对象用accept()监视端口;S3:打开连接到客户端Socket类对象的输入/输出流,向服务器端ServerSocket类对象发送相应请求,服务器接受客户请求并返回客户端Socket类对象,从而建立连接;S4:通信双方按照一定协议对Socket对象进行读/写操作;S5:关闭Socket。,(2)客户端进程,按给定的服务器端的地址及端口号,建立客户端套接字Socket类的对象,并向服务器端发送请求,等待服务器的响应。代码如下:try/创建客户端Socket类的对象socket,服务器地址取本地,端口号为55558 Socket socket=new Socket(localhost,55558);catch(UnknownHostException e)e.printStackTrace();catch(IOException e)e.printStackTrace();,(3)服务器端进程,按与客户端商定的端口号建立服务器端套接字ServerSocket类的对象,然后用ServerSocket对象的阻塞方法accept()监听该端口号中是否有客户端发送的请求。若没有请求,则服务器进程会处于等待状态并一直监听端口;一旦接收到客户端发送的请求,accept()方法就会获取返回该客户端对象,从而在服务器端保存与客户端的连接,接下来就可利用该连接实现与客户端之间的数据交换。,(3)服务器端进程,建立服务器端套接字及端口监听的代码:try ServerSocket serversocket=new ServerSocket(55558);Socket socket=serversocket.accept();catch(IOException e)e.printStackTrace();,强调:服务器的端口号和客户端进程中指定端口号应该一致,否则不能建立连接。,2Socket类的构造方法与常用方法,表3-2 常用方法及功能,3ServerSocket类的构造方法与常用方法,3.2 简单服务器程序,根据图3-1所示的“使用ServerSocket类和Socket类实现通信的C/S模型”设计一个简单的基于Socket的通信系统。该系统由服务器端程序与客户端程序两部分组成,其基本功能分别是:()服务器端程序它的任务是监听C/S双方约定的端口(55558),等待并接收客户请求,接受客户请求后建立一个至客户端的基于套接字的连接,然后利用该连接返回到客户端的Socket对象,创建一个服务器端输入流InputStream和一个服务器端输出流OutputStream,同时将它们分别包装成便于操作与刷新的BufferedReader输入流和PrintWriter输出流。然后,服务器端从InputStream读入客户端输出的数据,用OutputStream向客户端输出数据,直到接收到客户端的数据终止标志“结束”为止,最后关闭连接,释放网络资源,结束本次通信。,()客户端程序,首先创建客户端Socket对象后,然后在约定端口向服务器端发送请求,待服务器端接受请求后建立基于套接字的连接,然后利用该连接的Socket对象,创建一个客户端输入流InputStream和一个客户端输出流OutputStream,同时将它们分别包装成便于操作与刷新的BufferedReader输入流和PrintWriter输出流。然后,客户端从InputStream读入服务器端输出的数据,用OutputStream向服务器端输出数据,直到发送完数据终止标志“结束”为止,最后关闭连接,释放网络资源,结束本次通信。,例3-1 基于Socket的简单服务器程序。程序清单:SimpleServerSocketDemo.java运行方法:在待运行类的主目录下编写并运行SimpleServerSocketDemo.bat文件,其内容如下:java socket.SimpleServerSocketDemoPause,图3-2 服务器端的输出结果,【例3-1】程序分析:,服务器端程序与客户端程序都使用同样的端口号(55558),服务器端程序在本地机器上运行其ServerSocket只需要一个端口号,而不需要IP地址。服务器端ServerSocket类的实例调用accept()方法时,会陷入阻塞状态,直到某个客户端程序请求与它建立连接。连接正常建立后,accept()将返回一个客户端Socket类的实例,即本次C/S套接字连接的实例,它是一个可读写的双向管道。,【例3-1】程序分析:(续一),必须将ServerSocket构造方法、accept()方法和I/O流操作方法等放在一个try-finally代码块,以确保无论什么方式结束,ServerSocket、Socket和I/O流都能被正确关闭。若ServerSocket对象创建失败,则抛出IOException异常,并由finally块确保无论正常与否结束通信,均会关闭连接、释放网络套接字等资源。由于套接字使用了重要的非内存资源,因此要特别谨慎,必须以显式方式将它们及时清除。,【例11-5】程序分析:(续二),当程序中利用标准输出流System.out将ServerSocket类构造的实例和accept()方法返回的Socket类的实例打印输出时,自动调用了它们的toString()方法,其结果如下:ServerSocket addr=0.0.0.0/0.0.0.0,port=0,localport=55558Socket addr=/127.0.0.1,port=3024,localport=55558,【例11-5】程序分析:(续三),数据交换部分:服务器端的输入流InputStream和输出流OutputStream是从Socket类的实例创建的。它采用装饰模式,先利用两个“转换器”类InputStreamReader和OutputStreamWriter,将InputStream和OutputStream对象分别转换成为Reader和Writer对象。再利用类BufferedReader和PrintWriter,将Reader和Writer对象分别转换成为BufferedReader和PrintWriter对象,以方便读写与刷新操作。若构造方法PrintWriter(Writer out,boolean autoFlush)中的“autoFlush”为“true”时,则PrintWriter类的out对象每次调用println()结束时会自动刷新输出缓冲区(但不适用于print()语句),使输出流中的信息能即时通过网络传递出去。,11.3.3 简单客户端程序,根据11.3.2节中基于Socket的通信系统中对客户端程序功能的分析,其实现代码如例11-6所示。P400【例11-6】基于Socket的简单客户端程序。程序清单11-6:SimpletClientSocketDemo.java运行方法:在待运行类的主目录下编写并运行SimpletClientSocketDemo.bat文件,其内容如下:java socket.SimpletClientSocketDemopause运行结果:如图11-4所示。,图11-4 例11-6中客户端的输出结果,【例11-6】程序分析:,客户端使用本地主机(Localhost)地址与位于同一台机器中的服务器程序建立连接,因此可在一台物理机器中完成测试,若将客户端程序与服务器端程序分布在一个物理网络中,则可在客户端Socket对象中指定服务器的IP地址,即可实现通信。客户端程序中获得本地主机IP地址的InetAddress的途径有三种:使用null、使用localhost,或者直接使用保留地址127.0.0.1。若向getByName()传递一个null,则默认寻找localhost,并生成特殊的保留地址127.0.0.1。注意:在创建名为socket的套接字时,同时使用了InetAddress以及端口号。,【例11-6】程序分析:(续一),服务器程序启动后在本地主机(127.0.0.1)上为其分配端口55558。一旦客户端程序发出请求,当前机器中的下一个可用端口就会分配给客户端程序(此处为3024),并同时告知与其连接的服务程序。此例中,服务器端进程获取的客户端套接字如下所示:Socket addr=127.0.0.1,port=3024,localport=55558它表示服务器进程已接受来自IP为127.0.0.1机器的3024端口的客户端进程的连接,同时监听其本地的55558端口,而在客户端输出的套接字如下所示:Socket addr=localhost/127.0.0.1,port=55558,localport=3024它表示客户端进程已用其本地端口3024与127.0.0.1机器上的55558端口建立了连接。,【例11-6】程序分析:(续二),数据交换:创建好客户端Socket对象后,调用其getInputStream()和getOutputStream()方法分别创建客户端的输入流InputStream和输出流OutputStream,并与服务器端程序一样采用装饰模式,最终将它们分别转换成为BufferedReader输入流和PrintWriter输出流,以方便读写与刷新操作。为了测试通信正常与否,此处,客户端输出流通过发送“From Client”加数字的字符串数据来初始化通信,而客户端输入流则从服务器输出流中接收“From Server”加数字的字符串行,写入System.out后,在屏幕打印输出。最后,为终止数据交换,客户端输出流通过向服务器端输入流发送“结束”字符串,以结束本次通信,释放套接字连接资源。,【例11-6】程序分析:(续三),在客户端程序中同样采用try-finally块,以确保由Socket代表的网络资源能得到正确的清除。套接字建立的“专用”连接会一直持续到明确断开连接为止(除非某端或中间链路出现故障而崩溃)。在连接未拆除前,参与连接的双方都被锁定在通信中,且无论是否有数据传递,连接都会连续处于开放状态。因此,每次通信结束时,若不及时拆除连接将会增加网络的额外开销,甚至使资源耗尽,导致系统崩溃。,11.3.4 基于多线程的服务器程序,例11-5中的SimpleServerSocketDemo尽管能正常工作,但每次只能为一个客户端程序提供服务。实际应用中,要求服务器能同时处理多个客户端的请求。解决此问题的关键就是将多线程处理机制应用到网络通信中来。如图11-5所示是在图11-2的基础上改进而来的基于Socket的多线程C/S通信模型,它可应用于对例11-5与例11-6的改造,从而实现响应多客户请求的数据通信,提高服务器的并发性能。,11.3.4 基于多线程的服务器程序(续一),图11-5 基于Socket的多线程C/S通信模型,其基本思想是:在服务器程序中创建单个ServerSocket的实例,并循环调用其accept()方法以等候一个新连接。一旦accept()返回一个客户端线程的Socket的实例,就用该Socket实例新建一个服务线程,为该特定的客户端线程服务。客户端程序采用多线程技术能创建多个Socket实例的线程,并能控制其活动线程的总数量,以防止服务器过载和网络拥塞。,11.3.4 基于多线程的服务器程序(续二),例11-7是基于多线程的服务器程序,它是对例11-5的改进,它与SimpleServer SocketDemo.java很相似,只是为一个特定的客户端线程提供服务的所有操作都被移入一个独立的线程类MultithreadServerSocket中。P402【例11-7】基于多线程的服务器程序。程序清单11-7:MultithreadServerSocketDemo.java,11.3.4 基于多线程的服务器程序(续三),运行方法:在待运行类的主目录下编写并运行MultithreadServerSocketDemo.bat文件,其内容如下:java socket.MultithreadServerSocketDemopause,图11-6 例11-7中服务器端输出的部分结果,【例11-7】程序分析:,一旦有新的客户端Socket线程请求建立一个连接时,服务器端的MultithreadServerSocket线程会取得由accept()返回的Socket对象。然后与例11-5一样,创建一个BufferedReader输入流和一个PrintWriter输出流。最后,它调用Thread的start()方法进行服务线程的初始化,然后调用run()完成数据交换。数据交换操作与例11-5相同。,【例11-7】程序分析:(续一),套接字的清除必须进行谨慎的设计。此例中,ServerSocket套接字是在MultithreadServerSocket外部创建的,所以清除工作可以“共享”。若MultithreadServerSocket构造方法失败,则只需向调用者抛出一个异常即可,然后由调用者负责线程的清除。但若构造方法成功,则必须由MultithreadServerSocket对象负责线程的清除,这是在它的run()里进行的。,11.3.5 基于多线程的客户端程序,为了验证服务器代码确实能为多个客户端提供服务,下面这个程序将使用线程创建许多客户端,并与相同的服务器建立连接。每个线程的“存在时间”都是有限的。一旦到期,就留出空间以便创建一个新线程。允许创建的线程的最大数量是由final int MAXTHREADS决定的。这个值很关键,若把它设得很大,线程便有可能耗尽资源,产生不可预知的程序错误。基于多线程的客户端程序如例11-8所示,它是对例11-6的改进。P405【例11-8】基于多线程的客户端程序。程序清单11-8:MultithreadClientSocketDemo.java,11.3.5 基于多线程的客户端程序(续一),运行方法:在待运行类的主目录下编写并运行MultithreadClientSocketDemo.bat文件,其内容是:java socket.MultithreadClientSocketDemopause,图11-7 例11-8中客户端输出的部分结果,【例11-8】程序分析:,MultithreadClientSocket构造方法用于获取一个InetAddress,并用它创建一个客户端的套接字实例。然后用Socket实例创建输入流InputStream和输出流OutputStream对象,再用start()执行线程的初始化,并调用run(),其数据交换与例11-6相同。线程的“存在时间”是有限的,最终都会结束。在套接字创建好以后,在构造方法完成之前,假若构造方法失败,套接字会被清除。否则,为套接字调用close()的责任便落到了run()方法上。,【例11-8】程序分析:(续一),tcount跟踪计算目前存在的MultithreadClientSocket对象的数量。它将作为构造方法的一部分增值,并在run()退出时减值。在MultithreadClientSocketDemo.main()中线程的数量会被检查。若数量太多,则多余的暂不创建。方法随后进入“休眠”状态。因此一旦部分线程最后被终止,则多出的那些线程就可以创建了。,