Java Socket實戰之六 使用NIO包實現Socket通信

本文地址:http://blog.csdn.net/kongxx/article/details/7288896

Java Socket實戰之一 單線程通信

Java Socket實戰之二 多線程通信

Java Socket實戰之三 傳輸對象

Java Socket實戰之四 傳輸壓縮對象

Java Socket實戰之五 使用加密協議傳輸對象

前面幾篇文章介紹了使用java.io和java.net類庫實現的Socket通信,下面介紹一下使用java.nio類庫實現的Socket。

java.nio包是Java在1.4之後增加的,用來提高I/O操作的效率。在nio包中主要包括以下幾個類或接口:

* Buffer:緩衝區,用來臨時存放輸入或輸出數據。

* Charset:用來把Unicode字符編碼和其它字符編碼互轉。

* Channel:數據傳輸通道,用來把Buffer中的數據寫入到數據源,或者把數據源中的數據讀入到Buffer。

* Selector:用來支持異步I/O操作,也叫非阻塞I/O操作。


nio包中主要通過下面兩個方面來提高I/O操作效率:

* 通過Buffer和Channel來提高I/O操作的速度。

* 通過Selector來支持非阻塞I/O操作。


下面來看一下程序中是怎麼通過這些類庫實現Socket功能。


首先介紹一下幾個輔助類

輔助類SerializableUtil,這個類用來把java對象序列化成字節數組,或者把字節數組反序列化成java對象。

package com.googlecode.garbagecan.test.socket; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerializableUtil { public static byte[] toBytes(Object object) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(baos); oos.writeObject(object); byte[] bytes = baos.toByteArray(); return bytes; } catch(IOException ex) { throw new RuntimeException(ex.getMessage(), ex); } finally { try { oos.close(); } catch (Exception e) {} } } public static Object toObject(byte[] bytes) { ByteArrayInputStream bais = new ByteArrayInputStream(bytes); ObjectInputStream ois = null; try { ois = new ObjectInputStream(bais); Object object = ois.readObject(); return object; } catch(IOException ex) { throw new RuntimeException(ex.getMessage(), ex); } catch(ClassNotFoundException ex) { throw new RuntimeException(ex.getMessage(), ex); } finally { try { ois.close(); } catch (Exception e) {} } } }輔助類MyRequestObject和MyResponseObject,這兩個類是普通的java對象,實現了Serializable接口。MyRequestObject類是Client發出的請求,MyResponseObject是Server端作出的響應。

