《netty权威指南》读书笔记
一、BIO
1、服务端程序:
1 package bio; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.io.PrintWriter; 7 import java.net.ServerSocket; 8 import java.net.Socket; 9 import java.util.Date;10 11 public class BioServer {12 13 public static void main(String[] args) throws IOException {14 ServerSocket serverSocket = new ServerSocket(8081);15 Socket clientSocket = null;16 while(true){17 clientSocket = serverSocket.accept();//如果没有客户端接入,主线程阻塞在这里18 new Thread(new ServerHandler(clientSocket)).start();19 }20 //finall关闭serverSocket21 }22 23 }24 25 class ServerHandler implements Runnable{26 private Socket clientSocket;27 28 public ServerHandler(Socket clientSocket) {29 this.clientSocket = clientSocket;30 }31 32 @Override33 public void run() {34 try {35 BufferedReader reader = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));36 PrintWriter writer = new PrintWriter(this.clientSocket.getOutputStream(), true);37 while(true){38 String body = reader.readLine();39 if (body==null){40 break;41 }42 System.out.println(body);43 writer.println(new Date().toString() + "->" + body);44 }45 } catch (IOException e) {46 e.printStackTrace();47 }48 //finally关闭资源:流和socket49 }50 }
- 服务端使用8081端口打开服务,不断接入客户端请求,每接入一个请求,都创建一个线程来处理这个请求。
- 处理逻辑:读取客户端传来的信息,之后想客户端写信息。
2、客户端程序:
1 package bio; 2 3 import java.io.BufferedReader; 4 import java.io.IOException; 5 import java.io.InputStreamReader; 6 import java.io.PrintWriter; 7 import java.net.Socket; 8 9 public class BioClient {10 public static void main(String[] args) throws IOException {11 Socket clientSocket = new Socket("127.0.0.1", 8081);12 BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));13 PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);14 writer.println("haha");15 String resp = reader.readLine();16 System.out.println(resp);17 //finall关闭serverSocket18 }19 }
- 客户端创建socket去连接服务端,之后想服务端写信息,并且读取服务端传来的信息。
服务端阻塞的几个点:
- serverSocket.accept();//如果没有客户端接入,主线程阻塞在这里
- 输入流InputStream的read操作也会阻塞:直到“有数据可读”或者“可用数据读取完毕”或者“发生异常”
- 输出流OutputStream的write操作也会阻塞:直到“所有要发送的字节全部写入”或者“发生异常”
二、NIO
1、服务端程序
1 package nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SelectionKey; 7 import java.nio.channels.Selector; 8 import java.nio.channels.ServerSocketChannel; 9 import java.nio.channels.SocketChannel;10 import java.util.Iterator;11 12 public class NioServer {13 public static void main(String[] args) throws IOException {14 Selector selector = Selector.open();15 16 ServerSocketChannel serverChannel = ServerSocketChannel.open();17 serverChannel.configureBlocking(false);18 serverChannel.socket().bind(new InetSocketAddress(8083));//监听链接8082端口的客户端socket19 serverChannel.register(selector, SelectionKey.OP_ACCEPT);//将serverChannel注册到selector,并监听接受连接事件20 21 while (selector.select() > 0) { //该方法会发生阻塞,直到至少有一个事件"准备就绪"为止22 Iteratorit = selector.selectedKeys().iterator();23 while (it.hasNext()) {24 SelectionKey sk = it.next();25 if (sk.isAcceptable()) {26 SocketChannel clientChannel = serverChannel.accept();//相当于客户端三次握手27 clientChannel.configureBlocking(false);28 clientChannel.register(selector, SelectionKey.OP_READ);29 } else if (sk.isReadable()) {30 SocketChannel clientChannel = (SocketChannel) sk.channel();31 ByteBuffer buf = ByteBuffer.allocate(1024);32 while (clientChannel.read(buf) > 0) { //将通道中的数据读到缓冲区,因为clientChannel已经是非阻塞的,所以这里的read是非阻塞的33 buf.flip();//将缓冲区由写模式切换到读模式34 byte[] bytes = new byte[buf.remaining()];35 buf.get(bytes);//将缓冲区中的数据读取到bytes中36 String body = new String(bytes, "UTF-8");37 System.out.println("接收到来自客户端的信息:" + body);38 buf.clear();39 }40 }41 it.remove();42 }43 }44 }45 }
- nio三组件:
- Buffer:用于存取数据,最主要的是ByteBuffer
- position:下一个将被操作的字节位置
- limit:在写模式下表示可以进行写的字节数,在读模式下表示可以进行读的字节数
- capacity:Buffer的大小
- Channel:用于传输数据,与Buffer相互配合
- Selector:多路复用器。轮询注册在其上的Channel,当发现某个或者多个Channel处于“就绪状态”后(有新的TCP链接接入、读、写事件),从阻塞状态返回就绪的Channel的SelectionKey集合,之后进行IO操作。
- Buffer:用于存取数据,最主要的是ByteBuffer
- selector.select():阻塞,直到有“就绪事件”发生或抛出异常
2、客户端程序
1 package nio; 2 3 import java.io.IOException; 4 import java.net.InetSocketAddress; 5 import java.nio.ByteBuffer; 6 import java.nio.channels.SocketChannel; 7 8 public class NioClient { 9 public static void main(String[] args) throws IOException {10 SocketChannel clientChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8083));//向服务端发出连接请求,服务端会通过accept()方法实现三次握手后,建立连接11 clientChannel.configureBlocking(false);12 ByteBuffer buf = ByteBuffer.allocate(1024);//分配在JVM堆中:capacity=1024;position=0;limit=102413 // ByteBuffer buf = ByteBuffer.allocateDirect(1024);//分配在堆外内存(物理内存)中14 buf.put("abcde".getBytes());//将数据写入缓冲区buf中:capacity=1024;position=5;limit=102415 buf.flip();//将缓冲区从写模式切换到读模式:capacity=1024;position=0;limit=516 clientChannel.write(buf);//将缓冲区的数据写入通道:capacity=1024;position=5;limit=517 buf.clear();//清空缓冲区:capacity=1024;position=0;limit=102418 clientChannel.close();19 }20 }
附:ByteBuffer使用JVM堆内存和使用堆外内存的区别:(图片来自尚硅谷的NIO教程)
- 使用堆内存:
- 应用程序将数据写入用户地址空间(JVM)中,之后将用户地址空间中的数据拷贝到内核地址空间(操作系统)
- 使用堆外内存:
- 直接在物理内存开辟空间,应用程序直接将数据写入物理内存,之后操作系统将其写入硬盘 - “零拷贝”
三、BIO与NIO的比较
1、线程数
- BIO:一个客户端连接就要使用一个服务端线程来进行处理
- 可能会有大量的想爱你成处于休眠状态,只是等待输入或输出(阻塞)
- 为每个线程分配调用栈,大约1M,服务端内存吃紧
- 即使服务端内存很大,线程数太大会导致线程上下文切换浪费大量时间
- NIO:一个服务端线程操作一个Selector,就可以处理成千上万的客户端连接
2、阻塞情况
- BIO:读、写、接受连接都会发生阻塞
- NIO:只有Selector.select()会阻塞,其实是等待Channel上的“就绪事件”
3、面向对象
- BIO:面向流
- NIO:面向Buffer
4、适合点
- BIO:如果你有少量的连接使用非常高的带宽,一次发送大量的数据,也许典型的IO服务器实现可能非常契合
- NIO:如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,实现NIO的服务器可能是一个优势。
四、Reactor模型
主从模型:
- 主线程池:
- 全部由NIO线程组成,使用线程池是因为担心性能问题
- 接收客户端连接请求,可能包含认证
- 接收到客户端的连接请求并处理完成(比如认证)后,将创建出来的SocketChannel(查看上边的NIOServer类)注册到次线程池的某一条NIO线程上,之后这条NIO线程进行IO操作。
- 次线程池:
- 全部由NIO线程组成
- 进行IO操作(编解码、业务逻辑等)