多任务处理:阻塞和超时
阻塞和超时
Socket的I/O调用可能会因为多种原因而阻塞。数据输入方法read()和receive()在没有数据可读时会阻塞。TCP套接字的write()方法在没有足够的空间缓存传输的数据时可能阻塞。 ServerSocket 的accept()方法和Socket的构造函数都会阻塞等待,直到连接建立。同时,长的信息往返时间,高错误率的连接和慢速的(或已发生故障的)服务器,都可能导致需要很长的时间来建立连接。所有这些情况,只有在连接请求得到满足后这些方法才会返回。当然,调用一个已经阻塞的方法将使应用程序停止(并使运行它的线程无效)。
当程序在等待一次调用的完成时如果还有其他任务要执行的情况会怎样(如,更新"忙碌"状态的光标或响应用户请求)?这些程序可能没有时间来等待一个阻塞的方法调用。那UDP数据报文丢失的情况呢?如果我们阻塞等待接收一个数据报文,而它已经丢失,则会导致程序无限期地阻塞下去。这里,我们将对各种阻塞方法和限制阻塞的途径进行探讨。
1 accept(),read()和receive()
对于这些方法,我们可以使用Socket类、ServerSocket类和DatagramSocket类的setSoTimeout()方法,设置其阻塞的最长时间(以毫秒为单位)。如果在指定时间内这些方法没有返回,则将抛出一个InterruptedIOException异常。对于Socket实例,在调用read()方法前,我们还可以使用该套接字的InputStream的available()方法来检测是否有可读的数据。
2连接和写数据
Socket类的构造函数会尝试根据参数中指定的主机和端口来建立连接,并阻塞等待,直到连接成功建立或发生了系统定义的超时。不幸的是,系统定义的超时时间很长,而Java又没有提供任何缩短它的方法。要改变这种情况,可以使用Socket类的无参数构造函数,它返回的是一个没有建立连接的Socket实例。需要建立连接时,调用该实例的connect()方法,并指定一个远程终端和超时时间(毫秒)。
write()方法调用也会阻塞等待,直到最后一个字节成功写入到了TCP实现的本地缓存中。如果可用的缓存空间比要写入的数据小,在write()方法调用返回前,必须把一些数据成功传输到连接的另一端(详情见第6.1节)。因此,write()方法的阻塞总时间最终还是取决于接收端的应用程序。不幸的是Java现在还没有提供任何使write()超时或由其他线程将其打断的方法。所以如果一个可以在Socket实例上发送大量数据的协议可能会无限期地阻塞下去。
3限制每个客户端的时间
现在假设要实现一个为每个客户端限定了服务时间的回显协议。也就是说我们定义一个了目标,TIMELIMIT,并在协议中实现经过TIMELIMIT毫秒后,实例就自动终止。协议实例保持了对剩余服务时间的跟踪,并使用setSoTimeout()方法来保证read()方法的阻塞时间不会超过TIMELIMIT。由于没有办法限制write()调用的时间,我们并不能保证所定义的时间限制真正有效。尽管如此,TimelimitEchoProtocol.java还是实现了这种方法;要与TCPEchoServerExecutor.java一起使用,只需要简单地将while循环的第二行改为:
service.execute(new TimeLimitEchoProtocol(clntSock, logger));
后面将介绍更强大的机制(在所有的I/O调用上,包括写操作)来限制线程阻塞时间,这些机制都使用NIO包的工具类实现。
TimeLimitEchoProtocol.java
0 import java.io.IOException;
1 import java.io.InputStream;
2 import java.io.OutputStream;
3 import java.net.Socket;
4 import java.util.logging.Level;
5 import java.util.logging.Logger;
6
7 class TimelimitEchoProtocol implements Runnable {
8 private static final int BUFSIZE = 32; // Size (bytes) of buffer
9 private static final String TIMELIMIT = "10000"; // Default limit
(ms)
10 private static final String TIMELIMITPROP = "Timelimit"; //
Property
11
12 private static int timelimit;
13 private Socket clntSock;
14 private Logger logger;
15
16 public TimelimitEchoProtocol(Socket clntSock, Logger logger)
{
17 this.clntSock = clntSock;
18 this.logger = logger;
19 // Get the time limit from the System properties or take the
default
20 timelimit =
Integer.parseInt(System.getProperty(TIMELIMITPROP,TIMELIMIT));
21 }
22
23 public static void handleEchoClient(Socket clntSock, Logger
logger) {
24
25 try {
26 // Get the input and output I/O streams from socket
27 InputStream in = clntSock.getInputStream();
28 OutputStream out = clntSock.getOutputStream();
29 int recvMsgSize; // Size of received message
30 int totalBytesEchoed = 0; // Bytes received from client
31 byte[] echoBuffer = new byte[BUFSIZE]; // Receive buffer
32 long endTime = System.currentTimeMillis() + timelimit;
33 int timeBoundMillis = timelimit;
34
35 clntSock.setSoTimeout(timeBoundMillis);
36 // Receive until client closes connection, indicated by -1
37 while ((timeBoundMillis > 0) && // catch zero values
38 ((recvMsgSize = in.read(echoBuffer)) != -1)) {
39 out.write(echoBuffer, 0, recvMsgSize);
40 totalBytesEchoed += recvMsgSize;
41 timeBoundMillis = (int) (endTime -
System.currentTimeMillis()) ;
42 clntSock.setSoTimeout(timeBoundMillis);
43 }
44 logger.info("Client " + clntSock.getRemoteSocketAddress() +
45 ", echoed " + totalBytesEchoed + " bytes.");
46 } catch (IOException ex) {
47 logger.log(Level.WARNING, "Exception in echo protocol", ex);
48 }
49 }
50
51 public void run() {
52 handleEchoClient(this.clntSock, this.logger);
53 }
54 }
TimeLimitEchoProtocol.java
TimelimitEchoProtocol类与EchoProtocol类非常相似,唯一的区别在于它试图将回显连接的总服务时间限制在10秒钟之内。当handleEchoClient() 方法被调用时,就通过当前时间和服务期限计算出了服务的截止时间。每次read()调用结束后将重新计算当前时间与截止时间的差值,即剩余服务时间,并将套接字超时设置为该剩余时间。
相关下载:
Java_TCPIP_Socket编程(doc)
http://download.csdn.net/detail/undoner/4940239
文献来源:
UNDONER(小杰博客) :http://blog.csdn.net/undoner
LSOFT.CN(琅软中国) :http://www.lsoft.cn