网络I/O模型--03非阻塞模式(ServerSocket与Socket的超时处理)--解除accept()、 read()方法阻塞
对于阻塞方式的一种改进是在应用程序层面上将 “一直等待 ”的状态主动打开:
这种模式下,应用程序的线程不再一直等待操作系统的 I/O状态,而是在等待一段时间后就解除阻塞。如果没有得到想要的结果,则再次进行相同的操作 。 这样的工作方式,保证了应用程序的线程不会一直阻塞,而可以进行一些其他工作一一例如软件业务层面上暂时不需要这些网络数据的操作过程
服务端代码(对accept()方法也解除阻塞)
package testBlockSocket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //通过非阻塞的方式处理Socket连接 public class SocketServer3SocketTimeout { private final static Logger LOGGER = LoggerFactory.getLogger(SocketServer3SocketTimeout.class); private static Object xWait = new Object(); public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8888); // 设定阻塞时间 serverSocket.setSoTimeout(100); while (true) { Socket socket = null; try { // 程序不会一直在这里阻塞了 socket = serverSocket.accept(); } catch (SocketTimeoutException el) { // 执行到这里,说明本次 accept ()方法没有接收到任何数据报文,主线程在这里就可以做一些事情,记为 x synchronized (SocketServer3SocketTimeout.xWait) { LOGGER.info("这次没有接收到 TCP 连接,据报文,等待 10 毫秒,模拟事件 x 的处理时间"); SocketServer3SocketTimeout.xWait.wait(10); } continue; } InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); Integer sourcePort = socket.getPort(); int maxLen = 2048; byte[] contextBytes = new byte[maxLen]; int realLen; StringBuffer message = new StringBuffer(); // 以下接收数据,与 7.2.1 节中的代码处理过程一致 while ((realLen = inputStream.read(contextBytes, 0, maxLen)) != -1) { message.append(new String(contextBytes, 0, realLen)); // 我们假设读取到"over"关键字表示一段内容传输完成 if (message.indexOf("over") != -1) { break; } } // 下面打印信息 LOGGER.info("服务器收到来自于端口 : " + sourcePort + "的信息:" + message); // 下面开始发送信息 outputStream.write("回发响应信息 !".getBytes()); // 关闭 outputStream.close(); inputStream.close(); socket.close(); } } catch (Exception e) { SocketServer3SocketTimeout.LOGGER.error(e.getMessage(), e); } finally { // 关闭连接 if (serverSocket != null) { serverSocket.close(); } } } }
服务端代码改进(对accept()和read()方法解除阻塞)
package testBlockSocket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; //通过非阻塞的方式处理Socket连接 //通过非阻塞的方式同时处理read() public class SocketServer3SocketTimeoutReadTimeout { private final static Logger LOGGER = LoggerFactory.getLogger(SocketServer3SocketTimeoutReadTimeout.class); private static Object xWait = new Object(); public static void main(String[] args) throws IOException { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(8888); // 设定阻塞时间 serverSocket.setSoTimeout(100); while (true) { Socket socket = null; try { socket = serverSocket.accept(); } catch (SocketTimeoutException el) { // =================== // 执行到这里,说明本次 accept()方法没有接收到任何 TCP 连接主线程在这里就可以做一些事情,记为 x // ================== synchronized (SocketServer3SocketTimeoutReadTimeout.xWait) { LOGGER.info("这次没有接收到 TCP 连接,等待10 毫秒,模拟事件x 的处理时间 "); SocketServer3SocketTimeoutReadTimeout.xWait.wait(10); } continue; } InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream(); Integer sourcePort = socket.getPort(); int maxLen = 2048; byte[] contextBytes = new byte[maxLen]; int realLen; StringBuffer message = new StringBuffer(); // 下面我们收取信息(非阻塞方式, read()方法的等待超时时间) socket.setSoTimeout(10); BIORead: while (true) { try { while ((realLen = inputStream.read(contextBytes, 0, maxLen)) != -1) { message.append(new String(contextBytes, 0, realLen)); // 我们同样假设读取到"over"关键字,表示业务内容传输完成 if (message.indexOf("over") != -1) { break BIORead; } } } catch (SocketTimeoutException e2) { // ================= // 执行到这里,说明本次 read ()方法没有接收到任何数据流主线程在这里又可以做一些事情,记为 Y // ================= LOGGER.info("这次没有接收到任务数据报文,等待 10 ~盖秒 ,模拟事件 Y 的处理时间 "); continue; } } // 下面打印信息 LOGGER.info("服务器收到来自子端口:" + sourcePort + "的信息:" + message); // 下面开始发送信息 outputStream.write(" 回发响应信息 !".getBytes()); // 关闭in和 out 对象 inputStream.close(); outputStream.close(); } } catch (Exception e) { LOGGER.error(e.getMessage(), e); } finally { // 关闭服务 if (serverSocket != null) { serverSocket.close(); } } } }
对阻塞模型的改进 : 让 TCP 连接和数据读取这两个过程,都变成了“非阻塞”方式 。
这种方式对网络I/O 性能的提升意义不大,原因是这种处理方式实际上并没有解决accept()方法、 read()方法阻塞的根本问题 。 根据上文的描述, accept()方法、 read()方法阻塞的根本问题是底层接收数据时采用 了操作系统提供的“同步 I/O”工作方式。这两次改进过程,只是解决了I/O 操作的两步中的第一步:将程序层面的阻塞方式变成了非阻塞方式 。