package com.googlecode.garbagecan.test.socket.nio; import java.io.Serializable; public class MyRequestObject implements Serializable { private static final long serialVersionUID = 1L; private String name; private String value; private byte[] bytes; public MyRequestObject(String name, String value) { this.name = name; this.value = value; this.bytes = new byte[1024]; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Request [name: " + name + ", value: " + value + ", bytes: " + bytes.length+ "]"); return sb.toString(); } } package com.googlecode.garbagecan.test.socket.nio; import java.io.Serializable; public class MyResponseObject implements Serializable { private static final long serialVersionUID = 1L; private String name; private String value; private byte[] bytes; public MyResponseObject(String name, String value) { this.name = name; this.value = value; this.bytes = new byte[1024]; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } @Override public String toString() { StringBuffer sb = new StringBuffer(); sb.append("Response [name: " + name + ", value: " + value + ", bytes: " + bytes.length+ "]"); return sb.toString(); } }
下面主要看一下Server端的代碼,其中有一些英文註釋對理解代碼很有幫助,註釋主要是來源jdk的文檔和例子,這裏就沒有再翻譯

package com.googlecode.garbagecan.test.socket.nio; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.Logger; import com.googlecode.garbagecan.test.socket.SerializableUtil; public class MyServer3 { private final static Logger logger = Logger.getLogger(MyServer3.class.getName()); public static void main(String[] args) { Selector selector = null; ServerSocketChannel serverSocketChannel = null; try { // Selector for incoming time requests selector = Selector.open(); // Create a new server socket and set to non blocking mode serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); // Bind the server socket to the local host and port serverSocketChannel.socket().setReuseAddress(true); serverSocketChannel.socket().bind(new InetSocketAddress(10000)); // Register accepts on the server socket with the selector. This // step tells the selector that the socket wants to be put on the // ready list when accept operations occur, so allowing multiplexed // non-blocking I/O to take place. serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // Here's where everything happens. The select method will // return when any operations registered above have occurred, the // thread has been interrupted, etc. while (selector.select() > 0) { // Someone is ready for I/O, get the ready keys Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // Walk through the ready keys collection and process date requests. while (it.hasNext()) { SelectionKey readyKey = it.next(); it.remove(); // The key indexes into the selector so you // can retrieve the socket that's ready for I/O execute((ServerSocketChannel) readyKey.channel()); } } } catch (ClosedChannelException ex) { logger.log(Level.SEVERE, null, ex); } catch (IOException ex) { logger.log(Level.SEVERE, null, ex); } finally { try { selector.close(); } catch(Exception ex) {} try { serverSocketChannel.close(); } catch(Exception ex) {} } } private static void execute(ServerSocketChannel serverSocketChannel) throws IOException { SocketChannel socketChannel = null; try { socketChannel = serverSocketChannel.accept(); MyRequestObject myRequestObject = receiveData(socketChannel); logger.log(Level.INFO, myRequestObject.toString()); MyResponseObject myResponseObject = new MyResponseObject( "response for " + myRequestObject.getName(), "response for " + myRequestObject.getValue()); sendData(socketChannel, myResponseObject); logger.log(Level.INFO, myResponseObject.toString()); } finally { try { socketChannel.close(); } catch(Exception ex) {} } } private static MyRequestObject receiveData(SocketChannel socketChannel) throws IOException { MyRequestObject myRequestObject = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteBuffer buffer = ByteBuffer.allocate(1024); try { byte[] bytes; int size = 0; while ((size = socketChannel.read(buffer)) >= 0) { buffer.flip(); bytes = new byte[size]; buffer.get(bytes); baos.write(bytes); buffer.clear(); } bytes = baos.toByteArray(); Object obj = SerializableUtil.toObject(bytes); myRequestObject = (MyRequestObject)obj; } finally { try { baos.close(); } catch(Exception ex) {} } return myRequestObject; } private static void sendData(SocketChannel socketChannel, MyResponseObject myResponseObject) throws IOException { byte[] bytes = SerializableUtil.toBytes(myResponseObject); ByteBuffer buffer = ByteBuffer.wrap(bytes); socketChannel.write(buffer); } }下面是Client的代碼,代碼比較簡單就是啓動了100個線程來訪問Server

package com.googlecode.garbagecan.test.socket.nio; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.logging.Level; import java.util.logging.Logger; import com.googlecode.garbagecan.test.socket.SerializableUtil; public class MyClient3 { private final static Logger logger = Logger.getLogger(MyClient3.class.getName()); public static void main(String[] args) throws Exception { for (int i = 0; i < 100; i++) { final int idx = i; new Thread(new MyRunnable(idx)).start(); } } private static final class MyRunnable implements Runnable { private final int idx; private MyRunnable(int idx) { this.idx = idx; } public void run() { SocketChannel socketChannel = null; try { socketChannel = SocketChannel.open(); SocketAddress socketAddress = new InetSocketAddress("localhost", 10000); socketChannel.connect(socketAddress); MyRequestObject myRequestObject = new MyRequestObject("request_" + idx, "request_" + idx); logger.log(Level.INFO, myRequestObject.toString()); sendData(socketChannel, myRequestObject); MyResponseObject myResponseObject = receiveData(socketChannel); logger.log(Level.INFO, myResponseObject.toString()); } catch (Exception ex) { logger.log(Level.SEVERE, null, ex); } finally { try { socketChannel.close(); } catch(Exception ex) {} } } private void sendData(SocketChannel socketChannel, MyRequestObject myRequestObject) throws IOException { byte[] bytes = SerializableUtil.toBytes(myRequestObject); ByteBuffer buffer = ByteBuffer.wrap(bytes); socketChannel.write(buffer); socketChannel.socket().shutdownOutput(); } private MyResponseObject receiveData(SocketChannel socketChannel) throws IOException { MyResponseObject myResponseObject = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { ByteBuffer buffer = ByteBuffer.allocateDirect(1024); byte[] bytes; int count = 0; while ((count = socketChannel.read(buffer)) >= 0) { buffer.flip(); bytes = new byte[count]; buffer.get(bytes); baos.write(bytes); buffer.clear(); } bytes = baos.toByteArray(); Object obj = SerializableUtil.toObject(bytes); myResponseObject = (MyResponseObject) obj; socketChannel.socket().shutdownInput(); } finally { try { baos.close(); } catch(Exception ex) {} } return myResponseObject; } } }
最後測試上面的代碼,首先運行Server類,然後運行Client類,就可以分別在Server端和Client端控制檯看到發送或接收到的MyRequestObject或MyResponseObject對象了。

關於NIO和IO的比較,下面的兩篇文章對理解很有幫助,可以參考一下。

http://tutorials.jenkov.com/java-nio/nio-vs-io.html

https://blogs.oracle.com/slc/entry/javanio_vs_javaio


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章