selector socketChannel

篇文章对NIO进行了简介,对Channel和Buffer接口的使用进行了说明,并举了一个简单的例子来说明其使用方法。

本篇则重点说明selector,Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

与selector联系紧密的是ServerSocketChannel和SocketChannel,他们的使用与上篇文章描述的FileChannel的使用方法类似,然后与ServerSocket和Socket也有一些联系。

本篇首先简单的进selector进行说明,然后一个简单的示例程序,来演示即时通讯。

Selector

使用传统IO进行网络编程,如下图所示:


每一个到服务端的连接,都需要一个单独的线程(或者线程池)来处理其对应的socket,当连接数多的时候,对服务端的压力极大。并使用socket的getInputStream。Read方法来不断的轮训每个socket,效率可想而知。

而selector则可以在同一个线程中监听多个channel的状态,当某个channel有selector感兴趣的事情发现,selector则被激活。即不会主动去轮询。如下图所示:

 

Selector使用如下示意:

[java]  view plain  copy
  1. public static void main(String[] args) throws IOException {  
  2.       Selector selector = Selector.open();//声明selector  
  3.        
  4.       ServerSocketChannel sc = ServerSocketChannel.open();  
  5.       sc.configureBlocking(false);//必须设置为异步  
  6.       sc.socket().bind(new InetSocketAddress(8081));//绑定端口  
  7.        
  8.       //把channel 注册到 selector上  
  9.       sc.register(selector, SelectionKey.OP_ACCEPT|SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);  
  10.        
  11.       while(true){  
  12.          selector.select();//阻塞,直到注册的channel上某个感兴趣的事情发生  
  13.          Set selectedKeys = selector.selectedKeys();  
  14.          Iterator keyIterator = selectedKeys.iterator();  
  15.          while(keyIterator.hasNext()) {  
  16.              SelectionKey key = keyIterator.next();  
  17.              if(key.isAcceptable()) {  
  18.                  // a connection was accepted by a ServerSocketChannel.  
  19.              } else if (key.isConnectable()) {  
  20.                  // a connection was established with a remote server.  
  21.              } else if (key.isReadable()) {  
  22.                  // a channel is ready for reading  
  23.              } else if (key.isWritable()) {  
  24.                  // a channel is ready for writing  
  25.              }  
  26.              keyIterator.remove();  
  27.          }  
  28.       }  
  29.        
  30.    }  

 

极简即时通讯

本例子是是一个极为简单的例子,很多地方都不完善,但是例子可以很好的说明selector的使用方法。

本例子包含服务端和客户端两个部分,其中服务端采用两个selector,用来建立连接和数据的读写。两个selector在两个线程中。

服务端

