转载自  从NIO到Netty开发

1. 从传统BIO到NIO的升级

  1. Client/Server模型是网络编程的基本模型,服务端提供位置信息,客户端通过连接操作向服务端发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。
  2. 传统的Socket编程是服务端一直处于accpet阻塞等待的状态,并且只有客户端发送了请求,服务才会从阻塞状态变成处理任务的状态,当任务处理完了,服务端接着进入阻塞状态,再此看来,服务端和客户端都是同步的情况。
  3. Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。

2. NIO新特性

  1. Java NIO: Channels and Buffers(通道和缓冲区)标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
  2. Java NIO: Non-blocking IO(非阻塞IO) Java NIO可以让你非阻塞的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。
  3. Java NIO: Selectors(选择器) Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。

3. NIO服务端实现

根据上图,发现服务端的事件有2个,一是接受连接事件,二是读取数据:

public class NIOServer {private ByteBuffer readBuffer;    private Selector selector;    private ServerSocket serverSocket;    public static void main(String[] args) {NIOServer server = new NIOServer();server.init();System.out.println("server started:8383");server.listener();}    public void init() {        //1. 创建临时缓冲区readBuffer = ByteBuffer.allocate(1024);        //2. 创建服务端Socket非阻塞通道ServerSocketChannel serverSocketChannel;        try {serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);            //3. 指定内部Socket绑定的服务端地址 并支持重用端口,因为有可能多个客户端同时访问同一端口serverSocket=serverSocketChannel.socket();serverSocket.setReuseAddress(true);serverSocket.bind(new InetSocketAddress(8383));            //4. 创建轮询器 并绑定到管道上,开始监听客户端请求selector = Selector.open();serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (Exception e) {e.printStackTrace();}}    private void listener() {        while (true) {            try {                //5. 开始监听事件,不断取出事件的key,假如存在事件,则直接处理。selector.select();Iterator<SelectionKey> keys = selector.selectedKeys().iterator();                while (keys.hasNext()) {SelectionKey key = keys.next();keys.remove();handleKey(key);}} catch (Exception e) {e.printStackTrace();}}}    private void handleKey(SelectionKey key) throws IOException {SocketChannel channel = null;        try {            //6. 如果有客户端要连接 这里则处理是否接受连接事件if (key.isAcceptable()) {ServerSocketChannel severChannel = (ServerSocketChannel) key.channel();channel = severChannel.accept();channel.configureBlocking(false);                // 告诉轮询器 接下来关心的是读取客户端数据这件事channel.register(selector, SelectionKey.OP_READ);} else if (key.isReadable()) { //7. 如果客户端发送数据,则这里读取数据。channel = (SocketChannel) key.channel();                // 清空缓冲区readBuffer.clear();                // 当客户端关闭channel后,会不断收到read事件,此刻read方法返回-1 所以对应的服务器端也需要关闭channelint readCount = channel.read(readBuffer);                if (readCount > 0) {readBuffer.flip();String question = CharsetHelper.decode(readBuffer).toString();System.out.println("server get the question:" + question);String answer = getAnswer(question);channel.write(CharsetHelper.encode(CharBuffer.wrap(answer)));} else {channel.close();}} } catch (Exception e) {e.printStackTrace();}finally {            //8. 断开连接通道if (channel!=null) {channel.close();}}}    public static String getAnswer(String question) {String answer = null;        switch (question) {        case "who":answer = "我是小娜\n";            break;        case "what":answer = "我是来帮你解闷的\n";            break;        case "where":answer = "我来自外太空\n";            break;        case "hi":answer = "hello\n";            break;        case "bye":answer = "88\n";            break;        default:answer = "请输入 who, 或者what, 或者where";}        return answer;}}

4. NIO客户端实现:

客户端的实现有3个步骤:1.请求连接。2.当连接成功,写数据。3.读取服务端结果。

public class NIOClient implements Runnable {private BlockingQueue<String> words;    private Random random;    public static void main(String[] args) {        // 多个线程发起Socket客户端连接请求for (int i = 0; i < 5; i++) {NIOClient c = new NIOClient();c.init();            new Thread(c).start();}}    //1. 初始化要发送的数据private void init() {words = new ArrayBlockingQueue<String>(5);random = new Random();        try {words.put("hi");words.put("who");words.put("what");words.put("where");words.put("bye");} catch (Exception e) {            // TODO: handle exception}}    //2. 启动子线程代码@Overridepublic void run() {SocketChannel channel = null;Selector selector = null;        try {            //3. 创建连接服务端的通道 并设置为阻塞方法,这里需要指定服务端的ip和端口号channel = SocketChannel.open();channel.configureBlocking(false);channel.connect(new InetSocketAddress("localhost", 8383));selector = Selector.open();            //4. 请求关心连接事件channel.register(selector, SelectionKey.OP_CONNECT);            boolean isOver = false;            while (!isOver) {selector.select();Iterator<SelectionKey> keys = selector.selectedKeys().iterator();                while (keys.hasNext()) {SelectionKey key = keys.next();keys.remove();                    if (key.isConnectable()) { //5. 当通道连接准备完毕,发送请求并指定接收允许获取服务端返回信息if (channel.isConnectionPending()) {                            if (channel.finishConnect()) {key.interestOps(SelectionKey.OP_READ);channel.write(CharsetHelper.encode(CharBuffer.wrap(getWord())));sleep();} else {key.cancel();}}} else if (key.isReadable()) {//6. 开始读取服务端返回数据ByteBuffer byteBuffer = ByteBuffer.allocate(512);channel.read(byteBuffer);byteBuffer.flip();String answer = CharsetHelper.decode(byteBuffer).toString();System.out.println("client get the answer:" + answer);String word = getWord();                        if (word != null) {channel.write(CharsetHelper.encode(CharBuffer.wrap(getWord())));} else {isOver = true;}sleep();}}}} catch (Exception e) {e.printStackTrace();} finally {            //7. 关闭通道if (channel != null) {                try {channel.close();} catch (IOException e) {e.printStackTrace();}}}}    public String getWord() {        return words.poll();}    private void sleep() {        try {TimeUnit.SECONDS.sleep(random.nextInt(3));} catch (InterruptedException e) {e.printStackTrace();}}}

5.Netty开发简介

上面提到,NIO可以实现同步非阻塞的数据交互,但是对于NIO来说,一个普通的请求数据需要太多的开发步骤,不利于推广,这里主要介绍NIO的实现框架Netty.

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。

6. Netty服务端实现:

public class EchoServer {private final int port;    public EchoServer(int port) {        this.port = port;}    public void start() throws Exception {        //1.创建线程组EventLoopGroup group=new NioEventLoopGroup();        try {            //2.创建服务端启动对象 装配线程组&交互通道&服务器端口&网络请求处理器链ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(group).channel(NioServerSocketChannel.class) .localAddress("localhost", port)    .childHandler(new ChannelInitializer<Channel>() { @Overrideprotected void initChannel(Channel ch) throws Exception {ch.pipeline().addLast(new EchoOutHandler1());ch.pipeline().addLast(new EchoOutHandler2());ch.pipeline().addLast(new EchoInHandler1());ch.pipeline().addLast(new EchoInHandler2());}});            // 3.开始监听客户端请求ChannelFuture channelFuture = serverBootstrap.bind().sync();System.out.println("开始监听,端口号为:"+channelFuture.channel().localAddress());            // 4.等待所有请求执行完毕后,关闭通道;如请求还没执行完,这里为阻塞状态。channelFuture.channel().closeFuture().sync();} catch (Exception e) {e.printStackTrace();}finally {            //5.停止所有线程组内部代码的执行group.shutdownGracefully().sync();}}    public static void main(String[] args) throws Exception {        new EchoServer(2000).start();}}

7.Netty客户端实现:

public class EchoClient {public static void main(String[] args) throws InterruptedException {        new EchoClient("localhost", 2000).start();}    private final String host;    private final int port;    public EchoClient(String host, int port) {        this.host = host;        this.port = port;}    private void start() throws InterruptedException {        //1.创建线程组EventLoopGroup group = new NioEventLoopGroup();        try {            //2. 创建客户端启动对象,同样需要装配线程组,通道,绑定远程地址,请求处理器链。Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).remoteAddress(host, port).handler(new ChannelInitializer<Channel>() {                        @Overrideprotected void initChannel(Channel ch) throws Exception {ch.pipeline().addLast(new EchoClientHandler());}});            //3.开始请求连接ChannelFuture future = bootstrap.connect().sync();            //4.当请求操作结后,关闭通道。future.channel().closeFuture().sync();} catch (Exception e) {e.printStackTrace();} finally {            if (group != null) {group.shutdownGracefully().sync();}}}}

8.Netty处理器链

对于向服务端发送一个请求,并得到一个响应来说。如果使用Netty来说,需要实现两种不同的处理器,一个是读的一个是写的。他们共同组成一个链式调用,如下图:

对于服务端来说,上面我们创建了4个处理器,他们组成一条链,分别是:EchoInHandler1 -> EchoInHandler2 -> EchoOutHandler2 -> EchoOutHandler1.

public class EchoInHandler1 extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("EchoInHandler1  channelRead...");        //将消息传递到新的链。。。ctx.fireChannelRead(msg);}    @Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}public class EchoInHandler2 extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("EchoInHandler2  channelRead...");        // Object msg 为Netty的一种缓存对象ByteBuf buffer = (ByteBuf) msg;        byte[] req = new byte[buffer.readableBytes()];buffer.readBytes(req);String reqBody = new String(req, "UTF-8");System.out.println("获取到的客户端请求:" + reqBody);        // 往客户端写数据String date = new Date().toString();ByteBuf returnBuf = Unpooled.copiedBuffer(date.getBytes());ctx.write(returnBuf);}    @Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}}public class EchoOutHandler2 extends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println("EchoOutHandler2 write...");ctx.write(msg);}}public class EchoOutHandler1 extends ChannelOutboundHandlerAdapter {@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {System.out.println("EchoOutHandler1 write...");System.out.println("write msg:" + msg);ctx.write(msg);ctx.flush();// 最后将数据刷新到客户端}}

客户端的处理器主要是当连接成功后,请求获取当前时间,并读取返回结果:

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>{    //客户端连接服务器的时候调用@Override    public void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("客户端连接服务器。。。。");        byte[] req = "QUERY TIME ORDER".getBytes();ByteBuf copiedBuffer = Unpooled.buffer(req.length);copiedBuffer.writeBytes(req);ctx.writeAndFlush(copiedBuffer);}    //读取服务端数据@Override    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf bytbuf) throws Exception {System.out.println("client get the server's data");        byte[] resp=new byte[bytbuf.readableBytes()];bytbuf.readBytes(resp);String respContent = new String(resp,"UTF-8");System.out.println("返回的数据:"+respContent);}    //强制关闭服务器的连接也会造成异常:远程主机强迫关闭了一个现有的连接。@Override    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println(cause.getLocalizedMessage());ctx.close();}}

从NIO到Netty开发相关推荐

  1. 【Netty】使用 Netty 开发 HTTP 服务器

    文章目录 一. HTTP 服务器开发 二. HTTP 服务器代码分析 1 . Netty 开发 HTTP 服务器与 TCP 服务器对比 2 . ChannelInitializer 设置 3 . 自定 ...

  2. 谈谈如何使用Netty开发实现高性能的RPC服务器

    RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络,从远程计算机程序上请求服务,而不必了解底层网络技术的协议.说的再直白一点,就是客户端在不必知道 ...

  3. 从BIO、NIO到Netty

    本文来说下从 BIO.NIO 到 Netty的一路发展过程 文章目录 BIO 传统的阻塞式通信流程 代码示例 NIO NIO概述 NIO核心组件解读 NIO为啥更好 使用NIO编写代码太复杂 Nett ...

  4. NIO与Netty编程(三)之Netty编程

    1.概述 Netty是JBOSS提供的一个Java开源框架.Netty提供异步的,基于事件驱动的网络应用程序框架,用以快速开发高性能.高可靠性的网络IO程序. Netty是一个基于NIO的网络编程框架 ...

  5. IO回忆录之怎样过目不忘(BIO/NIO/AIO/Netty)2017版

    有热心的网友加我微信,时不时问我一些技术的或者学习技术的问题.有时候我回微信的时候都是半夜了.但是我很乐意解答他们的问题.因为这些年轻人都是很有上进心的,所以在我心里他们就是很优秀的,我愿意多和努力的 ...

  6. 搭建简单的Netty开发环境

    今天准备学学声明显赫的Netty框架,自然要先学会怎么搭建一个简单的Netty开发环境啦.话不多说,下面进入正文. 编辑器方面自然是推荐IntelJ idea了,idea的强大不必多说,百度一下你就知 ...

  7. 华为架构师撰写的Netty核心笔记,从Java NIO到Netty的高级特性

    众所周知,Netty 作为当前流行的 NIO 框架,操作省时.省事还安全,在云计算.大数据通讯,电商.游戏等领域都有广泛的应用.如果是一个大型网站,内部接口非常多的情况下,好处很明显--首先就是长链接 ...

  8. NIO 与 Netty 编程

    多线程编程 基本知识回顾 线程安全 线程间通信 BIO 编程 NIO 编程 概述 文件IO 网络IO AIO 和IO 总结 Netty 框架 概述 核心API 入门案例  ...

  9. 分别基于IO、NIO、Netty的Java网络程序

    分别基于IO.NIO.Netty的Java网络程序 IDE:IntelliJ IDEA 文章目录 分别基于IO.NIO.Netty的Java网络程序 一.Java NIO 1.1 NIO与传统IO对比 ...

最新文章

  1. Laravel5.4重新登陆跳转到登陆前页面的原理和实现
  2. python使用符号#表示单行注释-Python中注释(多行注释和单行注释)的用法实例...
  3. iOS开发中显示实时的FPS值
  4. 计算机表格按性别排列,Excel表格性别数据-Excel 按性别(男女)排序
  5. ExtJs4 基础必备
  6. 【图像边缘检测】基于matlab最小二乘法椭圆边缘检测【含Matlab源码146期】
  7. VC维(Vapnik–Chervonenkis dimension)
  8. snipaste滚动截图方法_Snipaste——这是我用过最好用的截图贴图工具!
  9. Docker运维笔记-Docker端口映射
  10. wincc报表日报表实例_wincc报表例程
  11. Web前端从开始到入门(2)
  12. 农业大省吉林谋乡村全面振兴:农业强、农村美、农民富
  13. 单调、加班、血汗工厂,被夸大的富士康背后真相到底是什么?
  14. 蓝桥杯 2018 C++ A组 初赛部分题解
  15. 搞事情 | 大数据文摘和ta的朋友们:环游世界的80天
  16. 数据安全法(草案)概述
  17. CSDN社区关于水晶报表的讨论
  18. 新恒结衣为什么是中国程序员共同的老婆?
  19. 163企业邮箱注册申请,外贸企业邮箱首选什么邮箱呢?
  20. Datetimepicker.js用法

热门文章

  1. java spring 拦截器_Spring MVC拦截器(Interceptor)的配置及使用
  2. leetcode53. 最大子数组和(暴力+贪心)
  3. 7-1 作业调度算法--先来先服务 (30 分)(思路+详解+vector+map+map做法)Come Baby!!!!!!!!!!!
  4. leedcode05 找出缺失的观测数据(思路加详解)
  5. 矩形法_字体设计 | 新手必学,超简单的矩形造字法!!
  6. MarkDown语法, 快捷键,Dos命令
  7. 数据结构---邻接矩阵的BFS
  8. P1297 [国家集训队]单选错位 期望
  9. NC107617 poj3020 Antenna Placement
  10. 牛客题霸 [容器盛水问题] C++题解/答案