非阻塞服务器

传统服务器是阻塞的,即ServerSocket的accept方法,在新到达一个客户端连接前,不会返回(当然,有异常发生时例外)。

为应付多个客户端同时连接,往往会为每个客户端创建一个线程,开销较大,所以一般会用线程池解决。

但总感觉这种方法不够优雅,也不够好。还好有NIO。

其实接触NIO已经有一段时间了,但是总是服务器NIO这块没弄明白,最近终于把它弄的差不多了。写了一个ECHO的例子,希望对以后面临同样问题的人一点提示。

(ECHO就是客户端发给服务器什么信息,服务器就把什么信息返回给客户端)

首先是服务器端代码:

Code:
  1. package nio.server;   
  2.   
  3. import java.io.IOException;   
  4. import java.net.InetSocketAddress;   
  5. import java.net.ServerSocket;   
  6. import java.nio.ByteBuffer;   
  7. import java.nio.channels.SelectionKey;   
  8. import java.nio.channels.Selector;   
  9. import java.nio.channels.ServerSocketChannel;   
  10. import java.nio.channels.SocketChannel;   
  11. import java.util.Iterator;   
  12. import java.util.Set;   
  13.   
  14. public class MainServer {   
  15.        
  16.     public static final int DEFAULT_PORT=22333;   
  17.        
  18.     private int port;   
  19.     private boolean stopped=false;   
  20.     private byte[] signature="ECHO:".getBytes();   
  21.     private ServerSocketChannel serverChannel;   
  22.     private Selector selector;   
  23.        
  24.     public MainServer(int port){   
  25.         this.port=port;   
  26.     }   
  27.        
  28.     public MainServer(){   
  29.         this.port=DEFAULT_PORT;   
  30.     }   
  31.        
  32.     public void startUp(){   
  33.   
  34.         ByteBuffer buffer=ByteBuffer.allocate(1024);   
  35.         try {   
  36.             //打开一个ServerSocketChannel   
  37.             serverChannel=ServerSocketChannel.open();   
  38.             //获取ServerSocketChannel的对等ServerSocket,将其绑定到指定端口   
  39.             ServerSocket serverSocket=serverChannel.socket();   
  40.             serverSocket.bind(new InetSocketAddress(port));   
  41.             //将ServerSocketChannel设置为非阻塞状态   
  42.             serverChannel.configureBlocking(false);   
  43.             //打开一个选择器   
  44.             selector=Selector.open();   
  45.             //将ServerSocketChannel注册到选择器   
  46.             serverChannel.register(selector, SelectionKey.OP_ACCEPT);   
  47.             while(false == stopped){   
  48.                 //阻塞   
  49.                 selector.select();   
  50.                 //获取所有的被激活的选择键,并遍历   
  51.                 Set<SelectionKey> keys=selector.selectedKeys();   
  52.                 Iterator<SelectionKey> iterator=keys.iterator();   
  53.                 while(iterator.hasNext()){   
  54.                     SelectionKey key=iterator.next();   
  55.                     //移除正在遍历的选择键,防止以后再次调用它   
  56.                     iterator.remove();   
  57.                     //如果被激活的选择键的channel是ServerSocketChannel,则说明有新的Socket连接   
  58.                     if(key.isAcceptable()){   
  59.                         ServerSocketChannel serverSocketChannel=(ServerSocketChannel)key.channel();   
  60.                         //获取新的Socket对应的SocketChannel   
  61.                         SocketChannel socketChannel=serverSocketChannel.accept();   
  62.                         //将SocketChannel设置为非阻塞   
  63.                         socketChannel.configureBlocking(false);   
  64.                         //将SocketChannel注册到选择器上,为读状态   
  65.                         socketChannel.register(selector, SelectionKey.OP_READ);   
  66.                     //如果被激活的键的channel是SocketChannel,且其为读状态   
  67.                     }else if(key.isReadable()){   
  68.                         SocketChannel socketChannel=(SocketChannel)key.channel();   
  69.                         buffer.clear();   
  70.                         //读取信息   
  71.                         int length=socketChannel.read(buffer);   
  72.                         byte[] attachment=new byte[length];   
  73.                         buffer.flip();   
  74.                         buffer.get(attachment,0,length);   
  75.                         //将读取的信息作为附件添加给这个键   
  76.                         key.attach(attachment);   
  77.                         //将这个键的状态转换为写状态   
  78.                         key.interestOps(SelectionKey.OP_WRITE);   
  79.                     //如果被激活的键的channel是SocketChannel,且其为写状态   
  80.                     }else if(key.isWritable()){   
  81.                         SocketChannel socketChannel=(SocketChannel)key.channel();   
  82.                         //获取这个键所对应的附件   
  83.                         byte[] attachment=(byte[])key.attachment();   
  84.                         buffer.clear();   
  85.                         //添加标记   
  86.                         buffer.put(signature,0,signature.length);   
  87.                         buffer.put(attachment,0,attachment.length);   
  88.                         //注意,仅在window上测试可行,其他操作系统不一定以/r/n作为行结束符   
  89.                         buffer.put((byte)'/r');   
  90.                         buffer.put((byte)'/n');   
  91.                         buffer.flip();   
  92.                         socketChannel.write(buffer);   
  93.                         //将这个键的状态转换为读状态   
  94.                         key.interestOps(SelectionKey.OP_READ);   
  95.                     }   
  96.                 }   
  97.             }   
  98.         } catch (IOException e) {   
  99.             e.printStackTrace();   
  100.         }   
  101.     }   
  102.        
  103.     public void shutDown(){   
  104.         this.stopped=true;   
  105.         try {   
  106.             serverChannel.close();   
  107.             selector.close();   
  108.         } catch (IOException e) {   
  109.             e.printStackTrace();   
  110.         }   
  111.         selector=null;   
  112.     }   
  113.        
  114.     public static void main(String[] args){   
  115.         MainServer server=new MainServer();   
  116.         server.startUp();   
  117.     }   
  118. }   