[java]  view plain  copy
  1. /** 
  2.  * 简单的即时通讯服务端,采用建立连接 selector和数据 selector分离。很不完善 
  3.  * 
  4.  */  
  5. public class ServerSocketChannelTest {  
  6.    
  7.    private static final int SERVER_PORT = 8081;  
  8.    
  9.    private ServerSocketChannel server;  
  10.    
  11.    private volatile Boolean isStop = false;  
  12.    
  13.    //负责建立连接的selector  
  14.    private Selector conn_Sel;  
  15.    //负责数据读写的selector  
  16.    private Selector read_Sel;  
  17.    
  18. // private ExecutorService sendService = Executors.newFixedThreadPool(3);  
  19.     
  20.    //锁,用来在建立连接后,唤醒read_Sel时使用的同步  
  21.    private Object lock = new Object();  
  22.    
  23.    //注册的用户  
  24.    private Map clents = new HashMap();  
  25.    
  26.    /** 
  27.     * 初始化,绑定端口 
  28.     */  
  29.    public void init() throws IOException {  
  30.    
  31.       //创建ServerSocketChannel  
  32.       server = ServerSocketChannel.open();  
  33.    
  34.       //绑定端口  
  35.       server.socket().bind(new InetSocketAddress(SERVER_PORT));  
  36.       server.configureBlocking(false);  
  37.       //定义两个selector  
  38.       conn_Sel = Selector.open();  
  39.       read_Sel = Selector.open();  
  40.       //把channel注册到selector上,第二个参数为兴趣的事件  
  41.       server.register(conn_Sel, SelectionKey.OP_ACCEPT);  
  42.    
  43.    }  
  44.    
  45.    // 负责建立连接。  
  46.    private void beginListen() {  
  47.       System.out.println("--------开始监听----------");  
  48.       while (!isStop) {  
  49.          try {  
  50.             conn_Sel.select();  
  51.          } catch (IOException e) {  
  52.             e.printStackTrace();  
  53.             continue;  
  54.          }  
  55.    
  56.          Iterator it = conn_Sel.selectedKeys().iterator();  
  57.    
  58.          while (it.hasNext()) {  
  59.             SelectionKey con = it.next();  
  60.             it.remove();  
  61.    
  62.             if (con.isAcceptable()) {  
  63.                 try {  
  64.                    SocketChannel newConn = ((ServerSocketChannel) con  
  65.                          .channel()).accept();  
  66.                    handdleNewInConn(newConn);  
  67.                 } catch (IOException e) {  
  68.                    e.printStackTrace();  
  69.                    continue;  
  70.                 }  
  71.             } else if (con.isReadable()) {//废代码,执行不到。  
  72.                 try {  
  73.                    handleData((SocketChannel) con.channel());  
  74.                 } catch (IOException e) {  
  75.                    e.printStackTrace();  
  76.                 }  
  77.             }  
  78.    
  79.          }  
  80.    
  81.       }  
  82.    }  
  83.     
  84.     
  85.     
  86.    /** 
  87.     * 负责接收数据 
  88.     */  
  89.    private void beginReceive(){  
  90.       System.out.println("---------begin receiver data-------");  
  91.       while (true) {  
  92.          synchronized (lock) {  
  93.          }  
  94.           
  95.          try {  
  96.             read_Sel.select();  
  97.          } catch (IOException e) {  
  98.             e.printStackTrace();  
  99.             continue;  
  100.          }  
  101.    
  102.          Iterator it = read_Sel.selectedKeys().iterator();  
  103.    
  104.          while (it.hasNext()) {  
  105.             SelectionKey con = it.next();  
  106.             it.remove();  
  107.             if (con.isReadable()) {  
  108.                 try {  
  109.                    handleData((SocketChannel) con.channel());  
  110.                 } catch (IOException e) {  
  111.                    e.printStackTrace();  
  112.                 }  
  113.             }  
  114.    
  115.          }  
  116.       }  
  117.    }  
  118.     
  119.    
  120.    private void handdleNewInConn(SocketChannel newConn) throws IOException {  
  121.       newConn.configureBlocking(false);  
  122.       //这里必须先唤醒read_Sel,然后加锁,防止读写线程的中select方法再次锁定。  
  123.       synchronized (lock) {  
  124.          read_Sel.wakeup();  
  125.          newConn.register(read_Sel, SelectionKey.OP_READ);  
  126.       }  
  127.       //newConn.register(conn_Sel, SelectionKey.OP_READ);  
  128.    }  
  129.    
  130.    private void handleData(final SocketChannel data) throws IOException {  
  131.    
  132.       ByteBuffer buffer = ByteBuffer.allocate(512);  
  133.    
  134.       try {  
  135.          int size= data.read(buffer);  
  136.          if (size==-1) {  
  137.             System.out.println("-------连接断开-----");  
  138.             //这里暂时不处理,这里可以移除已经注册的客户端  
  139.          }  
  140.    
  141.       } catch (IOException e) {  
  142.          e.printStackTrace();  
  143.          return;  
  144.       }  
  145.       buffer.flip();  
  146.    
  147.       byte[] msgByte = new byte[buffer.limit()];  
  148.       buffer.get(msgByte);  
  149.    
  150.       Message msg = Message.getMsg(new String(msgByte));  
  151.        
  152.       //这里读完数据其实已经可以另开线程了下一步的处理,理想情况下,根据不同的消息类型,建立不同的队列,把待发送的消息放进队列  
  153.       //当然也可以持久化。如果在数据没有读取前,另开线程的话,读写线程中 read_Sel.select(),会立刻返回。可以把  
  154.       if (msg.getType().equals("0")) {// 注册  
  155.          ClientInfo info = new ClientInfo(msg.getFrom(), data);  
  156.          clents.put(info.getClentID(), info);  
  157.          System.out.println(msg.getFrom() + "注册成功");  
  158.    
  159.       } else {// 转发  
  160.    
  161.          System.out.println("收到"+msg.getFrom()+"发给"+msg.getTo()+"的消息");  
  162.           
  163.          ClientInfo to = clents.get(msg.getTo());  
  164.          buffer.rewind();  
  165.          if (to != null) {  
  166.             SocketChannel sendChannel = to.getChannel();  
  167.    
  168.             try {  
  169.                 while (buffer.hasRemaining()) {  
  170.                    sendChannel.write(buffer);  
  171.    
  172.                 }  
  173.             } catch (Exception e) {  
  174.             }  
  175.    
  176.             finally {  
  177.                 buffer.clear();  
  178.             }  
  179.    
  180.          }  
  181.    
  182.       }  
  183.    
  184.    }  
  185.    
  186.     
  187.     
  188.     
  189.     
  190.     
  191.    public static void main(String[] args) throws IOException {  
  192.       final ServerSocketChannelTest a = new ServerSocketChannelTest();  
  193.       a.init();  
  194.       new Thread("receive..."){  
  195.          public void run() {  
  196.             a.beginReceive();  
  197.          };  
  198.       }.start();  
  199.       a.beginListen();  
  200.    
  201.    }  
  202.    
  203. }  


