Netty权威指南 第2版

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)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hello_world!

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值