扩展篇】三. Netty基本介绍及使用


中国加油,武汉加油!

篇幅较长,请配合目录观看

项目准备

  1. 本案例基于 扩展篇】二. NIO实现通讯

1. Netty是什么

  1. JBoss提供的一个java开源框架。
  2. 不需要运行在Tomcat之类的服务器,是单独构建的服务器。
  3. 可以构建HTTP服务器,socket服务器,websocket服务器。
  4. 对JDK1.4以后提供的NIO的封装。
  5. 是一款异步的事件驱动的网络应用程序框架,支持快速开发可维护的高性能面向协议服务器和客户端,底层利用了NIO的机制
  6. 解决了io操作的繁琐,性能不保证的问题,便于开发者将精力集中在业务上,对开发者透明化,大大降低了开发门槛。

2. Netty中的重要组件

组件含义详情
Bootstrap/ServerBootstrap建立连接Netty引导组件,简化了NIO的开发步骤,是一个Netty的程序的开始,作用是配置和串联各个组件
EventLoopGroup事件循环组是EvenLoop组合,可以包含多个EventLoop。创建EventLoopGroup的时候,内部包含的方法就会创建一个子对象EventLoop
EventLoop事件循环循环服务Channel,可以包含多个Channel
Channel通道代表一个Scoket连接,或者其他IO操作组件。是通讯的载体
ChannelInitalizer初始化连接主要提供了一个传输通道Piepeline
ChannelPiepeline传输通道主要是管理各种ChannelHandler业务控制器,提供了一个链式管理模式
ChannelHandler业务控制器主要业务写入的地方,用开发人员写入,Netty也提供了很多写好的控制器和适配器,可以直接引用
ChannelInboundHandler通道传入控制器继承至ChannelHandler,在传输通道中对传入事间进行控制
ChannelOutboundHandler通道传出控制器继承至ChannelHandler,在传输通道中对传出事件进行控制
Decoder解码网络传输都是byte传输,所以Netty首先接收到的是byte,需要进行解码,编成java对象,Netty提供了很多解码器
Encoder编码和解码类似,在传出服务器的时候,需要编码成byte传输给客户端
Future/ChannelFuture消息返回Netty提供的返回结果,类似回调函数,告知你执行的结果是什么

3. Netty线程模型*

模型含义
单线程模型所有的IO操作都在同一个NIO线程上完成,一个线程纪要处理客户端连接还有处理客户端读写操作。
多线程模型与单线程模型对比,最大的区别就是一组NIO线程(线程池)处理IO操作。
主从线程模型服务端有2个线程池,主线程池主要处理客户端的连接,从线程池处理客户端的读写。

4. Netty实现通讯

4.1 新建netty-demo(maven)

4.2 导包

 <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.48.Final</version>
</dependency>

4.3 编写ServerSocketHandler

package com.wpj.hello;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.nio.charset.Charset;

public class ServerSocketHandler extends SimpleChannelInboundHandler<ByteBuf> {

    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("服务端读取的内容: " + byteBuf.toString(Charset.defaultCharset()));
    }
}

4.4 编写ServerDemo

package com.wpj.hello;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * Netty实现服务端
 */
public class ServerDemo {

