Java NIO-非阻塞通信

Java NIO-非阻塞通信

标签: 非阻塞通信JavaSocketNIOJava网络编程
3318人阅读 评论(0) 收藏 举报
分类:

NIO(Non-block IO)指非阻塞通信,相对于其编程的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现

客户端:

  1. package com.test.client;  
  2.   
  3. import java.io.DataInputStream;  
  4. import java.io.DataOutputStream;  
  5. import java.io.IOException;  
  6. import java.net.InetAddress;  
  7. import java.net.InetSocketAddress;  
  8. import java.nio.channels.SocketChannel;  
  9.   
  10. import org.apache.log4j.Logger;  
  11.   
  12. import com.test.util.SocketIO;  
  13.   
  14. public class Client {  
  15.     static Logger logger = Logger.getLogger(Client.class);  
  16.     private int port = 10000;  
  17.     private SocketChannel socketChannel;  
  18.       
  19.     public Client(){  
  20.         try {  
  21.             socketChannel = SocketChannel.open();  
  22.             InetAddress host = InetAddress.getLocalHost();  
  23.             InetSocketAddress addr = new InetSocketAddress(host, port);  
  24.               
  25.             socketChannel.connect(addr);  
  26.               
  27.             logger.debug("***");  
  28.             logger.debug("client ip:"+socketChannel.socket().getLocalAddress());  
  29.             logger.debug("client port:"+socketChannel.socket().getLocalPort());  
  30.             logger.debug("server ip:"+socketChannel.socket().getInetAddress());  
  31.             logger.debug("server port:"+socketChannel.socket().getPort());  
  32.             logger.debug("***");  
  33.         } catch (IOException e) {  
  34.             e.printStackTrace();  
  35.             logger.error("Cilent socket establish failed!");  
  36.         }  
  37.         logger.info("Client socket establish success!");  
  38.     }  
  39.       
  40.     public void request(String request){  
  41.         try{  
  42.             DataInputStream input = SocketIO.getInput(socketChannel.socket());  
  43.             DataOutputStream output = SocketIO.getOutput(socketChannel.socket());  
  44.               
  45.             if(null != request && !request.equals("")){  
  46.                 byte[] bytes = request.getBytes("utf-8");  
  47.                 output.write(bytes);  
  48.           
  49.                 bytes = new byte[64];  
  50.                 int num = input.read(bytes);  
  51.                 byte[] answer = new byte[num];  
  52.                 System.arraycopy(bytes, 0, answer, 0, num);  
  53.                 if(num > 0){  
  54.                     logger.info("server answer:"+new String(answer,"utf-8"));  
  55.                 }else{  
  56.                     logger.info("No server answer.");  
  57.                 }  
  58.             }  
  59.         }catch(Exception e){  
  60.             e.printStackTrace();  
  61.             logger.error("client request error");  
  62.         }finally{  
  63.             if(null != socketChannel){  
  64.                 try{  
  65.                     socketChannel.close();  
  66.                 }catch(Exception e){  
  67.                     e.printStackTrace();  
  68.                     logger.error("socket close error");  
  69.                 }  
  70.             }  
  71.         }  
  72.     }  
  73.       
  74.     public static void main(String[] args){  
  75.         Client client1 = new Client();  
  76.         //Client client2 = new Client();  
  77.         client1.request("your name?");  
  78.         //client2.request("your name?");  
  79.     }  
  80. }  