客户端


[java]  view plain  copy
  1. /** 
  2.  * new 次对象,然后调用start方法,其中self 是自己id 
  3.  * 
  4.  * to 是接收人id 
  5.  * 
  6.  */  
  7. public class Client {  
  8.     
  9.    /** 
  10.     * 自己的ID 
  11.     */  
  12.    private String self;  
  13.     
  14.    /** 
  15.     * 接收人ID 
  16.     */  
  17.    private String to;  
  18.     
  19.     //通道管理器   
  20.     private Selector selector;   
  21.      
  22.     private ByteBuffer writeBuffer = ByteBuffer.allocate(512);  
  23.    
  24.    private SocketChannel channel;  
  25.     
  26.    private Object lock = new Object();  
  27.    
  28.     
  29.    private volatile boolean isInit = false;  
  30.     
  31.     
  32.     public Client(String self, String to)  {  
  33.       super();  
  34.       this.self = self;  
  35.       this.to = to;  
  36.    }  
  37.    
  38.    /** 
  39.      * 获得一个Socket通道,并对该通道做一些初始化的工作 
  40.      * @param ip 连接的服务器的ip 
  41.      * @param port  连接的服务器的端口号          
  42.      * @throws IOException 
  43.      */   
  44.     public void initClient(String ip,int port) throws IOException {   
  45.         // 获得一个Socket通道   
  46.         channel = SocketChannel.open();   
  47.         // 设置通道为非阻塞   
  48.         channel.configureBlocking(false);   
  49.         // 获得一个通道管理器   
  50.         this.selector = Selector.open();   
  51.            
  52.         // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调   
  53.         //用channel.finishConnect();才能完成连接   
  54.         channel.connect(new InetSocketAddress(ip,port));   
  55.         //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。   
  56.         channel.register(selector, SelectionKey.OP_CONNECT);   
  57.     }   
  58.    
  59.     /** 
  60.      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理 
  61.      * @throws IOException 
  62.      */   
  63.     @SuppressWarnings("unchecked")   
  64.     public void listen() throws IOException {  
  65.         
  66.         // 轮询访问selector   
  67.         while (true) {   
  68.           synchronized (lock) {  
  69.          }  
  70.             selector.select();   
  71.             // 获得selector中选中的项的迭代器   
  72.             Iterator ite = this.selector.selectedKeys().iterator();   
  73.             while (ite.hasNext()) {   
  74.                 SelectionKey key =  ite.next();   
  75.                 // 删除已选的key,以防重复处理   
  76.                 ite.remove();   
  77.                 // 连接事件发生   
  78.                 if (key.isConnectable()) {   
  79.                     SocketChannel channel = (SocketChannel) key   
  80.                             .channel();   
  81.                     // 如果正在连接,则完成连接   
  82.                     if(channel.isConnectionPending()){   
  83.                         channel.finishConnect();   
  84.                            
  85.                     }   
  86.                     // 设置成非阻塞   
  87.                     channel.configureBlocking(false);   
  88.                       
  89.                  
  90.                     //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。   
  91.                     channel.register(this.selector, SelectionKey.OP_READ);   
  92.                     isInit = true;  
  93.                     // 获得了可读的事件   
  94.                      
  95.                 } else if (key.isReadable()) {   
  96.                         read(key);   
  97.                 }  
  98.    
  99.             }   
  100.    
  101.         }   
  102.     }   
  103.     /** 
  104.      * 处理读取服务端发来的信息的事件 
  105.      * @param key 
  106.      * @throws IOException  
  107.      */   
  108.     public void read(SelectionKey key) throws IOException{   
  109.    
  110.       SocketChannel data = (SocketChannel) key.channel();  
  111.       ByteBuffer buffer = ByteBuffer.allocate(512) ;  
  112.       try {  
  113.          data.read(buffer );  
  114.           
  115.       } catch (IOException e) {  
  116.          e.printStackTrace();  
  117.          data.close();  
  118.          return;  
  119.       }  
  120.       buffer.flip();  
  121.    
  122.       byte[] msgByte = new byte[buffer.limit()];  
  123.       buffer.get(msgByte);  
  124.    
  125.       Message msg = Message.getMsg(new String(msgByte));  
  126.       System.out.println("---收到消息--"+msg+" 来自 "+msg.getFrom());  
  127.         
  128.     }   
  129.        
  130.        
  131.     private void sendMsg(String content){  
  132.        writeBuffer.put(content.getBytes());  
  133.        writeBuffer.flip();  
  134.           try {  
  135.               while (writeBuffer.hasRemaining()) {  
  136.             channel.write(writeBuffer);  
  137.               }  
  138.          } catch (IOException e) {  
  139.             // TODO Auto-generated catch block  
  140.             e.printStackTrace();  
  141.          }         
  142.        writeBuffer.clear();  
  143.     }  
  144.      
  145.     /** 
  146.      * 启动客户端测试 
  147.      * @throws IOException  
  148.      */   
  149.     public  void start() throws IOException {   
  150.         initClient("localhost",8081);   
  151.         new Thread("reading"){  
  152.           public void run() {  
  153.                 try {  
  154.                 listen();  
  155.             } catch (IOException e) {  
  156.                 e.printStackTrace();  
  157.             }   
  158.           };  
  159.         }.start();  
  160.          
  161.         int time3  = 0;  
  162.          
  163.         while(!isInit&&time3<3){  
  164.           try {  
  165.              Thread.sleep(1000);  
  166.           } catch (InterruptedException e) {  
  167.              e.printStackTrace();  
  168.           }  
  169.           time3 ++;  
  170.         }  
  171.          
  172.         System.out.println("--------开始注册------");  
  173.         Message re = new Message("", self, "");  
  174.         sendMsg(re.toString());  
  175.         try {  
  176.          Thread.sleep(200);  
  177.       } catch (InterruptedException e) {  
  178.          e.printStackTrace();  
  179.       }  
  180.         System.out.println("-----注册成功----");  
  181.          
  182.         String content ="";  
  183.         System.out.println("---- 请输入要发送的消息,按回车发送,输入 123 退出----------");  
  184.         
  185.             Scanner s = new Scanner(System.in);  
  186.              
  187.             while (!content.equals("123")&&s.hasNext()) {  
  188.             content = s.next();  
  189.                Message msg = new Message(content, self, to);  
  190.                msg.setType("1");  
  191.                sendMsg(msg.toString());  
  192.                if (content.equals("123")) {  
  193.                 break;  
  194.             }  
  195.              System.out.println("---发送成功---");  
  196.    
  197.          }  
  198.              
  199.             channel.close();  
  200.       }  
  201.          
  202.      
  203. }  


客户端测试

[java]  view plain  copy
  1. public class TestClient1 {  
  2.    
  3.    public static void main(String[] args) throws IOException {  
  4.       Client c1 =new Client("1""2");  
  5.        
  6.       c1.start();  
  7.    }  
  8. }  
  9. public class TestClient2 {  
  10.    public static void main(String[] args) throws IOException {  
  11.       Client c2 =new Client("2""1");  
  12.        
  13.       c2.start();  
  14.    }  
  15. }  

结束

本文的例子极为简单,但是都经过测试。在编码的过程中,遇到的问题主要有两点:

1.     channel.register()方法阻塞

2.     使用线程池遇到问题。本文最后在服务端的读写线程中,没有使用线程池,原因注释说的比较明白,也说明了使用线程池的一种设想。

 

你可能感兴趣的:(java-网络编程,java-NIO)