|
![]() |
名片设计 CorelDRAW Illustrator AuotoCAD Painter 其他软件 Photoshop Fireworks Flash |
|
五、建立TCP客户端 讨论了套接字类的功能后,我们将分析一个完整的TCP客户端程序。此处我们将看到的客户端 程序是一个daytime客户端,它连接到一个daytime服务器程序以读取当前的日期和时间。建立套 接字连接并读取信息是一个相称简朴的过程,只需要少量的代码。默认情况下daytime服务运行在 13端口上。并非每台计算机都运行了daytime服务器程序,但是Unix服务器是客户端运行的很好的 系统。假如你没有访问Unix服务器的权限,在第七部分我们给出了TCP daytime服务器程序代码-- 有了这段代码客户端就可以运行了。 DaytimeClient的代码 import java.net.* import java.io.*; public class DaytimeClient { public static final int SERVICE_PORT = 13; public static void main(String args[]) { // 检查主机名称参数 if (args.length != 1) { System.out.println ("Syntax - DaytimeClient host"); return; } // 获取服务器程序的主机名称 String hostname = args[0]; try { // 获取一个连接到daytime服务的套接字 Socket daytime = new Socket (hostname, SERVICE_PORT); System.out.println ("Connection established"); // 在服务器程序停止的情况下设置套接字选项 daytime.setSoTimeout ( 2000 ); // 从服务器程序读取信息 BufferedReader reader = new BufferedReader ( new InputStreamReader (daytime.getInputStream() )); System.out.println ("Results : " + reader.readLine()); // 关闭连接 daytime.close(); } catch (IOException ioe) { System.err.println ("Error " + ioe); } } } DaytimeClient是如何工作的 Daytime应用程序是很轻易理解的,它使用了文章前面谈到的概念。建立套接字、获取输入 流,在很少的事件中(在连接时像daytime相同简朴的服务器程序失败)激活超时设置。不是连接 已筛选过的流,而是把有缓冲的读取程序连接到套接字输入流,并且把结果显示给用户。最后, 在关闭套接字连接后客户端终止。这是你可能得到的最简朴的套接字应用程序了--复杂性来自实 现的网络协议,而不是来自详细网络的编程。 运行DaytimeClient 运行上面的应用程序很简朴。简朴地把运行daytime服务的计算机的主机名称作为命令行参数 指定并运行它就可以了。假如daytime服务器程序使用了非标准的端口号(在后面会讨论),记得 需要改变端口号并重新编译。 例如,假如服务器程序在本机上,为了运行客户端将使用下面的命令: java DaytimeClient localhost 注重 Daytime服务器程序必须正在运行中,否则该客户端程序将不能建立连接。例如假如你正在使 用Wintel系统而不是Unix,那么你需要运行DaytimeServer(后面会谈到)。 六、ServerSocket类 服务器套接字是一种特定类型的套接字,它用于提供TCP服务。客户端套接字绑定到本地计算 机的任何空的端口,并且连接到特定服务器程序的端口和主机。服务器套接字与它的差别是它们 绑定到本地计算机的某个特定的端口,这样远程客户端才能定位某种服务。客户端套接字连接只 能连接到一台计算机,然而服务器套接字能够满意多个客户端的哀求。 它工作的方式很简朴--客户端知道服务运行在某个特定的端口(通常端口号是知名的,并且 特定的协议使用特定的端口号,但是服务器程序也可能运行在非标准的端口上)。它们建立连 接,在服务器程序内部,连接会被接受。服务器程序可以同时接受多个连接,在某个给定的时刻 也可以选择只接受一个连接。某个连接被接受后,它就表现为正常的套接字,形式为Socket对象- -一旦你把握了Socket类,编写服务器程序就和编写客户端程序几乎相同简朴了。服务器程序和客 户端程序的唯一区别是服务器程序帮定到特定的端口,使用ServerSocket对象。ServerSocket对 象就像创建客户端连接的工厂--你不必亲自建立Socket类的实例。这些连接都模仿正常的套接 字,因此你能够把输入和输出过滤流关联到这些连接上。 1、建立ServerSocket 你在建立服务器套接字后,就应该把它绑定到某个本地端口并预备接受输入的连接。当客户 端试图连接的时候,它们被放入一个队列中。一旦这个队列中的所有空间都被耗尽,其它的连接 的就会被拒绝。 构造函数 建立服务器套接字的最简朴的途径是绑定到某个本地地址,该地址作为使用构造函数的唯一 的参数。例如,为了在端口80(通常用于Web服务器程序)上提供某个服务,将使用下面的代码片 断: try { // 绑定到80端口,提供TCP服务(类似与HTTP) ServerSocket myServer = new ServerSocket ( 80 ); // ...... } catch (IOException ioe) { System.err.println ("I/O error - " + ioe); } 这是ServerSocket构造函数的最简朴的形式,但是下面有一些其它的答应更多自定义的构造 函数。所有这些函数都是公共的。 ? ServerSocket(int port)产生java.io.IOException、java.lang.SecurityException异 常--把服务器套接字绑定到特定的端口号,这样远程客户端才能定位TCP服务。假如传递进来的值 为零(zero),就使用任何空闲的端口--但是客户端可能没办法访问该服务,除非用什么方法通 知了客户端端口号是多少。在默认情况下,队列的大小设置为50,但是也提供了备用的构造函 数,它答应修改这个设置。假如端口已经被绑定了,或者安全性约束条件(例如安全性规则或知 名端口上的操作系统约束条件)阻挡了访问,就会产生非常。 ? ServerSocket(int port, int numberOfClients)产生java.io.IOException、 java.lang.SecurityException非常--把服务器套接字绑定到特定的端口号并为队列分配足够的空 间用于支持特定数量的客户端套接字。它是ServerSocket(int port)构造函数的重载版本,假如 端口已经被绑定了或安全性约束条件阻挡了访问,就产生非常。 ? ServerSocket(int port, int numberOfClients, InetAddress address)产生 java.io.IOException、java.lang.SecurityException非常--把服务器套接字绑定到特定的端口 号,为队列分配足够的空间以支持特定数量的客户端套接字。它是ServerSocket(int port, int numberOfClients)构造函数的重载版本,在多地址计算机上,它答应服务器套接字绑定到某个特 定的IP地址。例如,某台计算机可能有两块网卡,或者使用虚拟IP地址把它配置成像几台计算机 相同工作的时候。假如地址的值为空(null),服务器套接字将在所有的本地地址上接受哀求。 假如端口已经被绑定了或者安全性约束条件阻挡了访问,就产生非常。 2、使用ServerSocket 虽然Socket类几乎是通用的,并且有很多方式,但是Server Socket类没有太多的方式,除了 接受哀求并作为模仿客户端和服务器之间连接的Socket对象的产生组件就没有几个了。其中最重 要的方式是accept()方式,它接受客户端连接哀求,但是还有其它几个开发者可能感到有用的方 法。 方式 假如没有注明的话该方式就是公共的。 ? Socket accept()产生java.io.IOException、java.lang.Security非常--等待客户端向某 个服务器套接字哀求连接,并接受连接。它是一种阻塞(blocking)I/O操作,并且不会返回,直 到建立一个连接(除非设置了超时套接字选项)。当连接建立时,它将作为Socket对象被返回。 当接受连接的时候,每个客户端哀求都被默认的安全治理程序验证,这使得接受一定IP地址并阻 塞其它IP地址、产生非常成为可能。但是,服务器程序不必依靠安全治理程序阻塞或终止连接-- 可以通过调用客户端套接字的getInetAddress()方式确定客户端的身份。 ? void close()产生java.io.IOException非常--关闭服务器套接字,取消TCP端口的绑定, 答应其它的服务使用该端口。 ? InetAddress getInetAddress()--返回服务器套接字的地址,在多地址计算机中(例如某 个计算机的本地主机可以通过两个或多个IP地址访问)它可能与本地地址不同。 ? int getLocalPort()--返回服务器套接字绑定到的端口号。 ? int getSoTimeout()产生java.io.IOException非常--返回超时套接字选项的值,该值决 定accept()操作可以阻塞多少毫秒。假如返回的值为零,accept()操作无限期阻塞。 ? void implAccept(Socket socket)产生java.io.IOException非常--这个方式答应 ServerSocket子类传递一个未连接的套接字子类,让这个套接字对象接受输入的哀求。使用 implAccept方式接受连接时,重载的ServerSocket.accept()方式可以返回已连接的套接字。很少 开发者希望对ServerSocket再细分类,在不必要的情况下应该避免使用它。 ? static void setSocketFactory ( SocketImplFactory factory )产生 java.io.IOException、java.net.SocketException、java.lang.SecurityException非常--为JVM 指定服务器套接字产生组件。它是一个静态的方式,在JVM的生存周期中只能调用一次。假如禁止 指定新的套接字产生组件,或者已经指定了一个,就会产生非常。 ? void setSoTimeout(int timeout)产生java.net.SocketException非常--为accept()操作 指定一个超时值(以毫秒计算)。假如指定的值是零,超时设置就被禁止了,该操作将无限制阻 塞。但是,假如答应超时设置,在accept()方式被调用的时候就启动一个计时器。当计时器期满 时,产生java.io.InterruptedIOException非常,并答应服务器程序执行进一步的操作。 3、从客户端接受和处理哀求 服务器套接字的最重要的功能是接受客户端套接字。一旦获取了某个客户端套接字,服务器 就可以执行服务器程序的所有"真实的工作",包括从套接字读取信息、向套接字写入信息以实现 某种网络协议。发送或接收的正确数据依靠于该协议的具体情况。例如,对存储的消息提供访问 的邮件服务器将监听命令并发回消息内容。telnet服务器监听键盘输入并把这些信息传递给一个 登陆外壳(shell),并把输出发回网络客户端。详细协议的操作与网络的相关性很小,更多的面 向编程。 下面的代码片断演示了假如接受客户端套接字,以及I/O流怎样连接到客户端: // 执行阻塞的读取操作,读取下一个套接字 Socket nextSocket = someServerSocket.accept(); // 连接到流的过滤器读取和写入程序 BufferedReader reader = new BufferedReader (new InputStreamReader (nextSocket.getInputStream() ) ); PrintWriter writer = new PrintWriter( new OutputStreamWriter (nextSocket.getOutputStream() ) ); 从这个时候开始,服务器程序就可以处理任何需要完成的事务并响应客户端哀求了,或者可 以选择事务给另一个线程中的代码运行。请记住与Java中的其它形式的I/O操作类似,从客户端读 取回应的时候代码会无限制阻塞--因此为了为多个客户端并行服务,必须使用多线程。但是在简 单的情形中,多个执行线程可能是不必要的,特殊是在对哀求响应快速并且处理时间很短的情况 下。 建立完整实现通用Internet协议的客户端/服务器应用程序需要作大量的工作,对于网络编程 的新手来说这一点更为明显。它也需要其它一些技巧,例如多线程编程。从现在开始,我们聚焦 于一个简朴的、作为单线程应用程序执行的TCP服务器程序框架。 七、建立TCP服务器程序 网络编程的最有趣的部分之一是编写网络服务器。客户端发送哀求并响应发回来的数据,但 是服务器执行大多数真正的工作。下面的例子是一个daytime(日期时间)服务器(你可以使用上 面描述的客户端测试它)。 DaytimeServer的代码 import java.net.*; import java.io.*; public class DaytimeServer { public static final int SERVICE_PORT = 13; public static void main(String args[]) { try { // 绑定到服务端口,给客户端授予访问TCP daytime服务的权限 ServerSocket server = new ServerSocket (SERVICE_PORT); System.out.println ("Daytime service started"); // 无限循环,接受客户端 for (;;) { // 获取下一个TCP客户端 Socket nextClient = server.accept(); // 显示连接细节 System.out.println ("Received request from " + nextClient.getInetAddress() + ":" + nextClient.getPort() ); // 不读取数据,只是向消息写信息 OutputStream out = nextClient.getOutputStream(); PrintStream pout = new PrintStream (out); // 把当前数据显示给用户 pout.print( new java.util.Date() ); // 清除未发送的字节 out.flush(); // 关闭流 out.close(); // 关闭连接 nextClient.close(); } } catch (BindException be) { System.err.println ("Service already running on port " + SERVICE_PORT ); } catch (IOException ioe) { System.err.println ("I/O error - " + ioe); } } } DaytimeServer是如何工作的 这是最简朴的服务器程序了。这个服务器程序的第一步是建立一个ServerSocket。假如端口 已经绑定了,将会产生一个BindException非常,因为两个服务器程序不可能共享一样的端口。否 则,就建立了服务器套接字。下一步是等待连接。 因为daytime是个异常简朴的协议,并且我们的第一个TCP服务器程序示例必须很简朴,所以 我们此处使用了单线程服务器程序。在简朴的TCP服务器程序中通常使用无限运行的for循环,或 者使用表达式的值一直为true的While循环。在这个循环中,第一行是server.accept()方式,它 会阻塞代码运行直到某个客户端试图连接为止。这个方式返回一个表示某个客户端的连接的套接 字。为了记录数据,该连接的IP地址和端口号被发送到System.out。你将看到每次某个人登陆进 来并获取某天的时间。 Daytime是一个仅作应答(response-only)的协议,因此我们不需要担心对任何输入信息的 读取过程。我们获得了一个OutputStream(输出流),接着把它包装进PrintStream(打印流), 使它工作更简朴。我们在使用java.util.Date类决定日期和时间后,基于TCP流把它发送给客户 端。最后,我们清除了打印流中的所有数据并通过在套接字上调用close()关闭该连接。 运行DaytimeServer 运行该服务器程序是很简朴的。该服务器程序没有命令行参数。假如这个服务器程序示例需 要运行在UNIX上,你需要把变量SERVICE_PORT的值该为1024,除非你关闭默认的daytime进程并作 为root运行这个示例。在Windows或其它操作系统上,就没有这个问题。假如需要在本机上运行该 服务器程序,需要使用下面的命令: java DaytimeServer 八、非常处理:特定套接字的非常 网络作为通讯的媒介布满了各种问题。随着大量的计算机连接到了全球Internet,遭碰到某 个主机名称无法解析、某个主机从网络断开了、或者某个主机在连接的过程中被锁定了的情形在 软件应用程序的生存周期中是很可能碰到的。因此,知道引起应用程序中出现的这类问题的条件 并很好的处理这些问题是很重要的。当然,并不是每个程序都需要精确的控制,在简朴的应用程 序中你可能希望使用通用的处理方式处理各种问题。但是对于更高级的应用程序,了解运行时可 能出现的特定套接字非常是很重要的。 注重 所有的特定套接字非常都扩展自SocketException,因此通过捕获该非常,你可以捕获到所有 的特定套接字的非常并编写一个通用的处理程序。此外,SocketException扩展自 java.io.IOException,假如你希望提供捕获所有I/O非常的处理程序可以使用它。 1、 SocketException java.net.SocketException表现了一种通用的套接字错误,它可以表现一定范围的特定错误 条件。对于更细致的控制,应用程序应该捕获下面讨论的子类。 2、 BindException java.net.BindException表明没有能力把套接字帮定到某个本地端口。最普通的原因是本地 端口已经被使用了。 3、ConnectException 当某个套接字不能连接到特定的远程主机和端口的时候,java.net.ConnectException就会发 生。发生这种情况有一个原因,例如远程服务器没有帮定到某个端口的服务,或者它被排队的查 询沉没了,不能接收更多的哀求。 4、 NoRouteToHostException 当由于出现网络错误,不能找到远程主机的路由的时候产生 java.net.NoRouteToHostException非常。它的起因可能是本地的(例如软件应用程序运行的网络 正在运行),可能是临时的网关或路由器问题,或者是套接字试图连接的远程网络的故障。另一 个普通原因是防火墙和路由器阻止了客户端软件,这通常是个持久的限制。 5、InterruptedIOException 当某个读取操作被阻塞了一段时间引起网络超时的时候产生 java.net.InterruptedIOException非常。处理超时问题是使代码更加牢固和可靠的很好的途径。 九、总结 在TCP中使用套接字通讯是你应该把握的一种重要的技术,因为目前使用的大多数有趣的应用 程序协议都是在TCP上出现的。Java套接字API提供了一种清楚的、易于使用的机制,利用这种机 制开发者可以作为服务器接受通讯或作为客户端启动通讯。通过使用前面讨论的概念(包括Java 下的输入和输出流),过渡到基于套接字的通讯是很直接的。有了建立在java.net程序包中的异 常处理水平后,很轻易处理运行时发生的网络错误。 返回类别: 教程 上一教程: 学习心得:JAVA为什么支持反射机制 下一教程: SERVLET开发中JDBC的高级应用 您可以阅读与"JAVA网络编程之传输控制协议(三)"相关的教程: · JAVA网络编程之传输控制协议(一) · JAVA网络编程之传输控制协议(二) · JAVA网络编程之URI、URL研究 · Java中的异步网络编程 · Java 网络编程---I/O部分学习笔记整理1 |
![]() ![]() |
快精灵印艺坊 版权所有 |
首页![]() ![]() ![]() ![]() ![]() ![]() ![]() |