服务端:

  1. package com.test.server;  
  2.   
  3. import java.net.InetSocketAddress;  
  4. import java.nio.ByteBuffer;  
  5. import java.nio.channels.SelectionKey;  
  6. import java.nio.channels.Selector;  
  7. import java.nio.channels.ServerSocketChannel;  
  8. import java.nio.channels.SocketChannel;  
  9. import java.nio.charset.Charset;  
  10. import java.util.Iterator;  
  11. import java.util.Set;  
  12.   
  13. import org.apache.log4j.Logger;  
  14.   
  15. public class Server {  
  16.     static Logger logger = Logger.getLogger(Server.class);  
  17.     private Selector selector;  
  18.     private ServerSocketChannel serverSocketChannel;  
  19.     private int queueNum = 10;  
  20.     private int bindPort = 10000;  
  21.     private int step = 0;  
  22.     private Charset charset = Charset.forName("utf-8");  
  23.     private ByteBuffer buffer = ByteBuffer.allocate(64);  
  24.       
  25.     public Server(){  
  26.         try{  
  27.             //为ServerSocketChannel监控接收连接就绪事件  
  28.             //为SocketChannel监控连接就绪事件、读就绪事件以及写就绪事件  
  29.             selector = Selector.open();  
  30.             //作用相当于传统通信中的ServerSocket  
  31.             //支持阻塞模式和非阻塞模式  
  32.             serverSocketChannel = ServerSocketChannel.open();  
  33.             serverSocketChannel.socket().setReuseAddress(true);  
  34.             //非阻塞模式  
  35.             serverSocketChannel.configureBlocking(false);  
  36.             //serverSocketChannel.socket()会获得一个和当前信道相关联的socket  
  37.             serverSocketChannel.socket().bind(new InetSocketAddress(bindPort),queueNum);  
  38.               
  39.             //注册接收连接就绪事件  
  40.             //注册事件后会返回一个SelectionKey对象用以跟踪注册事件句柄  
  41.             //该SelectionKey将会放入Selector的all-keys集合中,如果相应的事件触发  
  42.             //该SelectionKey将会放入Selector的selected-keys集合中  
  43.             serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);  
  44.         }catch(Exception e){  
  45.             e.printStackTrace();  
  46.             logger.error("Server establish error!");  
  47.         }  
  48.         logger.info("Server start up!");  
  49.     }  
  50.   
  51.     public void service() throws Exception{  
  52.         //判断是否有触发事件  
  53.         while(selector.select() > 0){  
  54.             Set<SelectionKey> selectedKeys = selector.selectedKeys();  
  55.             Iterator<SelectionKey> iterator = selectedKeys.iterator();  
  56.               
  57.             while(iterator.hasNext()){  
  58.                 SelectionKey selectionKey = iterator.next();  
  59.                 //处理事件后将事件从Selector的selected-keys集合中删除  
  60.                 iterator.remove();  
  61.                 try{  
  62.                     if(selectionKey.isAcceptable()){  
  63.                         this.Acceptable(selectionKey);  
  64.                     }else if(selectionKey.isReadable()){  
  65.                         this.Readable(selectionKey);  
  66.                     }else if(selectionKey.isWritable()){  
  67.                         this.Writable(selectionKey);  
  68.                     }  
  69.                 }catch(Exception e){  
  70.                     e.printStackTrace();  
  71.                     logger.error("event deal exception!");  
  72.                 }  
  73.             }  
  74.         }  
  75.     }  
  76.       
  77.     private void Acceptable(SelectionKey selectionKey) throws Exception{  
  78.         logger.info("accept:"+(++step));  
  79.           
  80.         ServerSocketChannel ssc = (ServerSocketChannel)selectionKey.channel();  
  81.         SocketChannel sc = (SocketChannel)ssc.accept();  
  82.           
  83.         sc.configureBlocking(false);  
  84.         sc.register(selector, SelectionKey.OP_READ);  
  85.           
  86.         logger.info(selectionKey.hashCode());  
  87.     }  
  88.       
  89.     private void Readable(SelectionKey selectionKey) throws Exception{  
  90.         logger.info("read:"+(++step));  
  91.           
  92.         SocketChannel sc = (SocketChannel)selectionKey.channel();  
  93.           
  94.         buffer.clear();  
  95.         int num = sc.read(buffer);  
  96.         String request = "";  
  97.         if(num > 0){  
  98.             buffer.flip();  
  99.               
  100.             request = charset.decode(buffer).toString();  
  101.             sc.register(selector, SelectionKey.OP_WRITE,request);  
  102.         }else{  
  103.             sc.close();  
  104.         }  
  105.           
  106.         logger.info(selectionKey.hashCode()+":"+request);  
  107.     }  
  108.       
  109.     private void Writable(SelectionKey selectionKey) throws Exception{  
  110.         logger.info("write:"+(++step));  
  111.           
  112.         String request = (String)selectionKey.attachment();  
  113.         SocketChannel sc = (SocketChannel)selectionKey.channel();  
  114.           
  115.         String answer = "not supported";  
  116.         if(request.equals("your name?")){  
  117.             answer = "server";  
  118.         }  
  119.           
  120.         logger.info(selectionKey.hashCode()+":"+answer);  
  121.           
  122.         buffer.clear();  
  123.         buffer.put(charset.encode(answer));  
  124.         buffer.flip();  
  125.         while(buffer.hasRemaining())  
  126.             sc.write(buffer);  
  127.           
  128.         sc.close();  
  129.     }  
  130.       
  131.     public static void main(String[] args) {  
  132.         Server server = new Server();  
  133.         try{  
  134.             server.service();  
  135.         }catch(Exception e){  
  136.             e.printStackTrace();  
  137.             logger.error("Server run exception!");  
  138.         }  
  139.     }  
  140. }  

