Netty基础

网络编程框架Netty的介绍与使用

一、简介

Netty的官网https://netty.io/

Netty是一个为了快速开发可维护的高性能协议处理器与客户端的异步事件驱动的网络应用框架

Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.

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

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。

'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

netty

BIO、NIO、AIO的区别:

同步阻塞的区别:

这点可以参考知乎https://www.zhihu.com/question/19732473:

  • 同步与异步的关注的是消息通信机制,所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由调用者主动等待这个调用的结果。而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

  • 阻塞与非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程,当前线程可以去处理其他任务。

下面这部分参考了https://www.jianshu.com/p/a4e03835921a

  1. BIO(Block IO)

    BIO是同步阻塞式的IO,数据的读取写入必须阻塞在一个线程内等待其完成。采用BIO通信模式的应用,通常客户端需要开两个线程:一个线程负责监听服务器发过来的消息,一个线程负责读取用户的输入并发送消息。服务器有一个线程负责监听客户端的连接,多个线程处理客户端的信息。通常一个客户端一个线程。这种方式如果有大量请求同时连接的话要创建大量线程,增加了服务器的压力,虽然可以通过线程池机制来改善,但仍然是治标不治本的措施,只能适用于并发请求量很小的情况。

  2. NIO(Non-block IO)

    NIO是同步非阻塞式的IO,NIO引入了 Channel , Selector,Buffer等抽象对象。NIO 是直接面向缓冲区(ByteBuffer),而传统的IO面向流的。NIO 通过Channel(通道) 进行读写,通道是双向的,可读也可写,而流的读写是单向的。无论读写,通道只能和Buffer交互。因为 Buffer,通道可以异步地读写。Selector(选择器)用于使用单个线程处理多个通道。因此,它需要较少的线程来处理这些通道。线程之间的切换对于操作系统来说是昂贵的。 因此,为了提高系统效率选择器是有用的。

    NIO通常有两个线程,每个线程绑定一个轮询器(selector),A轮询器负责轮询是否有新的连接,B轮询器负责轮询连接是否有数据可读。服务端监测到新的连接之后,不再创建一个新的线程,而是直接将新连接绑定到B轮询器上。

  3. AIO(Asynchronous IO)

    AIO是异步非阻塞的IO,异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

二、Netty实战

1、添加依赖


    io.netty
    netty-all
    4.1.42.Final

2、Netty群发消息

2.1 Netty的线程模型

单线程模型 - 只有一个线程处理所有客户端的所有请求

多线程模型 - 有一个线程池处理多个客户端的所有请求

主从线程模型(商用) - 主线程池的线程用来处理客户端的连接请求,从线程池的线程用来处理客户端的消息请求

服务器

public static void main(String[] args) {

    //创建两个主从线程池
    EventLoopGroup master = new NioEventLoopGroup();
    EventLoopGroup slave = new NioEventLoopGroup();

    //创建服务器的初始化引导对象
    ServerBootstrap serverBootstrap = new ServerBootstrap();

    //配置引导对象
    serverBootstrap
            //设置当前Netty的线程模型
            .group(master, slave)
            //设置Channel的类型
            .channel(NioServerSocketChannel.class)
            //设置事件处理器 -- 重要
            .childHandler(new ServerChannelHandler());

    //绑定端口
    ChannelFuture future = serverBootstrap.bind(8080);//绑定这个动作其实是一个异步的动作
    try {
        future.sync();//同步阻塞
        System.out.println("端口绑定完成,服务已经启动!");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

服务器的事件处理器:

@ChannelHandler.Sharable
public class ServerChannelHandler extends SimpleChannelInboundHandler {

    List channels = new ArrayList();

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("有一个客户端连接了服务器!");
        channels.add(ctx.channel());
    }

    //消息处理的方法
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
        System.out.println("接收到客户端的消息:" + byteBuf.toString(Charset.forName("UTF-8")));

        //将消息群发给其他的客户端
        for (Channel channel : channels) {
            if(channel != ctx.channel()){
                ByteBuf buf = Unpooled.copiedBuffer(byteBuf);
                channel.writeAndFlush(buf);
            }
        }
    }
}

客户端

public class NettyClient {
    public static void main(String[] args) {
        //创建引导对象
        Bootstrap bootstrap = new Bootstrap();
        //设置线程模型
        bootstrap
                .group(new NioEventLoopGroup())
                .channel(NioSocketChannel.class)
                //设置服务端消息处理器
                .handler(new SimpleChannelInboundHandler() {
                    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                        System.out.println("接收到服务端的消息:" + byteBuf.toString(Charset.forName("UTF-8")));
                    }
                });