客户端代码,没太多注释,感觉没必要

Code:
  1. package nio.server;   
  2.   
  3. import java.io.BufferedReader;   
  4. import java.io.IOException;   
  5. import java.io.InputStreamReader;   
  6. import java.io.OutputStream;   
  7. import java.net.InetSocketAddress;   
  8. import java.net.Socket;   
  9. import java.net.SocketAddress;   
  10.   
  11. public class MainClient {   
  12.        
  13.     public final static String DEFAULT_IP="127.0.0.1";   
  14.     public final static int DEFAULT_PORT=22333;   
  15.        
  16.     private String ip;   
  17.     private int port;   
  18.        
  19.     private boolean stopped=false;   
  20.        
  21.     private Socket socket;   
  22.        
  23.     private BufferedReader input;   
  24.        
  25.     public MainClient(String ip,int port){   
  26.         this.ip=ip;   
  27.         this.port=port;   
  28.     }   
  29.        
  30.     public MainClient(){   
  31.         this.ip=DEFAULT_IP;   
  32.         this.port=DEFAULT_PORT;   
  33.     }   
  34.        
  35.     public void startUp(){   
  36.         SocketAddress address=new InetSocketAddress(ip,port);   
  37.         socket=new Socket();   
  38.         BufferedReader input=new BufferedReader(new InputStreamReader(System.in));   
  39.         OutputStream output=null;   
  40.         BufferedReader reader=null;   
  41.         try {   
  42.             socket.connect(address);   
  43.             output=socket.getOutputStream();   
  44.             reader=new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf8"));   
  45.             while(false == stopped){   
  46.                 String inputString=input.readLine();   
  47.                 output.write(inputString.getBytes("utf8"));   
  48.                 output.flush();   
  49.                 String echoMessage=reader.readLine();   
  50.                 System.out.println(echoMessage);   
  51.             }   
  52.         } catch (IOException e) {   
  53.             e.printStackTrace();   
  54.         }   
  55.     }   
  56.        
  57.     public void shutDown(){   
  58.         this.stopped=true;   
  59.         try {   
  60.             socket.close();   
  61.             input.close();   
  62.         } catch (IOException e) {   
  63.             e.printStackTrace();   
  64.         }   
  65.         socket=null;   
  66.         input=null;   
  67.     }   
  68.        
  69.        
  70.     public static void main(String[] args) {   
  71.         MainClient client=new MainClient();   
  72.         client.startUp();   
  73.     }   
  74.   
  75. }   

OK,说简单也不简单,说难也不难。主要弄懂Selector Channel Buffer三个大概念就好,希望对需要的人能有所帮助!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值