IO工具类:

  1. package com.test.util;  
  2.   
  3. import java.io.DataInputStream;  
  4. import java.io.DataOutputStream;  
  5. import java.io.IOException;  
  6. import java.io.InputStream;  
  7. import java.io.OutputStream;  
  8. import java.net.Socket;  
  9.   
  10. public class SocketIO{  
  11.     public static DataInputStream getInput(Socket socket) throws IOException{  
  12.         //接收缓存区大小,socket获取输入流之前设置  
  13.         socket.setReceiveBufferSize(10);  
  14.         InputStream input = socket.getInputStream();  
  15.         return new DataInputStream(input);  
  16.     }  
  17.       
  18.     public static DataOutputStream getOutput(Socket socket) throws IOException{  
  19.         //发送缓存区大小,socket获取输出流之前设置  
  20.         socket.setSendBufferSize(10);  
  21.         OutputStream output = socket.getOutputStream();  
  22.         return new DataOutputStream(output);  
  23.     }  
  24. }  

log4j日志配置文件:

  1. log4j.rootLogger=debug,logOutput  
  2.   
  3. log console out put   
  4. log4j.appender.logOutput=org.apache.log4j.ConsoleAppender  
  5. log4j.appender.logOutput.layout=org.apache.log4j.PatternLayout  
  6. log4j.appender.logOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n  

server端的运行结果:

  1. INFO[13-10-16 11:40:41][com.test.server.Server] -> Server start up!  
  2. INFO[13-10-16 11:40:53][com.test.server.Server] -> accept:1  
  3. INFO[13-10-16 11:41:14][com.test.server.Server] -> 20469344  
  4. INFO[13-10-16 11:41:21][com.test.server.Server] -> read:2  
  5. INFO[13-10-16 11:41:37][com.test.server.Server] -> 11688861:your name?  
  6. INFO[13-10-16 11:43:00][com.test.server.Server] -> write:3  
  7. INFO[13-10-16 11:43:00][com.test.server.Server] -> 11688861:server  

可以看到readable方法中的SelectionKey和writable方法中的SelectionKey的哈希码是完全相同的,是同一个SelectionKey