        //连接服务器
        ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
        try {
            future.sync();
            System.out.println("连接服务器成功!");

            //给服务器循环发送消息
            Scanner scanner = new Scanner(System.in);
            while(true){
                System.out.println("请输入发送的内容:");
                String content = scanner.next();

                //发送消息到服务端
                Channel channel = future.channel();//和服务器的连接对象
                byte[] bytes = content.getBytes("UTF-8");
                ByteBuf byteBuf = Unpooled.buffer(bytes.length);
                byteBuf.writeBytes(bytes);
                channel.writeAndFlush(byteBuf);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
2.2 Netty的事件处理器

Netty中的消息按照方向可以分为两类:出站消息与入站消息。在Netty中可以配置事件处理器链,对出站消息和入站消息进行处理。 入站处理器 - 继承SimpleChannelInboundHandler类 出站处理器 - 继承ChannelOutboundHandlerAdapter类。

事件处理器最常用的是编码与解码,常见的编码与解码器有:

pipeline.addLast(new StringDecoder()); - 消息的解码,将ByteBuf转换成

Stringpipeline.addLast(new StringEncoder()); - 消息的编码,将String转成ByteBuf

pipeline.addLast(new LineBasedFrameDecoder(1024 * 1024)); - 按行解决拆包、粘包问题的解码器

public class NettyServer {
    public static void main(String[] args) {
        //主线程池 处理客户端的连接请求
        EventLoopGroup master = new NioEventLoopGroup();
        //从线程池 处理客户端的消息请求
        EventLoopGroup slave = new NioEventLoopGroup();
        //创建服务器的初始化引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                //设置netty的线程模型:单线程、多线程、主从线程池(主线程池负责连接,从线程池负责消息的发送)
                .group(master,slave)
                //设置管道类型
                .channel(NioServerSocketChannel.class)
                //设置子线程池的事件处理
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        //事件处理器链的使用
                        ChannelPipeline pipeline = channel.pipeline();
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new ServerChannelHandler());
                    }
                });
        //绑定端口是一个异步动作
        ChannelFuture future = serverBootstrap.bind(8080);
        try {
            //同步
            future.sync();
            System.out.println("绑定端口已完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、Netty对HTTP的支持

添加了三个事件处理器

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;


public class HttpNettyServer {
    public static void main(String[] args) {
        //主线程池 处理客户端的连接请求
        EventLoopGroup master = new NioEventLoopGroup();
        //从线程池 处理客户端的消息请求
        EventLoopGroup slave = new NioEventLoopGroup();
        //创建服务器的初始化引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                //设置netty的线程模型:单线程、多线程、主从线程池(主线程池负责连接,从线程池负责消息的发送)
                .group(master,slave)
                //设置管道类型
                .channel(NioServerSocketChannel.class)
                //设置子线程池的事件处理
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        //事件处理器链的使用
                        ChannelPipeline pipeline = channel.pipeline();
                        pipeline.addLast(new HttpServerCodec());
                        pipeline.addLast(new HttpObjectAggregator(1024*1024));
                        pipeline.addLast(new HttpChannelHandler());
                    }
                });
        //绑定端口是一个异步动作
        ChannelFuture future = serverBootstrap.bind(8080);
        try {
            //同步
            future.sync();
            System.out.println("绑定端口已完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

HttpChannelHandler

package com.qianfeng.http;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;

import java.nio.charset.StandardCharsets;

public class HttpChannelHandler extends SimpleChannelInboundHandler {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
        System.out.println(fullHttpRequest.method());
        System.out.println(fullHttpRequest.uri());
        System.out.println(fullHttpRequest.headers());
        System.out.println(fullHttpRequest.content().toString(StandardCharsets.UTF_8));
    }
}
3.1 使用Netty编写一个Http文件服务器

服务器

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;


public class HttpNettyServer {
    public static void main(String[] args) {
        //主线程池 处理客户端的连接请求
        EventLoopGroup master = new NioEventLoopGroup();
        //从线程池 处理客户端的消息请求
        EventLoopGroup slave = new NioEventLoopGroup();
        //创建服务器的初始化引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                //设置netty的线程模型:单线程、多线程、主从线程池(主线程池负责连接,从线程池负责消息的发送)
                .group(master,slave)
                //设置管道类型
                .channel(NioServerSocketChannel.class)
                //设置子线程池的事件处理
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        //事件处理器链的使用
                        ChannelPipeline pipeline = channel.pipeline();
                        //用于传输文件
                        pipeline.addLast(new ChunkedWriteHandler());
                        pipeline.addLast(new HttpServerCodec());
                        //http对象聚合处理器
                        pipeline.addLast(new HttpObjectAggregator(1024*1024));
                        //自定义的处理器
                        pipeline.addLast(new HttpChannelHandler());
                    }
                });
        //绑定端口是一个异步动作
        ChannelFuture future = serverBootstrap.bind(80);
        try {
            //同步
            future.sync();
            System.out.println("绑定端口已完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

自定义的处理器


import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.stream.ChunkedNioFile;

import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Objects;

public class HttpChannelHandler extends SimpleChannelInboundHandler {
    private static final String path = "d:\\ceshi";
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest fullHttpRequest) throws Exception {
        //如果请求方式不是get
        if(!"get".equalsIgnoreCase(fullHttpRequest.method().toString())){
           setError(channelHandlerContext,"不支持除get外的其他请求方式");
           return;
        }
        //获得请求路径
        String uri = fullHttpRequest.uri();
        //中文的处理
        uri = URLDecoder.decode(uri, "UTF-8");
        File file = new File(path,uri);
        //如果请求的文件不存在
        if(!file.exists()){
            setError(channelHandlerContext,"对不起,您访问的资源不存在");
            return;
        }
        //如果是目录
        if(file.isDirectory()){
            directoryHandler(channelHandlerContext,file);
        }
        //如果是文件
        else if(file.isFile()){
            fileHandler(channelHandlerContext,file);
        }
    }

    private void setError(ChannelHandlerContext ctx,String error){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
        //告诉浏览器响应体中的内容是什么类型
        response.headers().add("Content-Type","text/html;charset=utf-8");
        response.content().writeBytes((""+error+"").getBytes(StandardCharsets.UTF_8));
        ctx.writeAndFlush(response);
        ctx.close();
    }

    private void fileHandler(ChannelHandlerContext ctx,File file){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        //设置下载的响应头
        response.headers().add("Content-Type","application/octet-stream");
        response.headers().add("Content-Length",file.length());
        ctx.writeAndFlush(response);

        try {
            ChunkedNioFile nioFile = new ChunkedNioFile(file,1024*1024);
            //因为这是一个异步的操作,所以要设置一个监听器监听文件下载
            ChannelFuture future = ctx.writeAndFlush(nioFile);
            future.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(channelFuture.isSuccess()){
                        System.out.println("下载完成,关闭连接");
                        ctx.close();
                    }
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void directoryHandler(ChannelHandlerContext ctx,File file){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        StringBuilder sb = new StringBuilder("");
        sb.append("
    "); String filePath = file.getPath(); String substring = filePath.substring(filePath.indexOf(path) + path.length()); substring = substring.replaceAll("\\\\","/"); for (File f : Objects.requireNonNull(file.listFiles())) { sb.append("
  • "); sb.append("").append(f.getName()).append(""); sb.append("
  • "); } sb.append("
").append(""); response.headers().add("Content-Type","text/html;charset=utf-8"); response.content().writeBytes(sb.toString().getBytes(StandardCharsets.UTF_8)); ctx.writeAndFlush(response); ctx.close(); } }

4、Netty对WebSocket的支持

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

package com.qianfeng.websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;


public class WebSocketServer {
    public static void main(String[] args) {
        //主线程池 处理客户端的连接请求
        EventLoopGroup master = new NioEventLoopGroup();
        //从线程池 处理客户端的消息请求
        EventLoopGroup slave = new NioEventLoopGroup();
        //创建服务器的初始化引导对象
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                //设置netty的线程模型:单线程、多线程、主从线程池(主线程池负责连接,从线程池负责消息的发送)
                .group(master,slave)
                //设置管道类型
                .channel(NioServerSocketChannel.class)
                //设置子线程池的事件处理
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(Channel channel) throws Exception {
                        //事件处理器链的使用
                        ChannelPipeline pipeline = channel.pipeline();

                        pipeline.addLast(new HttpServerCodec());
                        //http对象聚合处理器
                        pipeline.addLast(new HttpObjectAggregator(1024*1024));
                        //websocket
                        pipeline.addLast(new WebSocketServerProtocolHandler("/"));
                        //自定义的处理器
                        pipeline.addLast(new WebSocketHandler());
                    }
                });
        //绑定端口是一个异步动作
        ChannelFuture future = serverBootstrap.bind(80);
        try {
            //同步
            future.sync();
            System.out.println("绑定端口已完成");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

WebSocketHandler

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

public class WebSocketHandler extends SimpleChannelInboundHandler {
    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("有客户端连接");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("客户端断开连接");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {
        System.out.println("接收到客户端发来的消息:"+textWebSocketFrame.text());
        ctx.writeAndFlush(new TextWebSocketFrame("你好"));
    }
}

客户端的编写:

                var ws;
                  //初始化连接websocket
                  function initWebSocket(){
                        
                        //判断浏览器是否支持WebSocket
                        if(window.WebSocket){
                              
                              //连接WebSocket服务器
                              ws = new WebSocket("ws://127.0.0.1");
                              
                              //设置websocket的各种回调方法
                              ws.onopen = function(){
                                    console.log("已经正常连接WebSocket服务器!");
                              };
                              
                              ws.onclose = function(){
                                    console.log("连接已经关闭!");
                              }
                              
                              ws.onerror = function(){
                                    console.log("连接异常!");
                              }
                              
                              ws.onmessage = function(msg){
                                    console.log("已经接收到服务器的消息:"  + msg.data);
                                    
                                    //
                                    var msg = "
  • 服务器:" + msg.data + "
  • "; document.getElementById("msgUl") .insertAdjacentHTML("beforeEnd", msg);//startAfter startBefore //'beforeBegin', 'afterBegin', 'beforeEnd', 'afterEnd' } } else { alert("骚瑞,您的浏览器太垃圾了,请换个高级的浏览器!"); } }

    你可能感兴趣的:(Netty基础)