Netty与传统Server对比

时间:2022-04-24
本文章向大家介绍Netty与传统Server对比,主要内容包括前言、传统Socket / OIO、NIOServer、Netty Server、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

前言

本文旨在介绍传统Socket服务端与NIO服务端的差异.

以餐厅服务员简单举例,每个客人对应一个请求.

传统Socket / OIO

 1 public class OioServer {
 2 
 3     @SuppressWarnings("resource")
 4     public static void main(String[] args) throws Exception {
 5 
 6         ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
 7         //创建socket服务,监听10101端口
 8         ServerSocket server=new ServerSocket(10101);
 9         System.out.println("服务器启动!");
10         while(true){
11             //获取一个套接字(阻塞)
12             final Socket socket = server.accept();
13             System.out.println("来个一个新客户端!");
14             newCachedThreadPool.execute(new Runnable() {
15                 
16                 @Override
17                 public void run() {
18                     //业务处理
19                     handler(socket);
20                 }
21             });
22             
23         }
24     }
25     
26     /**
27      * 读取数据
28      * @param socket
29      * @throws Exception
30      */
31     public static void handler(Socket socket){
32             try {
33                 byte[] bytes = new byte[1024];
34                 InputStream inputStream = socket.getInputStream();
35                 
36                 while(true){
37                     //读取数据(阻塞)
38                     int read = inputStream.read(bytes);
39                     if(read != -1){
40                         System.out.println(new String(bytes, 0, read));
41                     }else{
42                         break;
43                     }
44                 }
45             } catch (Exception e) {
46                 e.printStackTrace();
47             }finally{
48                 try {
49                     System.out.println("socket关闭");
50                     socket.close();
51                 } catch (IOException e) {
52                     e.printStackTrace();
53                 }
54             }
55     }
56 }

缺点

单线程情况下只能有一个客户端

用线程池可以有多个客户端连接,但是非常消耗性能

类比图

NIOServer

  1 public class NIOServer {
  2     // 通道管理器
  3     private Selector selector;
  4 
  5     /**
  6      * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
  7      * 
  8      * @param port
  9      *            绑定的端口号
 10      * @throws IOException
 11      */
 12     public void initServer(int port) throws IOException {
 13         // 获得一个ServerSocket通道
 14         ServerSocketChannel serverChannel = ServerSocketChannel.open();
 15         // 设置通道为非阻塞
 16         serverChannel.configureBlocking(false);
 17         // 将该通道对应的ServerSocket绑定到port端口
 18         serverChannel.socket().bind(new InetSocketAddress(port));
 19         // 获得一个通道管理器
 20         this.selector = Selector.open();
 21         // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
 22         // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
 23         serverChannel.register(selector, SelectionKey.OP_ACCEPT);
 24     }
 25 
 26     /**
 27      * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
 28      * 
 29      * @throws IOException
 30      */
 31     public void listen() throws IOException {
 32         System.out.println("服务端启动成功!");
 33         // 轮询访问selector
 34         while (true) {
 35             // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
 36             selector.select();
 37             // 获得selector中选中的项的迭代器,选中的项为注册的事件
 38             Iterator<?> ite = this.selector.selectedKeys().iterator();
 39             while (ite.hasNext()) {
 40                 SelectionKey key = (SelectionKey) ite.next();
 41                 // 删除已选的key,以防重复处理
 42                 ite.remove();
 43 
 44                 handler(key);
 45             }
 46         }
 47     }
 48 
 49     /**
 50      * 处理请求
 51      * 
 52      * @param key
 53      * @throws IOException
 54      */
 55     public void handler(SelectionKey key) throws IOException {
 56         
 57         // 客户端请求连接事件
 58         if (key.isAcceptable()) {
 59             handlerAccept(key);
 60             // 获得了可读的事件
 61         } else if (key.isReadable()) {
 62             handelerRead(key);
 63         }
 64     }
 65 
 66     /**
 67      * 处理连接请求
 68      * 
 69      * @param key
 70      * @throws IOException
 71      */
 72     public void handlerAccept(SelectionKey key) throws IOException {
 73         ServerSocketChannel server = (ServerSocketChannel) key.channel();
 74         // 获得和客户端连接的通道
 75         SocketChannel channel = server.accept();
 76         // 设置成非阻塞
 77         channel.configureBlocking(false);
 78 
 79         // 在这里可以给客户端发送信息哦
 80         System.out.println("新的客户端连接");
 81         // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
 82         channel.register(this.selector, SelectionKey.OP_READ);
 83     }
 84 
 85     /**
 86      * 处理读的事件
 87      * 
 88      * @param key
 89      * @throws IOException
 90      */
 91     public void handelerRead(SelectionKey key) throws IOException {
 92         // 服务器可读取消息:得到事件发生的Socket通道
 93         SocketChannel channel = (SocketChannel) key.channel();
 94         // 创建读取的缓冲区
 95         ByteBuffer buffer = ByteBuffer.allocate(1024);
 96         int read = channel.read(buffer);
 97         if(read > 0){
 98             byte[] data = buffer.array();
 99             String msg = new String(data).trim();
100             System.out.println("服务端收到信息:" + msg);
101             
102             //回写数据
103             ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
104             channel.write(outBuffer);// 将消息回送给客户端
105         }else{
106             System.out.println("客户端关闭");
107             key.cancel();
108         }
109     }
110 
111     /**
112      * 启动服务端测试
113      * 
114      * @throws IOException
115      */
116     public static void main(String[] args) throws IOException {
117         NIOServer server = new NIOServer();
118         server.initServer(8000);
119         server.listen();
120     }
121 
122 }

优点

利用Selector多路复用技术, 一个线程可以处理多个客户端.

类比图

Netty Server