五、建立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