https://www.cnblogs.com/plxz/p/9910493.html
第一章 Java的I/O演进之路
1.I/O基础入门
1.Linux网络I/O模型简介
1.阻塞I/O模型:最常用的I/O模型,缺省情况下, 所有文件操作都是阻塞的
2.非阻塞I/O模型:recvform从应用层到内核的时候,轮询检查是否有数据到来
3.I/O复用模型
4.信号渠道I/O模型
5.异步I/O:告知内核启动某个操作,并让内核在整个操作完成后通知我们
2.I/O多路复用技术
1.应用场景:服务器需要同时处理多个处于监听状态或者多个连接状态的套接字;服务器需要同时处理多种网络协议的套接字
2.epoll的改进
1.支持一个进行打开的socket描述符不受限制
2.I/O效率不会随着FD数目的增加而线性下降
3.使用mmap加速内核与用户空间的消息传递
4.epoll API更加简单
2.Java的I/O演进
1.Java的I/O发展简史
第二章 NIO入门
1.传统的BIO编程
1.BIO通信模型
2.同步阻塞式I/O创建的TimeServer源码分析
1.TimeServer
public class TimeServer { public static void main(String[] args) throws IOException { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(port); System.out.println("服务器启动,端口=" + port); Socket socket = null; while (true) { socket = serverSocket.accept();//阻塞等待客户端连接 new Thread(new TimeServerHandler(socket)).start(); } } finally { if (serverSocket != null) { System.out.println("服务器关闭"); serverSocket.close(); serverSocket = null; } } } }
2.TimeServerHandler
public class TimeServerHandler implements Runnable { private Socket socket; public TimeServerHandler(Socket socket) { this.socket = socket; } @Override public void run() { BufferedReader in = null; PrintWriter out = null; try { in = new BufferedReader(new InputStreamReader(this.socket.getInputStream())); out = new PrintWriter(this.socket.getOutputStream(), true); String currentTime = null; String body = null; while (true) { body = in.readLine(); if (body == null) { break; } System.out.println("收到客户端请求:" + body); if ("QUERY TIME ORDER".equalsIgnoreCase(body)) { currentTime = String.valueOf(System.currentTimeMillis()); } else { currentTime = "BAD ORDER"; } out.println(currentTime);//发送消息给客户端 } } catch (Exception e) { if (in != null) { try { in.close(); } catch (IOException e1) { e1.printStackTrace(); } } if (out != null) { out.close(); out = null; } if (this.socket != null) { try { this.socket.close(); } catch (IOException e1) { e1.printStackTrace(); } this.socket = null; } } } }
3.NpeCheck
public class NpeCheck { public static boolean checkArray(String[] args) { if (args != null && args.length > 0) { return true; } else { return false; } } }
3.同步阻塞式I/O创建的TimerClient源码分析
public class TimeClient { public static void main(String[] args) { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } Socket socket = null; BufferedReader in = null; PrintWriter out = null; try { socket = new Socket("127.0.0.1", port); in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = new PrintWriter(socket.getOutputStream(), true); out.println("QUERY TIME ORDER");//发送请求到服务器 System.out.println("发送请求到服务器"); String resp = in.readLine();//读取服务器的响应 System.out.println("当前时间=" + resp); } catch (Exception e) { //不需要处理 } finally { if (out != null) { out.close(); out = null; } if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } in = null; } if (socket != null) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } socket = null; } } } }
2.伪异步I/O编程
1.伪异步I/O模型图
当新的客户端接入时,将客户端的socket封装成一个Task投递到后端的线程池中进行处理,jdk的线程池维护一个消息队列和N个活跃线程,对消息队列中的任务进行处理。
2.伪异步I/O创建的TimerServer源码分析
1.TimerServer
public class TimeServer { public static void main(String[] args) throws IOException { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } ServerSocket server = null; try { server = new ServerSocket(port); System.out.println("服务器启动,端口=" + port); Socket socket = null; TimeServerHandlerExecutePool singleExecutor=new TimeServerHandlerExecutePool(50,100);//创建I/O任务线程池 while (true) { socket = server.accept();//阻塞等待客户端连接 singleExecutor.execute(new TimeServerHandler(socket));//线程池自动调用线程执行 } } finally { if (server != null) { System.out.println("服务器关闭"); server.close(); server = null; } } } }
2.TimeServerHandlerExecutePool线程池
public class TimeServerHandlerExecutePool { private ExecutorService executor; //创建线程池 public TimeServerHandlerExecutePool(int maxPoolSize, int queueSiez) { executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), maxPoolSize, 120L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(queueSiez)); } //执行任务 public void execute(Runnable task) { executor.execute(task); } }
3.伪异步I/O弊端分析:通信对方返回应答时间过长会引起的级联故障
1.服务端处理换么,返回应答消息耗时60s,平时只需10ms
2.采用伪异步I/O的线程正在读取故障服务节点的响应,由于读取输入流是阻塞的,它将会被同步阻塞60s
3.加入所有的可用线程都被故障服务器阻塞,那后续所有的I/O消息都将在队列中排队
4.由于线程池采用阻塞队列实现,当队列积满后,后续入队列的操作将被阻塞
5.由于前端只有一个Accptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息将被拒绝,客户端会发生大量的连接超时
6.由于几乎所有的连接都会超时,调用者任务系统已经崩溃,无法接收新的请求消息
3.NIO编程
1.NIO类库简介
1.缓冲区Buffer,保护一些要写入或者读出的数据,实质是一个字节数组,提供了对数据的结构化访问以及维护读写位置等信息
2.通道Channel:双向的,可以用于读、写或者二者同时进行,网络读写SelectableChannel和文件操作FileChannel
3.多路复用器Selector:提供已经就绪的任务
2.NIO服务端序列图
3.NIO创建的TimerServer源码分析
1.TimeServer
public class TimeServer { public static void main(String[] args) throws IOException { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } //创建多路复用类,负责轮询多路复用器Selector,可以处理多个客户端的并发接入 MultiplexerTimeServer timeServer=new MultiplexerTimeServer(port); new Thread(timeServer,"NIO-MultiplexerTimeServer-001").start(); } }
2.MultiplexerTimeServer多路复用器
import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class MultiplexerTimeServer implements Runnable { private Selector selector; private ServerSocketChannel serverChannel; private volatile boolean stop; public MultiplexerTimeServer(int port) { try { selector = Selector.open();//3.1创建Reactor线程 serverChannel = ServerSocketChannel.open();//1.打开ServerSocketChannel,用于监听客户端的连接,是所有客户端连接的父管道 serverChannel.configureBlocking(false);//2.2设置连接为非阻塞模式 serverChannel.socket().bind(new InetSocketAddress(port), 1024);//2.1绑定监听端口,设置连接为非阻塞模式 serverChannel.register(selector, SelectionKey.OP_ACCEPT);//4.将打开ServerSocketChannel注册到Reactor线程的多路复用器selector上,监听ACCEPT事件 System.out.println("服务器启动,端口=" + port); } catch (IOException e) { e.printStackTrace(); System.exit(1);//若端口占用,退出程序 } } public void stop() { this.stop = true; } @Override public void run() { while (!stop) { try { //5.多路复用器在线程run方法的无限循环体内轮询准备就绪的key selector.select(1000);//1秒轮询1次 Set<SelectionKey> selectionKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeys.iterator(); SelectionKey key; while (it.hasNext()) { key = it.next(); it.remove(); try { handleInput(key); } catch (Exception e) { if (key != null) { key.cancel(); if (key.channel() != null) { key.channel().close(); } } } } } catch (Throwable t) { t.printStackTrace(); } } //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会自动去注册并关闭,所以不需要重复释放资源 if (selector != null) { try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } //处理新接入的请求消息 private void handleInput(SelectionKey key) throws IOException { if (key.isValid()) { //处理新接入的请求消息 if (key.isAcceptable()) { //接收新的连接 ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); //6.多路复用器监听到有新的客户端接入,处理新的接入请求,完成TCP三次握手,建立物理链路 SocketChannel sc = ssc.accept(); //7.设置客户端链路为非阻塞模式 sc.configureBlocking(false); //8.将新接入的客户端连接注册到Reactor线程的多路复用器上,监听读操作,读取客户端发送的网络消息 sc.register(selector, SelectionKey.OP_READ); } if (key.isReadable()) { //读取数据 SocketChannel sc = (SocketChannel) key.channel(); ByteBuffer readBuffer = ByteBuffer.allocate(1024); //9.异步读取客户端请求消息到缓冲区 int readBytes = sc.read(readBuffer); if (readBytes > 0) {//读取到字节,对字节进行编解码 readBuffer.flip();//刷新指针 //10.对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将界面成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排 byte[] bytes = new byte[readBuffer.remaining()];//创建可读的字节数组 readBuffer.get(bytes);//复制到新创建的数组中 String body = new String(bytes, "UTF-8");//创建请求消息体 System.out.println("接收请求=" + body); String currentTime; if ("QUERY TIME ORDER".equalsIgnoreCase(body)) { currentTime = String.valueOf(System.currentTimeMillis()); } else { currentTime = "BAD ORDER"; } doWrite(sc, currentTime); } else if (readBytes < 0) {//链路已经关闭,需要关闭SocketChannel,释放资源 key.cancel(); sc.close(); } else { //没有读取到字节,属于正常场景,忽略 } } } } //将应答消息异步发送给客户端 private void doWrite(SocketChannel channel, String response) throws IOException { if (StringUtils.isNotBlank(response)) { byte[] bytes = response.getBytes();//将字符串编码成字节数组 ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);//根据字节数组的容量创建ByteBuffer writeBuffer.put(bytes);//将字节数组复制到缓冲区 writeBuffer.flip();//刷新缓冲区 //11.将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发给客户端 //如果发送区TCP缓冲区满,会导致写半包,此时,需要注册监听写操作位,循环写,知道整包消息写入TCP缓冲区 channel.write(writeBuffer); } } }
4.NIO客户端序列图
5.NIO创建的TimerClient源码分析
1.TimeClient
public class TimeClient { public static void main(String[] args) { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } new Thread(new TimeClientHandle("127.0.0.1",port),"TimeClient-001").start(); } }
2.TimeClientHandle
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class TimeClientHandle implements Runnable { private String host; private int port; private Selector selector; private SocketChannel socketChannel; private volatile boolean stop; public TimeClientHandle(String host, int port) { this.host = host == null ? "127.0.0.1" : host; this.port = port; try { selector = Selector.open();//6.1创建Reactor线程 socketChannel = SocketChannel.open();//1.打开SocketChannel,绑定客户端本地地址 socketChannel.configureBlocking(false);//2.设置SocketChannel为非阻塞模式,同时设置客户端连接的TCP参数 } catch (IOException e) { e.printStackTrace(); System.exit(1); } } @Override public void run() { try { doConnect(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } while (!stop) { try { //7.多路复用器在线程的run方法的无限循环体内轮询准备就绪的key selector.select(1000); Set<SelectionKey> selectionKeySet = selector.selectedKeys(); Iterator<SelectionKey> it = selectionKeySet.iterator(); SelectionKey key; while (it.hasNext()) { key = it.next(); it.remove(); try { handleInput(key); } catch (Exception e) { if (key != null) { key.cancel(); if (key.channel() != null) { key.channel().close(); } } } } } catch (Exception e) { e.printStackTrace(); System.exit(1); } } //多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会自动去注册并关闭,所以不需要重复释放资源 if (selector != null) { try { selector.close(); } catch (IOException e) { e.printStackTrace(); } } } //处理新接入的请求消息 private void handleInput(SelectionKey key) throws IOException { if (key.isValid()) { //判断是否连接成功 SocketChannel sc = (SocketChannel) key.channel(); if (key.isConnectable()) {//8.接收连接事件进行处理 if (sc.finishConnect()) {//9.判断连接结果,如果连接成功,注册读事件到多路复用器 sc.register(selector, SelectionKey.OP_READ);//10.注册读事件到多路复用器 doWrite(sc); } else { System.exit(1);//连接失败,进程退出 } } if (key.isReadable()) { //11.异步读服务器的应答消息到缓冲区 ByteBuffer readBuffer = ByteBuffer.allocate(1024); int readByte = sc.read(readBuffer); if (readByte > 0) { //12.对ByteBuffer进行编解码,如果有半包消息指针reset,继续读取后续的报文,将界面成功的消息封装成Task,投递到业务线程池中,进行业务逻辑编排 readBuffer.flip(); byte[] bytes = new byte[readBuffer.remaining()]; readBuffer.get(bytes); String body = new String(bytes, "UTF-8"); System.out.println("读取时间=" + body); this.stop = true; } else if (readByte < 0) { //对端链路关闭 key.cancel(); sc.close(); } else { //没有读取到字节,属于正常场景,忽略 } } } } private void doConnect() throws IOException { //如果连接成功,则注册到多路复用器上,发送请求消息,读应答 if (socketChannel.connect(new InetSocketAddress(host, port))) {//3.异步连接服务器 socketChannel.register(selector, SelectionKey.OP_READ);//4.注册读状态到多路复用器 doWrite(socketChannel); } else { socketChannel.register(selector, SelectionKey.OP_CONNECT);//5.向Reactor线程的多路复用器注册连接状态位,监听TCPACK应答 } } //将应答消息异步发送给客户端 private void doWrite(SocketChannel sc) throws IOException { byte[] req = "QUERY TIME ORDER".getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(req.length); writeBuffer.put(req); writeBuffer.flip(); sc.write(writeBuffer);//13.将POJO对象encode成ByteBuffer,调用SocketChannel的异步write接口,将消息异步发给客户端 if (!writeBuffer.hasRemaining()) {//判断消息是否全部发送完成 System.out.println("请求服务器成功"); } } }
4.AIO编程
1.AIO创建的TimerServer源码分析
1.TimeServer
import com.example.demo.util.NpeCheck; import java.io.IOException; public class TimeServer { public static void main(String[] args) throws IOException { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } //创建多路复用类,负责轮询多路复用器Selector,可以处理多个客户端的并发接入 AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port); new Thread(timeServer, "AIO-AsyncTimeServerHandler-001").start();//3.2创建多路复用器并启动线程 } }
2.AsyncTimeServerHandler
import java.io.IOException; import java.net.InetSocketAddress; import java.nio.channels.AsynchronousServerSocketChannel; import java.util.concurrent.CountDownLatch; public class AsyncTimeServerHandler implements Runnable { private int port; CountDownLatch latch; AsynchronousServerSocketChannel asynchronousServerSocketChannel; public AsyncTimeServerHandler(int port) { this.port = port; try { asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open(); asynchronousServerSocketChannel.bind(new InetSocketAddress(port)); System.out.println("服务器启动,端口号=" + port); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { latch = new CountDownLatch(1);//完成一组正在执行的操作之前,允许当前的线程一直阻塞 doAccept(); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } } private void doAccept() { asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());//接收客户端连接 } }
3.AcceptCompletionHandler
import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; //作为handler来接收通知消息 public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel,AsyncTimeServerHandler> { @Override public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) { attachment.asynchronousServerSocketChannel.accept(attachment, this);//接口客户端连接 ByteBuffer buffer = ByteBuffer.allocate(1024); result.read(buffer, buffer, new ReadCompletionHandler(result)); } @Override public void failed(Throwable exc, AsyncTimeServerHandler attachment) { exc.printStackTrace(); attachment.latch.countDown(); } }
2.AIO创建的TimerClient源码分析
1.TimeClient
import com.example.demo.util.NpeCheck; public class TimeClient { public static void main(String[] args) { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } new Thread(new AsyncTimeClientHandler("127.0.0.1", port), "AIO-AsyncTimeClient-001").start();//6.2创建多路复用器并启动线程 } }
2.AsyncTimeClientHandler
import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; import java.util.concurrent.CountDownLatch; public class AsyncTimeClientHandler implements CompletionHandler<Void, AsyncTimeClientHandler>, Runnable { private AsynchronousSocketChannel client; private String host; private int port; private CountDownLatch latch; public AsyncTimeClientHandler(String host, int port) { this.host = host; this.port = port; try { client = AsynchronousSocketChannel.open(); } catch (IOException e) { e.printStackTrace(); } } @Override public void run() { latch = new CountDownLatch(1);//防止异步操作没有执行完成线程就退出 //发起异步操作,attachment用于回调通知时作为入参被传递,handler异步回调通知接口 client.connect(new InetSocketAddress(host, port), this, this); try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } try { client.close(); } catch (IOException e) { e.printStackTrace(); } } @Override public void completed(Void result, AsyncTimeClientHandler attachment) { byte[] req = "QUERY TIME ORDER".getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(req.length); writeBuffer.put(req); writeBuffer.flip(); client.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { if (buffer.hasRemaining()) {//未发送完成继续异步发送 client.write(buffer, buffer, this); } else {//发送完成,异步读取 ByteBuffer readBuffer = ByteBuffer.allocate(1024); client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { buffer.flip(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); String body; try { body = new String(bytes, "UTF-8"); System.out.println("读取时间=" + body); latch.countDown(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { client.close();//关闭链路 latch.countDown();//让线程执行完毕 } catch (IOException e) { //忽略 } } }); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { client.close(); latch.countDown(); } catch (IOException e) { //忽略 } } }); } @Override public void failed(Throwable exc, AsyncTimeClientHandler attachment) { try { client.close(); latch.countDown(); } catch (IOException e) { //忽略 } } }
3.ReadCompletionHandler
import org.apache.commons.lang3.StringUtils; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.channels.AsynchronousSocketChannel; import java.nio.channels.CompletionHandler; public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> { private AsynchronousSocketChannel channel;//用于读取半包消息和发送应答 public ReadCompletionHandler(AsynchronousSocketChannel channel) { if (this.channel == null) { this.channel = channel; } } @Override public void completed(Integer result, ByteBuffer attachment) { attachment.flip(); byte[] body = new byte[attachment.remaining()]; attachment.get(body); try { String req = new String(body, "UTF-8"); System.out.println("收到请求:" + req); String currentTime; if ("QUERY TIME ORDER".equalsIgnoreCase(req)) { currentTime = String.valueOf(System.currentTimeMillis()); } else { currentTime = "BAD ORDER"; } doWrite(currentTime); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } private void doWrite(String currentTime) { if (StringUtils.isNotBlank(currentTime)) { byte[] bytes = (currentTime).getBytes(); ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length); writeBuffer.put(bytes); writeBuffer.flip(); channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer buffer) { //如果没有发送完成,继续发送 if (buffer.hasRemaining()) { channel.write(buffer, buffer, this); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { channel.close(); } catch (IOException e) { //忽略 } } }); } } @Override public void failed(Throwable exc, ByteBuffer attachment) { try { this.channel.close(); } catch (IOException e) { //忽略 } } }
3.AIO版本时间服务器运行结果
异步SocketChannel是被动执行对象,我们不需要像NIO编程那样创建一个独立的I/O线程来处理读写操作。对应AsynchronousServerSocketChannel和AsynchronousSocketChannel都是JDK底层的线程池负责回调并驱动读写操作,AIO编程比NIO编程更为简单。
5.4种I/O对比
1.概念澄清
1.异步非阻塞I/O,NIO不是真正意义的异步非阻塞I/O,是非阻塞I/O
2.多路复用器Selector
3.伪异步I/O,通过线程池做缓冲区实现
2.不同I/O模型对比
6.选择Netty的理由
1.不选择Java元素NIO编程的原因
2.为什么选择Netty
第3章 Netty入门应用
3.1Netty开发环境的搭建
3.1.1下载Netty的软件包
3.1.2搭建Netty应用工程
3.2Netty服务端开发
3.2.1步骤
3.3Netty客户端开发
3.3.1TimeClient
import com.example.demo.util.NpeCheck; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; public class TimeClient { public void connect(int port, String host) throws Exception { //配置客户端NIO线程组 EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY,true) .handler(new ChannelInitializer<SocketChannel>() {//处理网络I/O事件 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new TimeClientHandler()); } }); //发起异步连接操作 ChannelFuture f=b.connect(host,port).sync(); //等待客户端链路关闭 f.channel().closeFuture().sync(); }finally { //优雅退出,释放NIO线程组 group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } new TimeClient().connect(port,"127.0.0.1"); } }
3.3.2TimeClientHandler
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; public class TimeClientHandler extends ChannelHandlerAdapter { private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName()); private final ByteBuf firstMessage; public TimeClientHandler() { byte[] req = "QUERY TIME ORDER".getBytes(); firstMessage = Unpooled.buffer(req.length); firstMessage.writeBytes(req); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(firstMessage);//连接成功后,发送请求 } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf buf = (ByteBuf) msg;//读取应答消息 byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); String body = new String(req, "UTF-8"); System.out.println("当前时间=" + body); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //释放资源 logger.warning("异常=" + cause.getMessage()); ctx.close(); } }
3.4运行和调试
3.4.1服务器和客户端的运行
3.4.2打包和部署
第4章 TCP粘包/拆包问题的解决之道
4.1TCP粘包/拆包
一个完整的包可能会被TCP拆分多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送
4.1.1TCO粘包/拆包问题说明
1.示意图
2.分析
4.1.2TCP粘包/拆包发生的原因
4.1.3粘包问题的解决策略
4.2未考虑TCP粘包导致功能异常案例
4.2.1TimeServer的改造
4.2.2TimeClient的改造
4.2.3运行结果
4.3理由LineBasedFrameDecoder解决TCP粘包问题
4.3.1支持TCP粘包的TimeServer
1.TimeServer
import com.example.demo.util.NpeCheck; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; public class TimeServer { public void bind(int port) throws Exception { //配置服务端的NIO线程组 EventLoopGroup bossGroup = new NioEventLoopGroup();//用于接收客户端的线程组 EventLoopGroup workerGroup = new NioEventLoopGroup();//用于用于网络读写的线程组 try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)//设置通道 .option(ChannelOption.SO_BACKLOG, 1024)//设置TCP参数 .childHandler(new ChildChannelHandler());//用于处理网络I/O事件(记录日志、对消息编解码) ChannelFuture f = b.bind(port).sync();//绑定端口,同步等待成功 f.channel().closeFuture().sync();//等待服务端监听端口关闭,才退出main函数 } finally { //优雅退出,释放线程池资源 bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } private class ChildChannelHandler extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new TimeServerHandler()); } } public static void main(String[] args) throws Exception { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]); } catch (NumberFormatException e) { //采用默认值 } } new TimeServer().bind(port); } }
2.TimeServerHandler
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.Date; public class TimeServerHandler extends ChannelHandlerAdapter { private int counter; @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String) msg;//接收到的是删除回车换行符后的请求消息 System.out.println("接收到请求命令:" + body + ";次数="+ ++counter); String currentTime; if ("QUERY TIME ORDER".equals(body)) { currentTime = new Date(System.currentTimeMillis()).toString(); } else { currentTime = "参数错误"; } currentTime += System.getProperty("line.separator"); ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes()); ctx.writeAndFlush(resp); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } }
4.3.2支持TCP粘包的TimeClient
1.TimeClient
import com.example.demo.util.NpeCheck; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; public class TimeClient { public void connect(int port, String host) throws Exception { //配置客户端NIO线程组 EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class) .option(ChannelOption.TCP_NODELAY,true) .handler(new ChannelInitializer<SocketChannel>() {//处理网络I/O事件 @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new TimeClientHandler()); } }); //发起异步连接操作 ChannelFuture f=b.connect(host,port).sync(); //等待客户端链路关闭 f.channel().closeFuture().sync(); }finally { //优雅退出,释放NIO线程组 group.shutdownGracefully(); } } public static void main(String[] args) throws Exception { int port = 8080; if (NpeCheck.checkArray(args)) { try { port = Integer.valueOf(args[0]);//设置监听端口 } catch (NumberFormatException e) { //采用默认值 } } new TimeClient().connect(port,"127.0.0.1"); } }
2.TimeClientHandler
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import java.util.logging.Logger; public class TimeClientHandler extends ChannelHandlerAdapter { private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName()); private int counter; private byte[] req; public TimeClientHandler() { req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes(); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { ByteBuf message; for (int i = 0; i < 100; i++) { message = Unpooled.buffer(req.length); message.writeBytes(req); ctx.writeAndFlush(message); } } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { String body = (String)