    public static void main(String[] args) {

        try {
            // 创建2个线程池(主,从)
            EventLoopGroup master = new NioEventLoopGroup();    // 主要负责连接
            EventLoopGroup slave = new NioEventLoopGroup();     // 主要负责读写

            // 创建引导类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
//        serverBootstrap.group(); // 单线程模型
//        serverBootstrap.group(master); // 多线程模型
            serverBootstrap.group(master, slave);
            // 设置通道的类型
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childHandler(new ServerSocketHandler());
            // 绑定端口号是用异步来完成的
            ChannelFuture channelFuture = serverBootstrap.bind(8080);// 设置服务端端口号
            channelFuture.sync(); // 阻塞住
            System.out.println("服务端启动成功。。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

4.5 编写ClientDemo

package com.wpj.hello;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.charset.Charset;
import java.util.Scanner;

public class ClientDemo {
    public static void main(String[] args) {

        try {
            // 创建一个线程池
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            // 创建一个引导类
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup); // 客户端这边用多线程模型处理
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new SimpleChannelInboundHandler<ByteBuf>() {
                @Override
                protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
                    System.out.println("读取服务端响应的内容:"+byteBuf.toString(Charset.defaultCharset()));
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8080);
            channelFuture.sync();
            System.out.println("客户端连接成功。。。");

            // 创建一个ByteBuf对象
            ByteBuf byteBuf = Unpooled.buffer(1024 * 10);
            byteBuf.writeBytes("hello".getBytes("utf-8"));
            channelFuture.channel().writeAndFlush(byteBuf);
            System.out.println("客户端写入完成。。。。");

            // 给服务端写入数据
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5. 解编码器

5.1 编写SocketChannelHandlerString

package com.wpj.channelpipeline;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class SocketChannelHandlerString extends SimpleChannelInboundHandler<String>{
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String str) throws Exception {
        System.out.println("服务端读取的内容:"+str);
    }
}

5.2 定义服务端

package com.wpj.channelpipeline;

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.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class ServerDemo {

    public static void main(String[] args) {

        EventLoopGroup master = new NioEventLoopGroup();
        EventLoopGroup salve = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(master,salve);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.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 SocketChannelHandlerString());
                }
            });
            ChannelFuture channelFuture = bootstrap.bind(8080);
            channelFuture.sync();
            System.out.println("服务端启动成功");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

5.3 定义客户端

package com.wpj.channelpipeline;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.nio.charset.Charset;
import java.util.Scanner;

public class ClientDemo {

    public static void main(String[] args) {

        try {
            // 创建一个线程池
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            // 创建一个引导类
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup); // 客户端这边用多线程模型处理
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.handler(new SimpleChannelInboundHandler<String>(){
                @Override
                protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
                    System.out.println("ClientDemo.channelRead0");
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8080);
            channelFuture.sync();
            System.out.println("客户端连接成功。。。");

            Scanner scanner = new Scanner(System.in);
            while(true){
                ByteBuf byteBuf = Unpooled.buffer(1024 * 10);
                String next = scanner.next();
                byteBuf.writeBytes(next.getBytes("utf-8"));
                channelFuture.channel().writeAndFlush(byteBuf); // 异步的操作
                System.out.println("客户端写入完成。。。。");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

6. Http解编码器

6.1 定义SocketChannelHandlerHttpRequest

package com.wpj.http;

import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;

import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

public class SocketChannelHandlerHttpRequest extends SimpleChannelInboundHandler<FullHttpRequest>{
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest req) throws Exception {

        System.out.println("uri:"+req.uri());
        System.out.println("method:"+req.method().name());
        HttpHeaders headers = req.headers();
        List<Map.Entry<String, String>> entries = headers.entries();
        for(Map.Entry<String, String> ent:entries){
            String key = ent.getKey();
            String value = ent.getValue();
            System.out.println("--"+key+":"+value);
        }

        ByteBuf content = req.content();
        System.out.println(content.toString(Charset.defaultCharset()));

        // 响应给浏览器
        Channel channel = channelHandlerContext.channel();
        FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,HttpResponseStatus.OK);
        // 响应浏览器的内容
        String html ="<html><body><h1>Hello World</h1></body></html>";
        // 把html内容写入到resp对象里面
        resp.content().writeBytes(html.getBytes("utf-8"));
        // 输出resp对象
        channel.writeAndFlush(resp);
    }
}

6.2 定义服务端

package com.wpj.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 ServerDemo {
    public static void main(String[] args) {
        EventLoopGroup master = new NioEventLoopGroup();
        EventLoopGroup salve = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(master,salve);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new ChannelInitializer() {
                @Override
                protected void initChannel(Channel channel) throws Exception {
                    ChannelPipeline pipeline = channel.pipeline();
                    // 添加Http解编码器
                    pipeline.addLast(new HttpServerCodec()); // -- HttpRqeuest
                    pipeline.addLast(new HttpObjectAggregator(1024*10)); // -->FullHttpRequest
                    pipeline.addLast(new SocketChannelHandlerHttpRequest());

                }
            });
            ChannelFuture channelFuture = bootstrap.bind(8080);
            channelFuture.sync();
            System.out.println("服务端启动成功");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

6.3 Test

通过浏览器或者PostMan去测试

7. Nttey实现Ftp服务器

7.1 服务端

package com.wpj.ftp;

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 FtpServer {

    public static void main(String[] args) {
        try {
            EventLoopGroup master = new NioEventLoopGroup();
            EventLoopGroup slave= new NioEventLoopGroup();

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(master,slave);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new ChannelInitializer() {
                @Override
                protected void initChannel(Channel channel) throws Exception {
                    ChannelPipeline pipeline = channel.pipeline();
                    pipeline.addLast(new HttpServerCodec()); // 解码HttpRequest
                    pipeline.addLast(new HttpObjectAggregator(1024*10)); // 加密FullHttpRequset
                    pipeline.addLast(new SocketChannelHandler());
                }
            });
            ChannelFuture channelFuture= bootstrap.bind(8080);
            channelFuture.sync();
            System.out.println("服务端启动成功。。。");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

7.2 编写SocketChannelHandler

package com.wpj.ftp;

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

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class SocketChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    String path = "E:\\codeDevelop\\ideaDevelop\\springboot\\nz1904-shop";

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, FullHttpRequest request) throws Exception {
        String name = request.method().name();
        System.out.println(name);
        // /abc
        String uri = request.uri();
        if(!"GET".equals(name)){
            respError(channelHandlerContext,"只支持GET类型");
            return ;
        }

        File file = new File(path+uri);
        if(!file.exists()){
            respError(channelHandlerContext,"文件不存在");
            return ;
        }

        // 判断file是文件还是目录
        if(file.isDirectory()){
            showDirectory(channelHandlerContext,file,uri);
        }else{
            System.out.println("文件:"+file);
            showDirectoryFile(channelHandlerContext,file);
        }
    }

    private void showDirectoryFile(ChannelHandlerContext channelHandlerContext, File file) {

        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 10);
            FileChannel fileChannel = new FileInputStream(file.getAbsolutePath()).getChannel();
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append("<html>");
            while(fileChannel.read(byteBuffer) != -1){
                byteBuffer.flip();
                stringBuffer.append(new String(byteBuffer.array()));
                byteBuffer.clear();
            }
            stringBuffer.append("</html>");
            respContent(channelHandlerContext,stringBuffer.toString());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void showDirectory(ChannelHandlerContext channelHandlerContext, File file,String uri) {

        File[] files = file.listFiles(); // 获取目录下面子文件
        String html ="<html>";
        for(int i =0;i<files.length;i++){
            if("/".equals(uri)){
                html+="<li><a href ='"+uri+files[i].getName()+"'>"+files[i].getName()+"</a></li>";
            }else{
                html+="<li><a href ='"+uri+File.separator+files[i].getName()+"'>"+files[i].getName()+"</a></li>";
            }
        }
        html+="</html>";

        respContent(channelHandlerContext,html);
    }

    /**
     * 下载文件
     *  1.设置响应头(stream)
     *         a)流类型
     *         b)文件件的名称
     * 2。把文件写到resp中
     * @param channelHandlerContext
     * @param content
     */
    public void respError(ChannelHandlerContext channelHandlerContext,String content){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().add("ContextType","text/html;charset=utf8");
        String html = "<html><head><meta charset=\"utf-8\"></head> <h1>"+content+"</h1></html>";

        try {
            response.content().writeBytes(html.getBytes("utf-8"));
            channelHandlerContext.channel().writeAndFlush(response);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
    
    public void respContent(ChannelHandlerContext channelHandlerContext,String conent){
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
        response.headers().add("ContextType","text/html;charset=utf8");
        try {
            response.content().writeBytes(conent.getBytes("utf-8"));
            channelHandlerContext.channel().writeAndFlush(response);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值