SelectionKey是在SocketChannel类或ServerSocketChannel类注册要监控的事件时产生的,这两个类本身并没有register方法,需要查看它们共同父类AbstractSelectableChannel(只有关键代码):

  1. public abstract class AbstractSelectableChannel  
  2.         extends SelectableChannel{  
  3.     ......  
  4.     // Keys that have been created by registering this channel with selectors.  
  5.     // They are saved because if this channel is closed the keys must be  
  6.     // deregistered.  Protected by keyLock.  
  7.     private SelectionKey[] keys = null;  
  8.   
  9.     public final SelectionKey register(Selector sel, int ops, Object att)  
  10.             throws ClosedChannelException{  
  11.         if (!isOpen())  
  12.             throw new ClosedChannelException();  
  13.         if ((ops & ~validOps()) != 0)  
  14.             throw new IllegalArgumentException();  
  15.         synchronized (regLock) {  
  16.             if (blocking)  
  17.                 throw new IllegalBlockingModeException();  
  18.             SelectionKey k = findKey(sel);  
  19.             if (k != null) {  
  20.                 k.interestOps(ops);  
  21.                 k.attach(att);  
  22.             }  
  23.             if (k == null) {  
  24.                 // New registration  
  25.                 k = ((AbstractSelector)sel).register(this, ops, att);  
  26.                 addKey(k);  
  27.             }  
  28.             return k;  
  29.         }  
  30.     }  
  31.   
  32.     private SelectionKey findKey(Selector sel) {  
  33.         synchronized (keyLock) {  
  34.             if (keys == null)  
  35.                 return null;  
  36.             for (int i = 0; i < keys.length; i++)  
  37.                 if ((keys[i] != null) && (keys[i].selector() == sel))  
  38.                     return keys[i];  
  39.             return null;  
  40.         }  
  41.     }  
  42.   
  43.     void removeKey(SelectionKey k) {            // package-private  
  44.         synchronized (keyLock) {  
  45.             for (int i = 0; i < keys.length; i++)  
  46.             if (keys[i] == k) {  
  47.                 keys[i] = null;  
  48.                 keyCount--;  
  49.             }  
  50.             ((AbstractSelectionKey)k).invalidate();  
  51.         }  
  52.     }  
  53.     ......  
  54. }  

ServerSocketChannel和Socketchannel向Selector中注册了特定事件,Selector就会监控这些事件是否发生。ServerSocketChannel和Socketchannel都为AbstractSelectableChannel类的子类,AbstractSelectableChannel类的register方法负责注册事件,该方法会返回一个SelectionKey对象,该对象用于跟踪被注册事件

  1. public abstract class SelectionKey {  
  2.     protected SelectionKey() { }  
  3.   
  4.     public abstract SelectableChannel channel();  
  5.   
  6.     public abstract Selector selector();  
  7.     ......  
  8. }  

一个Selector对象中包含了3种类型的键集(即SelectionKey集合,SelectionKey在以下部分被称为“键”)

1,all-keys:所有注册至该Selector的事件键集(selector.keys())

2,selected-keys:相关事件已经被Selector捕获的键集(selector.selectedKeys())

3,cancel-keys:已被取消的键集(无法访问该集合)

selected-keys和cancel-keys都为all-keys的子集,对于一个新建的Selector,这3个键集都为空

注册事件时会将相应的SelectionKey加入Selector的all-keys键集中

取消SelectionKey或者关闭了SelectionKey相关联的Channel,则会将相应的SelectionKey加入cancel-keys键集中

当执行选择器的选择操作时(selector.select(),对于选择器来说,这个方法应该是相当重要的):

1,将cancel-keys中的每个SelectionKey从3个键集中移除(如果存在的话),并注销SelectionKey所关联的Channel,cancel-keys键集变为空集。

2,如果监控的事件发生,Selector会将相关的SelectionKey加入selected-keys键集中

 

以下为对源代码的分析、推测:

Selector作为选择器,保存了所有的Selectionkey(注册的,取消的,触发的),通过上面的AbstractSelectableChannel类的源代码,发现Channel本身也保存了一个自身关联的SelectionKey数组,这看起来是完全没有必要的,但是仔细看一下register方法,能看出些许端倪:

Selector本身维护了3个集合,all-keys,selected-keys和cancel-keys,频繁的注册操作、取消注册将会导致这3个集合频繁的变化,伴随频繁变化的是频繁的加锁,这会严重的降低Selector的性能,毕竟一个Selector会被多个Channel作为选择器使用,本身非阻塞的实现方式就是提高性能的一种解决方式

当注册新的事件时,如果存在该通道相关的SelectionKey,则更新该SelectionKey所关注的事件以及其携带的附加信息,如果不存在,则添加新的SelectionKey

这样做的好处是,比起删除以前的SelectionKey,添加新的SelectionKey,修改SelectionKey所关注的事件以及其携带的附加信息显然是更好的选择,毕竟不需要修改Selector所维护的键集,当然也不需要频繁加锁(通过查看Selector类的api,SelectionKey并不是thread-safe的,显然并没有加锁,但是并没有什么问题),能够提供更好的性能

 

总之,SelectionKey的哈希码会重复是很正常的,毕竟不是单纯的注册时新建、触发后删除方式,Java实现时进行了优化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值