JAVA网络编程之传输控制协议(三)
类别: JSP教程
五、建立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程序包中的异
常处理水平后,很容易处理运行时发生的网络错误。
讨论了套接字类的功能后,我们将分析一个完整的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网络编程之传输控制协议(二)
- 下一篇: VRML与JAVA在网络课件中交互运用分析
-= 资 源 教 程 =-
文 章 搜 索