网络编程Socket的阻塞和非阻塞IO
Posted S_Ignatius
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络编程Socket的阻塞和非阻塞IO相关的知识,希望对你有一定的参考价值。
网络应用程序一个很重要的工作是传输数据。传输数据的过程不一样取决于使用哪种“交通工具“,但是传输的方式都是一样的:都是以字节码传输。JAVA开发网络程序传输数据的过程和方式是被抽象了的,我们不需要关注底层接口,只需要使用Java API 或其他网络框架就能达到数据传输的目的。发送数据和接收数据都是字节码。
Socket网络编程我就不多啰嗦了,这里我通过两个简单的示例比较下阻塞式IO(OIO)和非阻塞式IO(NIO)。
OIO中,每个线程只能处理一个channel,该线程和该channel绑定。也就是同步的,客户端在发送请求后,必须得在服务端有回应后才发送下一个请求。所以这个时候的所有请求将会在服务端得到同步。
NIO中,每个线程可以处理多个channel。也就是异步的,客户端在发送请求后,不必等待服务端的回应就可以发送下一个请求,这样对于所有的请求动作来说将会在服务端得到异步,这条请求的链路就象是一个请求队列,所有的动作在这里不会得到同步的。
你可能使用过Java提供的网络接口工作过,遇到过想从阻塞传输切换到非阻塞传输的情况,这种情况是比较困难的,因为阻塞IO和非阻塞IO使用的API有很大的差异。当我们想切换传输方式时要花很大的精力和时间来重构代码。
先看一个传统的阻塞IO传输实现的Socket服务端:
1 /** 2 * 传统阻塞IO(OIO),原始socket 3 * 4 * <p>Title: PlainOioserver</p> 5 * @author wyx 6 * @date 2016-6-15 下午1:36:04 7 */ 8 public class PlainOioServer { 9 public void server(int port) throws Exception{ 10 // bind server to port 11 final ServerSocket socket = new ServerSocket(port); 12 while(true){ 13 // accept connection 14 final Socket clientSocket = socket.accept(); 15 System.out.println("Accepted connection form " + clientSocket); 16 // create new thread to handle connection 17 new Thread(new Runnable() { 18 19 @Override 20 public void run() { 21 OutputStream out; 22 try { 23 out = clientSocket.getOutputStream(); 24 // write message to connected client 25 out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8"))); 26 out.flush(); 27 // close connection once message written and flushed 28 clientSocket.close(); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 } 32 } 33 }).start(); // start thread to begin handling 34 } 35 } 36 }
上面的方式很简洁,但是这种阻塞模式在大连接的情况就会有严重的问题,如:客户端连接超时,服务器响应严重延迟等。为了解决这一问题,我们可以使用异步网络处理所有的并发连接,但问题在于NIO和OIO的API是完全不同的,所以一个用OIO开发的网络应用程序想要使用NIO重构代码几乎是重新开发。
下面代码是使用Java NIO实现的例子:
1 /** 2 * 传统非阻塞式IO(NIO),原始socket 3 * 4 * <p>Title: PlainNioServer</p> 5 * @author wyx 6 * @date 2016-6-15 下午1:46:09 7 */ 8 public class PlainNioServer { 9 public void server(int port) throws Exception{ 10 System.out.println("Listening for connections on port " + port); 11 // open selector that handles channels 12 Selector selector = Selector.open(); 13 // open ServerSocketChannel 14 ServerSocketChannel serverChannel = ServerSocketChannel.open(); 15 // get ServerSocket 16 ServerSocket serverSocket = serverChannel.socket(); 17 // bind server to port 18 serverSocket.bind(new InetSocketAddress(port)); 19 // set to non-blocking 20 serverChannel.configureBlocking(false); 21 // register ServerSocket to selector and specify than it is interested in new accepted clients 22 serverChannel.register(selector, SelectionKey.OP_ACCEPT); 23 final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes()); 24 while(true){ 25 // Wait for new events that are ready for process. this will block until something happens 26 int n = selector.select(); 27 if(n > 0){ 28 // Obtain all SelectionKey instances that received enents 29 Iterator<SelectionKey> iter = selector.selectedKeys().iterator(); 30 while(iter.hasNext()){ 31 SelectionKey key = iter.next(); 32 iter.remove(); 33 //Check if event was because new client ready to get accepted 34 if(key.isAcceptable()){ 35 ServerSocketChannel server = (ServerSocketChannel) key.channel(); 36 SocketChannel client = server.accept(); 37 System.out.println("Accepted connection from " + client); 38 client.configureBlocking(false); 39 // Accept client and register it to seletor 40 client.register(selector, SelectionKey.OP_WRITE, msg.duplicate()); 41 } 42 43 // Check if event was because socket is ready to write data 44 if(key.isWritable()){ 45 SocketChannel client = (SocketChannel) key.channel(); 46 ByteBuffer buff = (ByteBuffer) key.attachment(); 47 // Write date to connected client 48 while(buff.hasRemaining()){ 49 if(client.write(buff) == 0){ 50 break; 51 } 52 } 53 client.close(); 54 } 55 } 56 } 57 } 58 } 59 }
如你所见,即使它们实现的功能时候一样的,但是代码完全不同。根据不同需求选用不同的实现方式,当然,也可以直接选择流行的网络传输框架框架实现,如:Netty。以便于后期维护。
以上是关于网络编程Socket的阻塞和非阻塞IO的主要内容,如果未能解决你的问题,请参考以下文章