本文地址: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;}@Overridepublic 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;}@Overridepublic 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 requestsselector = Selector.open();// Create a new server socket and set to non blocking modeserverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);// Bind the server socket to the local host and portserverSocketChannel.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 keysIterator<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/Oexecute((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