Java NIO模式的Socket通訊,是一種同步非阻塞IO設計模式,它為Reactor模式實現提供了基礎。
下面看看,Java實現的一個服務端和用戶端通訊的例子。
NIO模式的基本原理描述如下:
服務端開啟一個通道(ServerSocketChannel),並向通道中註冊一個選取器(Selector),這個選取器是與一些感興趣的操作的標識(SelectionKey,即通過這個標識可以定位到具體的操作,從而進行響應的處理)相關聯的,然後基於選取器(Selector)輪詢通道(ServerSocketChannel)上註冊的事件,並進行相應的處理。
用戶端在請求與服務端通訊時,也可以向伺服器端一樣註冊(比服務端少了一個SelectionKey.OP_ACCEPT操作集合),並通過輪詢來處理指定的事件,而不必阻塞。
下面的例子,主要以服務端為例,而用戶端只是簡單地發送請求資料和讀響應資料。
服務端實現,代碼如下所示:
package org.shirdrn.java.communications.nio;</p><p>import java.io.IOException;<br />import java.net.InetSocketAddress;<br />import java.nio.ByteBuffer;<br />import java.nio.channels.SelectionKey;<br />import java.nio.channels.Selector;<br />import java.nio.channels.ServerSocketChannel;<br />import java.nio.channels.SocketChannel;<br />import java.util.Iterator;<br />import java.util.Set;<br />import java.util.logging.Logger;</p><p>/**<br /> * NIO服務端<br /> *<br /> * @author shirdrn<br /> */<br />public class NioTcpServer extends Thread {</p><p>private static final Logger log = Logger.getLogger(NioTcpServer.class.getName());<br />private InetSocketAddress inetSocketAddress;<br />private Handler handler = new ServerHandler();</p><p>public NioTcpServer(String hostname, int port) {<br />inetSocketAddress = new InetSocketAddress(hostname, port);<br />}</p><p>@Override<br />public void run() {<br />try {<br />Selector selector = Selector.open(); // 開啟選取器<br />ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 開啟通道<br />serverSocketChannel.configureBlocking(false); // 非阻塞<br />serverSocketChannel.socket().bind(inetSocketAddress);<br />serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 向通道註冊選取器和對應事件標識<br />log.info("Server: socket server started.");<br />while(true) { // 輪詢<br />int nKeys = selector.select();<br />if(nKeys>0) {<br />Set<SelectionKey> selectedKeys = selector.selectedKeys();<br />Iterator<SelectionKey> it = selectedKeys.iterator();<br />while(it.hasNext()) {<br />SelectionKey key = it.next();<br />if(key.isAcceptable()) {<br />log.info("Server: SelectionKey is acceptable.");<br />handler.handleAccept(key);<br />} else if(key.isReadable()) {<br />log.info("Server: SelectionKey is readable.");<br />handler.handleRead(key);<br />} else if(key.isWritable()) {<br />log.info("Server: SelectionKey is writable.");<br />handler.handleWrite(key);<br />}<br />it.remove();<br />}<br />}<br />}<br />} catch (IOException e) {<br />e.printStackTrace();<br />}<br />}</p><p>/**<br /> * 簡單一處理器介面<br /> *<br /> * @author shirdrn<br /> */<br />interface Handler {<br />/**<br /> * 處理{@link SelectionKey#OP_ACCEPT}事件<br /> * @param key<br /> * @throws IOException<br /> */<br />void handleAccept(SelectionKey key) throws IOException;<br />/**<br /> * 處理{@link SelectionKey#OP_READ}事件<br /> * @param key<br /> * @throws IOException<br /> */<br />void handleRead(SelectionKey key) throws IOException;<br />/**<br /> * 處理{@link SelectionKey#OP_WRITE}事件<br /> * @param key<br /> * @throws IOException<br /> */<br />void handleWrite(SelectionKey key) throws IOException;<br />}</p><p>/**<br /> * 服務端事件處理實作類別<br /> *<br /> * @author shirdrn<br /> */<br />class ServerHandler implements Handler {</p><p>@Override<br />public void handleAccept(SelectionKey key) throws IOException {<br />ServerSocketChannel serverSocketChannel = (ServerSocketChannel)key.channel();<br />SocketChannel socketChannel = serverSocketChannel.accept();<br />log.info("Server: accept client socket " + socketChannel);<br />socketChannel.configureBlocking(false);<br />socketChannel.register(key.selector(), SelectionKey.OP_READ);<br />}</p><p>@Override<br />public void handleRead(SelectionKey key) throws IOException {<br />ByteBuffer byteBuffer = ByteBuffer.allocate(512);<br />SocketChannel socketChannel = (SocketChannel)key.channel();<br />while(true) {<br />int readBytes = socketChannel.read(byteBuffer);<br />if(readBytes>0) {<br />log.info("Server: readBytes = " + readBytes);<br />log.info("Server: data = " + new String(byteBuffer.array(), 0, readBytes));<br />byteBuffer.flip();<br />socketChannel.write(byteBuffer);<br />break;<br />}<br />}<br />socketChannel.close();<br />}</p><p>@Override<br />public void handleWrite(SelectionKey key) throws IOException {<br />ByteBuffer byteBuffer = (ByteBuffer) key.attachment();<br />byteBuffer.flip();<br />SocketChannel socketChannel = (SocketChannel)key.channel();<br />socketChannel.write(byteBuffer);<br />if(byteBuffer.hasRemaining()) {<br />key.interestOps(SelectionKey.OP_READ);<br />}<br />byteBuffer.compact();<br />}<br />}</p><p>public static void main(String[] args) {<br />NioTcpServer server = new NioTcpServer("localhost", 1000);<br />server.start();<br />}<br />}
用戶端實現,代碼如下所示:
package org.shirdrn.java.communications.nio;</p><p>import java.io.IOException;<br />import java.net.InetSocketAddress;<br />import java.nio.ByteBuffer;<br />import java.nio.channels.SocketChannel;<br />import java.util.logging.Logger;</p><p>/**<br /> * NIO用戶端<br /> *<br /> * @author shirdrn<br /> */<br />public class NioTcpClient {</p><p>private static final Logger log = Logger.getLogger(NioTcpClient.class.getName());<br />private InetSocketAddress inetSocketAddress;</p><p>public NioTcpClient(String hostname, int port) {<br />inetSocketAddress = new InetSocketAddress(hostname, port);<br />}</p><p>/**<br /> * 發送請求資料<br /> * @param requestData<br /> */<br />public void send(String requestData) {<br />try {<br />SocketChannel socketChannel = SocketChannel.open(inetSocketAddress);<br />socketChannel.configureBlocking(false);<br />ByteBuffer byteBuffer = ByteBuffer.allocate(512);<br />socketChannel.write(ByteBuffer.wrap(requestData.getBytes()));<br />while (true) {<br />byteBuffer.clear();<br />int readBytes = socketChannel.read(byteBuffer);<br />if (readBytes > 0) {<br />byteBuffer.flip();<br />log.info("Client: readBytes = " + readBytes);<br />log.info("Client: data = " + new String(byteBuffer.array(), 0, readBytes));<br />socketChannel.close();<br />break;<br />}<br />}</p><p>} catch (IOException e) {<br />e.printStackTrace();<br />}<br />}</p><p>public static void main(String[] args) {<br />String hostname = "localhost";<br />String requestData = "Actions speak louder than words!";<br />int port = 1000;<br />new NioTcpClient(hostname, port).send(requestData);<br />}<br />}
上述實現,NioTcpServer服務線程啟動後,監聽指定連接埠,等待用戶端請求的到來,然後NioTcpClient用戶端進程啟動並發送請求資料,服務端接收到請求資料後,響應用戶端(將請求的資料作為響應資料寫回到用戶端通道SocketChannel,並等待用戶端處理)。
實際上,用戶端和服務端可以採用同樣輪詢的非阻塞模式來實現,為簡單實現在這個例子中我們把用戶端角色簡化了,而實際上它可能在另一個系統通訊中充當服務端角色。
另外,上面對於不同事件是採用非線程的方式來處理,只是簡單地調用處理的方法。在實際中,如果存在大量串連、讀寫請求,可以考慮使用線程池來更大程度地並發處理,提高服務端處理的速度和輸送量,提升系統效能。