NIO-Socket通讯,为我们解决了server端多线程设计方面的性能/吞吐量等多方面的问题,它提供了以非阻塞模式 + 线程池的方式来解决Server端高并发问题..NIO并不能显著的提升Client-server的通讯性能(其中包括全局性耗时总和,Server物理机资源开销和实际计算量),但是它可以确保Server端在支撑相应的并发量情况下,对物理资源的使用处于可控状态.对于开发者而言,NIO合理的使用了平台(OS/VM/Http协议)的特性并提供了高效的便捷的编程级别的API.
为了展示,NIO交互的基本特性,我们模拟了一个简单的场景:Client端向server端建立连接,并持续交付大量数据,Server负载client的数据传输和处理.此程序实例并没有太多的关注异常处理和业务性处理,也没有使用线程池作为server端socket句柄管理,不过你可以简单的修改代码也实现它.
- TestMain.java:引导类
- ClientControllor.java:client连接处理类,负责队列化数据提交,并负责维护socket句柄.
- Packet.java:对于读取或者写入的buffer,进行二次封装,使其具有更好的可读性.
- ServerControllor.java:server端连接处理类,负责接收连接和数据处理
- ServerHandler.java:server端连接维护类.
TestMain.java:
package com.test.web; public class TestMain { /** * @param args */ public static void main(String[] args) throws Exception{ int port = 30008; ServerControllor sc = new ServerControllor(port); sc.start(); Thread.sleep(2000); ClientControllor cc = new ClientControllor("127.0.0.1", port); cc.start(); Packet p1 = Packet.wrap("Hello,I am first!"); cc.put(p1); Packet p2 = Packet.wrap("Hello,I am second!"); cc.put(p2); Packet p3 = Packet.wrap("Hello,I am thread!"); cc.put(p3); } }
ClientControllor.java
package com.test.web; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.zip.Adler32; import java.util.zip.Checksum; public class ClientControllor { private BlockingQueue<Packet> inner = new LinkedBlockingQueue<Packet>(100);//no any more private Object lock = new Object(); private InetSocketAddress remote; private Thread thread = new ClientThread(remote); public ClientControllor(String host,int port){ remote = new InetSocketAddress(host, port); } public void start(){ if(thread.isAlive() || remote == null){ return; } synchronized (lock) { thread.start(); } } public boolean put(Packet packet){ return inner.offer(packet); } public void clear(){ inner.clear(); } class ClientThread extends Thread { SocketAddress remote; SocketChannel channel; ClientThread(SocketAddress remote){ this.remote = remote; } @Override public void run(){ try{ try{ channel = SocketChannel.open(); channel.configureBlocking(true); boolean isSuccess = channel.connect(new InetSocketAddress(30008)); if(!isSuccess){ while(!channel.finishConnect()){ System.out.println("Client is connecting..."); } } System.out.println("Client is connected."); // Selector selector = Selector.open(); // channel.register(selector, SelectionKey.OP_WRITE); // while(selector.isOpen()){ // selector.select(); // Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // while(it.hasNext()){ // SelectionKey key = it.next(); // it.remove(); // if(!key.isValid()){ // continue; // } // if(key.isWritable()){ // write(); // } // } // } while(channel.isOpen()){ write(); } }catch(Exception e){ e.printStackTrace(); }finally{ if(channel != null){ try{ channel.close(); }catch(Exception ex){ ex.printStackTrace(); } } } }catch(Exception e){ e.printStackTrace(); inner.clear(); } } private void write() throws Exception{ Packet packet = inner.take(); synchronized (lock) { ByteBuffer body = packet.getBuffer();// ByteBuffer head = ByteBuffer.allocate(4); head.putInt(body.limit()); head.flip(); while(head.hasRemaining()){ channel.write(head); } Checksum checksum = new Adler32(); while(body.hasRemaining()){ checksum.update(body.get()); } body.rewind(); while(body.hasRemaining()){ channel.write(body); } long cks = checksum.getValue(); ByteBuffer tail = ByteBuffer.allocate(8); tail.putLong(cks); tail.flip(); while(tail.hasRemaining()){ channel.write(tail); } } } } }
Handler.java(接口,面向设计):
package com.test.web; import java.nio.channels.SocketChannel; public interface Handler { public void handle(SocketChannel channel); }
Packet.java
package com.test.web; import java.io.Serializable; import java.nio.ByteBuffer; import java.nio.charset.Charset; public class Packet implements Serializable { /** * */ private static final long serialVersionUID = 7719389291885063462L; private ByteBuffer buffer; private static Charset charset = Charset.defaultCharset(); private Packet(ByteBuffer buffer){ this.buffer = buffer; } public String getDataAsString(){ return charset.decode(buffer).toString(); } public byte[] getData(){ return buffer.array(); } public ByteBuffer getBuffer(){ return this.buffer; } public static Packet wrap(ByteBuffer buffer){ return new Packet(buffer); } public static Packet wrap(String data){ ByteBuffer source = charset.encode(data); return new Packet(source); } }
ServerControllor.java
package com.test.web; import java.net.InetSocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; public class ServerControllor { private int port; private Thread thread = new ServerThread();; private Object lock = new Object(); public ServerControllor(){ this(0); } public ServerControllor(int port){ this.port = port; } public void start(){ if(thread.isAlive()){ return; } synchronized (lock) { thread.start(); System.out.println("Server starting...."); } } class ServerThread extends Thread { private static final int TIMEOUT = 3000; private ServerHandler handler = new ServerHandler(); @Override public void run(){ try{ ServerSocketChannel channel = null; try{ channel = ServerSocketChannel.open(); channel.configureBlocking(false); channel.socket().setReuseAddress(true); channel.socket().bind(new InetSocketAddress(port)); Selector selector = Selector.open(); channel.register(selector, SelectionKey.OP_ACCEPT); while(selector.isOpen()){ System.out.println("Server is running,port:" + channel.socket().getLocalPort()); if(selector.select(TIMEOUT) == 0){ continue; } Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while(it.hasNext()){ SelectionKey key = it.next(); it.remove(); if(!key.isValid()){ continue; } if(key.isAcceptable()){ accept(key); }else if(key.isReadable()){ read(key); } } } }catch(Exception e){ e.printStackTrace(); }finally{ if(channel != null){ try{ channel.close(); }catch(Exception ex){ ex.printStackTrace(); } } } }catch(Exception e){ e.printStackTrace(); } } private void accept(SelectionKey key) throws Exception{ SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept(); socketChannel.configureBlocking(true); //socketChannel.register(key.selector(), SelectionKey.OP_READ); handler.handle(socketChannel); } private void read(SelectionKey key) throws Exception{ SocketChannel channel = (SocketChannel)key.channel(); //handler.handle(channel); } } }
ServerHandler.java
package com.test.web; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Semaphore; import java.util.zip.Adler32; import java.util.zip.Checksum; class ServerHandler implements Handler { private static Semaphore semaphore = new Semaphore(Runtime.getRuntime().availableProcessors() + 1); private static Map<SocketChannel,Thread> holder = new HashMap<SocketChannel,Thread>(32); @Override public void handle(SocketChannel channel) { synchronized (holder) { if(holder.containsKey(channel)){ return; } Thread t = new ReadThread(channel); holder.put(channel, t); t.start(); } } static class ReadThread extends Thread{ SocketChannel channel; ReadThread(SocketChannel channel){ this.channel = channel; } @Override public void run(){ try{ semaphore.acquire(); boolean eof = false; while(channel.isOpen()){ //ByteBuffer byteBuffer = new ByteBuffer(1024); ByteBuffer head = ByteBuffer.allocate(4);//int for data-size while(true){ int cb = channel.read(head); if(cb == -1){ throw new RuntimeException("EOF error,data lost!"); } if(isFull(head)){ break; } } head.flip(); int dataSize = head.getInt(); if(dataSize <= 0){ throw new RuntimeException("Data format error,something lost???"); } ByteBuffer body = ByteBuffer.allocate(dataSize); while(true){ int cb = channel.read(body); if(cb == -1){ throw new RuntimeException("EOF error,data lost!"); }else if(cb == 0 && this.isFull(body)){ break; } } ByteBuffer tail = ByteBuffer.allocate(8);//int for data-size while(true){ int cb = channel.read(tail); if(cb == -1){ eof = true; } if(isFull(tail)){ break; } } tail.flip(); long sck = tail.getLong(); Checksum checksum = new Adler32(); checksum.update(body.array(), 0, dataSize); long cck = checksum.getValue(); if(sck != cck){ throw new RuntimeException("Sorry,some data lost or be modified,please check!"); } body.flip(); Packet packet = Packet.wrap(body); System.out.println(packet.getDataAsString()); if(eof){ break; } } }catch(Exception e){ e.printStackTrace(); }finally{ if(channel != null){ try{ channel.close(); }catch(Exception ex){ ex.printStackTrace(); } } holder.remove(channel); semaphore.release(); } } private boolean isFull(ByteBuffer byteBuffer){ return byteBuffer.position() == byteBuffer.capacity() ? true : false; } } }
--End--