一. Socket 的I/O 调用可能会因为多种原因而阻塞。 数据输入方法 read(), receive() 在没有数据可读时会阻塞, 也就是等待.
Tcp 协议的 write() 在没有足够的空间缓存传输的数据时可能阻塞.
ServerSocket 的 accept() 和 Socket 的构造方法都会阻塞等待, 直到连接建立.
如果阻塞等待接收一个数据报文,而它已经丢失,则程序会无限期地阻塞下去.
1. accept(), read() 和 receive()
对于这些方法,可以使用Socket, ServerSocket , DatagramSocket类的setSoTimeout()方法,设置基阻塞的最长时间,如果超过此时间,则抛出一个InterruptedIOException, 即 阴断 IO 异常. 对于Socket 实例, 在调用read() 方法前, 还可能使用Socket对象的InputStream 的 available() 来检查是否有可读的数据.
Socket() 无参构造方法返回一个没有建立连接的Socket实例, 需要连接时,调用该实例的connect(), 并指定一个远程终端和超时时间
2. write() 调用也会阻塞等待, 直到最后一个字节成功写入到 TCP 实现的本地缓存中。 如果可用的缓存空间比要写入的数据小, 在write() 调用返回前, 必须把一些数据成功传输到连接的另一端. write() 方法的阻塞总时间最终还是取决于接收端的应用程序.
现在看一个实际示例:
实现一个为每个客户端限定了服务时间的回显协议, 限定了一个时长TimeLimit,程序经过TimeLimit, 实现就自动终止.
import java.io.IOException;
import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; class TimelimitEchoProtocol implements Runnable { private static final int BUFSIZE = 32; // Size (bytes) buffer private static final String TIMELIMIT = "10000"; // Default limit (ms) private static final String TIMELIMITPROP = "Timelimit"; // Thread property private static int timelimit; private Socket clntSock; private Logger logger; public TimelimitEchoProtocol(Socket clntSock, Logger logger) { this.clntSock = clntSock; this.logger = logger; // Get the time limit from the System properties or take the default timelimit = Integer.parseInt(System.getProperty(TIMELIMITPROP,TIMELIMIT)); } public static void handleEchoClient(Socket clntSock, Logger logger) { try { // Get the input and output I/O streams from socket InputStream in = clntSock.getInputStream(); OutputStream out = clntSock.getOutputStream(); int recvMsgSize; // Size of received message int totalBytesEchoed = 0; // Bytes received from client byte[] echoBuffer = new byte[BUFSIZE]; // Receive buffer long endTime = System.currentTimeMillis() + timelimit; int timeBoundMillis = timelimit; clntSock.setSoTimeout(timeBoundMillis); // Receive until client closes connection, indicated by -1 while ((timeBoundMillis > 0) && // catch zero values ((recvMsgSize = in.read(echoBuffer)) != -1)) { out.write(echoBuffer, 0, recvMsgSize); totalBytesEchoed += recvMsgSize; timeBoundMillis = (int) (endTime - System.currentTimeMillis()) ; clntSock.setSoTimeout(timeBoundMillis); } logger.info("Client " + clntSock.getRemoteSocketAddress() + ", echoed " + totalBytesEchoed + " bytes."); } catch (IOException ex) { logger.log(Level.WARNING, "Exception in echo protocol", ex); } } public void run() { handleEchoClient(this.clntSock, this.logger); } }
Timelimit EchoProtocol 类试图将回显连接的总服务时间限制在10 s内, 当 handleEchoClient() 被调用时,就通过当前时间和服务期限计算出服务的截止时间。 每次 read() 调用结束后将重新计算当前时间与截止时间的差值 , 即剩余服务