Netty之多协议技术(五)

上一章节主要介绍了netty的自带编解码使用和如何使用自定义的编解码完成业务,今天将要介绍netty关于协议相关知识,主要内容如下:

1 netty基于http协议的使用:完成文件目录浏览功能

该场景不需要自己写客户端,只需要写服务端即可,然后通过http的get方法请求该服务端,服务端就会返回文件目录的层级解析和展示:直接看代码:服务端代码

package http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * @Author 18011618
 * @Description
 * @Date 16:37 2018/6/25
 * @Modify By
 */
public class HttpFileServer {
    //浏览文件的根目录
    private static final String DEFAULT_URL = "/";

    public void run(final int port, final String url) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    // 请求消息解码器
                    ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
                    // 目的是将多个消息转换为单一的request或者response对象
                    ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
                    // 响应解码器
                    ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
                    // 目的是支持异步大文件传输()
                    ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
                    // 业务逻辑
                    ch.pipeline().addLast("fileServerHandler", new HttpFileServerHandler(url));
                }
            });
            ChannelFuture future = b.bind("127.0.0.1", port).sync();
            System.out.println("HTTP文件目录服务器启动,网址是 : " + "http://127.0.0.1:" + port + url);
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        String url = DEFAULT_URL;
        if (args.length > 1)
            url = args[1];
        new HttpFileServer().run(port, url);
    }
}

上面红色部分标准就是基于HTTP协议的实现,下面再看一下IO线程:

package http;

import static io.netty.handler.codec.http.HttpHeaders.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaders.setContentLength;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONNECTION;
import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpHeaders.Names.LOCATION;
import static io.netty.handler.codec.http.HttpMethod.GET;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN;
import static io.netty.handler.codec.http.HttpResponseStatus.FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpResponseStatus.METHOD_NOT_ALLOWED;
import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelProgressiveFuture;
import io.netty.channel.ChannelProgressiveFutureListener;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.stream.ChunkedFile;
import io.netty.util.CharsetUtil;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.regex.Pattern;

import javax.activation.MimetypesFileTypeMap;

/**
 * @Author 18011618
 * @Description 完成文件目录浏览处理的handler
 * @Date 16:36 2018/6/25
 * @Modify By
 */
public class HttpFileServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private final String url;

    public HttpFileServerHandler(String url) {
        this.url = url;
    }

    /**
     * 请求解析文件目录层级以及对应的内容
     * @param ctx
     * @param request
     * @throws Exception
     */
    public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        //是否可以解码
        if (!request.getDecoderResult().isSuccess()) {
            sendError(ctx, BAD_REQUEST);
            return;
        }
        //请求的方法是否是get
        if (request.getMethod() != GET) {
            sendError(ctx, METHOD_NOT_ALLOWED);
            return;
        }
        final String uri = request.getUri();
        final String path = sanitizeUri(uri);
        if (path == null) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        File file = new File(path);
        if (file.isHidden() || !file.exists()) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        if (file.isDirectory()) {
            if (uri.endsWith("/")) {
                sendListing(ctx, file);
            } else {
                sendRedirect(ctx, uri + '/');
            }
            return;
        }
        if (!file.isFile()) {
            sendError(ctx, FORBIDDEN);
            return;
        }
        //打开文件系统
        RandomAccessFile randomAccessFile = null;
        try {
            //以只读的方式打开文件
            randomAccessFile = new RandomAccessFile(file, "r");
        } catch (FileNotFoundException fnfe) {
            sendError(ctx, NOT_FOUND);
            return;
        }
        //获取文件长度
        long fileLength = randomAccessFile.length();
        //创建响应
        HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
        setContentLength(response, fileLength);
        setContentTypeHeader(response, file);
        if (isKeepAlive(request)) {
            response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
        }
        ctx.write(response);
        ChannelFuture sendFileFuture;
        sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
        //监听用户浏览目录事件
        sendFileFuture.addListener(new ChannelProgressiveFutureListener() {

            public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
                if (total < 0) { // total unknown
                    System.err.println("Transfer progress: " + progress);
                } else {
                    System.err.println("Transfer progress: " + progress + " / " + total);
                }
            }


            public void operationComplete(ChannelProgressiveFuture future) throws Exception {
                System.out.println("Transfer complete.");
            }
        });
        ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        if (!isKeepAlive(request)) {
            lastContentFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 请求出现异常 关闭链路
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }

    private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");

    private String sanitizeUri(String uri) {
        try {
            uri = URLDecoder.decode(uri, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            try {
                uri = URLDecoder.decode(uri, "ISO-8859-1");
            } catch (UnsupportedEncodingException e1) {
                throw new Error();
            }
        }
        if (!uri.startsWith(url)) {
            return null;
        }
        if (!uri.startsWith("/")) {
            return null;
        }
        uri = uri.replace('/', File.separatorChar);
        if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) || uri.startsWith(".") || uri.endsWith(".") || INSECURE_URI.matcher(uri).matches()) {
            return null;
        }
        return System.getProperty("user.dir") + File.separator + uri;
    }

    private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");

    private static void sendListing(ChannelHandlerContext ctx, File dir) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
        response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
        StringBuilder buf = new StringBuilder();
        String dirPath = dir.getPath();
        buf.append("<!DOCTYPE html>\r\n");
        buf.append("<html><head><title>");
        buf.append(dirPath);
        buf.append(" 目录:");
        buf.append("</title></head><body>\r\n");
        buf.append("<h3>");
        buf.append(dirPath).append(" 目录:");
        buf.append("</h3>\r\n");
        buf.append("<ul>");
        buf.append("<li>链接:<a href=\"../\">..</a></li>\r\n");
        for (File f : dir.listFiles()) {
            if (f.isHidden() || !f.canRead()) {
                continue;
            }
            String name = f.getName();
            if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
                continue;
            }
            buf.append("<li>链接:<a href=\"");
            buf.append(name);
            buf.append("\">");
            buf.append(name);
            buf.append("</a></li>\r\n");
        }
        buf.append("</ul></body></html>\r\n");
        ByteBuf buffer = Unpooled.copiedBuffer(buf, CharsetUtil.UTF_8);
        response.content().writeBytes(buffer);
        buffer.release();
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 重定向
     * @param ctx
     * @param newUri
     */
    private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
        response.headers().set(LOCATION, newUri);
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString() + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 设置HTTP请求HEAD
     * @param response
     * @param file
     */
    private static void setContentTypeHeader(HttpResponse response, File file) {
        MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
        response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
    }

}

OK 这个时候直接运行一下服务端:会输出一个URL


点击URL之后就能显示文件浏览目录层次


其中每一个超链接可以再点击:


一个简单的文件浏览器功能就实现了...

2 netty基于udp协议的使用:是一种基于UDP的传输协议

业务场景:客户端发送给服务端'我要写诗',服务端接受到客户端的请求,随机在已有的诗句中,返回一句给'客户端',直接看代码


首先看client相关的:

package udp.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
/**
 * @Author 18011618
 * @Description UDP处理的handlers
 * @Date 17:00 2018/6/25
 * @Modify By
 */
public class UDPClientHandler extends  SimpleChannelInboundHandler<DatagramPacket> {
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
        //数据包转换为字符串
        String response = msg.content().toString(CharsetUtil.UTF_8);
        if (response.startsWith("我要写诗: ")) {
            System.out.println(response);
            ctx.close();
        }
    }
}

和客户端:

package udp.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.CharsetUtil;
import java.net.InetSocketAddress;
/**
 * @Author 18011618
 * @Description 模拟UDP客户端发送请求
 * @Date 17:00 2018/6/25
 * @Modify By
 */
public class UDPClient {
    public void run(int port) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            //设置为UDP处理的channel
            b.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new UDPClientHandler());//设置业务处理的handler
            Channel ch = b.bind(0).sync().channel();
            // 向网段内的所有机器广播UDP消息
            // DatagramPacket UPD传输包
            ch.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("我要写诗", CharsetUtil.UTF_8), new InetSocketAddress("255.255.255.255", port))).sync();
            if (!ch.closeFuture().await(15000)) {
                System.out.println("查询超时!");
            }
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        new UDPClient().run(port);
    }
}

再看看服务端的代码:

package udp.server;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.ThreadLocalRandom;
/**
 * @Author 18011618
 * @Description UDP服务端处理的handler
 * @Date 17:01 2018/6/25
 * @Modify By
 */
public class UDPServerHandler extends SimpleChannelInboundHandler<DatagramPacket>{

    
    private static final String[] DICTIONARY = { "只要功夫深,铁棒磨成针。",
            "旧时王谢堂前燕,飞入寻常百姓家。", "洛阳亲友如相问,一片冰心在玉壶。", "一寸光阴一寸金,寸金难买寸光阴。",
            "老骥伏枥,志在千里。烈士暮年,壮心不已!" };

    private String nextQuote() {
        int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
        return DICTIONARY[quoteId];
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
        String req = packet.content().toString(CharsetUtil.UTF_8);
        System.out.println(req);
        if ("我要写诗".equals(req)) {
            ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer(
                    "我要写诗: " + nextQuote(), CharsetUtil.UTF_8), packet
                    .sender()));
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
        cause.printStackTrace();
    }
}

服务端:

package udp.server;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
/**
 * @Author 18011618
 * @Description UDP服务端接受客户端
 * @Date 17:01 2018/6/25
 * @Modify By
 */
public class UDPServer {
    public void run(int port) throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioDatagramChannel.class)
                    .option(ChannelOption.SO_BROADCAST, true)
                    .handler(new UDPServerHandler());
            b.bind(port).sync().channel().closeFuture().await();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            try {
                port = Integer.parseInt(args[0]);
            } catch (NumberFormatException e) {
                e.printStackTrace();
            }
        }
        new UDPServer().run(port);
    }
}

看一下效果:


每次返回的都是不一样的.

3 基于netty完成自定义一种协议开发:客户端的请求和服务端的响应以及json协议进行传输

虽然netty提供常用的协议给开发者使用,但如果想要开发自己的协议是否可以呢?答案是肯定,现在很多RPC框架在数据传输都会使用json代替了传统的xml文件传输方式,既然这个传输协议用的很多,接下来就实现一个这样的协议开发:下面首先分析要实现这样功能的步骤

& 自定义request编码器

&自定义reqeust解码器

&自定义response编码器

&自定义response解码器

&自定义json数据转换工具类

&自定义request的handler处理逻辑

&自定义response的handler处理逻辑

&request处理的client

&response处理的server

上面基本是就是一些核心步骤,按照这个步骤一步步来实现即可:整体看一下项目结构:


下面看具体的代码实现:首先定义请求和响应的javabean:

package com.netty.http.json.bean.http;

import io.netty.handler.codec.http.FullHttpRequest;

/**
 * Created by jack on 2018/5/4.
 * 封装请求参数
 */
public class HttpJsonRequest {
    private FullHttpRequest request;
    private Object body;

    public Object getBody() {
        return this.body;
    }

    public void setBody(Object body) {
        this.body = body;
    }

    public FullHttpRequest getRequest() {
        return this.request;
    }

    public void setRequest(FullHttpRequest request) {
        this.request = request;
    }

    public HttpJsonRequest(FullHttpRequest request, Object body) {
        this.request = request;
        this.body = body;
    }

    @Override
    public String toString() {
        return "HttpJsonRequest [request=" + request + ", body =" + body + "]";
    }
}
package com.netty.http.json.bean.http;

import io.netty.handler.codec.http.FullHttpResponse;

/**
 * Created by jack on 2018/5/4.
 * 封装响应结果
 */
public class HttpJsonResponse {
    private FullHttpResponse httpResponse;
    private Object result;

    public HttpJsonResponse(FullHttpResponse httpResponse, Object result) {
        this.httpResponse = httpResponse;
        this.result = result;
    }

    public FullHttpResponse getHttpResponse() {
        return this.httpResponse;
    }

    public void setHttpResponse(FullHttpResponse httpResponse) {
        this.httpResponse = httpResponse;
    }

    public Object getResult() {
        return this.result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
    @Override
    public String toString() {
        return "HttpJsonResponse [httpResponse=" + httpResponse + ", result="
                + result + "]";
    }
}
 
 

下面是一个工具类,主要提供两个方法,对象转换为Json和json转换为对象:在编解码器会用到

package com.netty.http.json.util;

import com.alibaba.fastjson.JSONObject;

/**
 * Created by jack on 2018/5/4.
 * 封装json工具类
 */
public class FastJsonUtil {

    /**
     * jsonstr to javabean
     * @param jsonString
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T parseObject(String jsonString,Class clazz){
        return  (T)JSONObject.parseObject(jsonString, clazz);
    }

    /**
     * javabean to jsonstr
     * @param t
     * @param <T>
     * @return
     */
    public static <T>  String parseJsonStr(T t){
        return JSONObject.toJSONString(t);
    }
}
 
 

下面先看两个抽象的类,分别是抽象解码器和抽象编码器:

解码器父类:

息对象转换为消息对象
package com.netty.http.json.codec.decode;

import com.netty.http.json.bean.Order;
import com.netty.http.json.util.FastJsonUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;

import java.nio.charset.Charset;

/**
 * Created by jack on 2018/5/4.
 * 解码 
 */
public abstract class AbstractHttpJsonDecoder<T> extends MessageToMessageDecoder<T> {
    private Class<?> clazz;
    private boolean isPrint;
    private final static Charset UTF_8 = Charset.forName("UTF-8");

    protected AbstractHttpJsonDecoder(Class<?> clazz) {
        this(clazz, false);
    }

    protected AbstractHttpJsonDecoder(Class<?> clazz, boolean isPrint) {
        this.clazz = clazz;
        this.isPrint = isPrint;
    }

    /**
     * 字符串转换为java对象
     * @param context
     * @param body
     * @return
     */
    protected Object decode(ChannelHandlerContext context, ByteBuf body){
        String content = body.toString(UTF_8);
        if (isPrint)
            System.out.println("this body is:"+content);        
        Object result = FastJsonUtil.parseObject(content, Order.class);
        return result;
    }

}

父类编码器:对象转换为bytebuf

package com.netty.http.json.codec.encode;

import com.netty.http.json.util.FastJsonUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;

import java.nio.charset.Charset;

/**
 * Created by jack on 2018/5/4.
 * 编码
 */
public abstract class AbstractHttpJsonEncoder<T> extends MessageToMessageEncoder<T> {
    public final static Charset charset = Charset.forName("utf-8");
    /**
     * 对象转换为bytebuf
     * @param context
     * @param body
     */
    protected ByteBuf encode (ChannelHandlerContext context,Object body){
        String str = FastJsonUtil.parseJsonStr(body);
        ByteBuf byteBuf = Unpooled.copiedBuffer(str,charset);
        return byteBuf;
    }
}
 
 

下面看一下request对应的解码器和编码器的具体实现:

解码器:

package com.netty.http.json.codec.decode;

import com.netty.http.json.bean.http.HttpJsonRequest;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.CharsetUtil;

import java.util.List;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Created by jack on 2018/5/4.
 * 服务端的解码
 * 字符串转换为对象
 */
public class HttpJsonRequestDecoder extends AbstractHttpJsonDecoder<FullHttpRequest> {

    /**
     * 解码
     * @param ctx
     * @param msg
     * @param out
     * @throws Exception
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, FullHttpRequest msg, List<Object> out) throws Exception {
        if (!msg.decoderResult().isSuccess()){
            sendError(ctx, HttpResponseStatus.BAD_REQUEST);
            return;
        }
        HttpJsonRequest request = new HttpJsonRequest(msg,decode(ctx,msg.content()));
        out.add(request);
    }

    public HttpJsonRequestDecoder(Class<?> clazz) {
        this(clazz, false);
    }

    /**
     * 构造器
     *
     * @param clazz   解码的对象信息
     * @param isPrint 是否需要打印
     */
    public HttpJsonRequestDecoder(Class<?> clazz, boolean isPrint) {
        super(clazz, isPrint);
    }

    /**
     * 测试的话,直接封装,实战中需要更健壮的处理
     */
    private static void sendError(ChannelHandlerContext ctx,
                                  HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                status, Unpooled.copiedBuffer("Failure: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}
 
 

编码器:

package com.netty.http.json.codec.encode;

import com.netty.http.json.bean.http.HttpJsonRequest;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.*;

import java.net.InetAddress;
import java.util.List;

/**
 * Created by jack on 2018/5/4.
 * 编码
 * 客户端编码
 * java对象转换为字节流
 */
public class HttpJsonRequestEncoder extends AbstractHttpJsonEncoder<HttpJsonRequest> {
    @Override
    protected void encode(ChannelHandlerContext ctx, HttpJsonRequest msg, List<Object> out) throws Exception {
        ByteBuf body = encode(ctx,msg.getBody());
        //如果业务自定义http消息头,就直接使用,否则就创建一个
        FullHttpRequest request = msg.getRequest();
        if (request ==null){

            request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/do", body);
            //设置httpheaders  实际应该是通过读取配置文件来加载的
            HttpHeaders headers = request.headers();
            //设置host
            headers.set(HttpHeaderNames.HOST, InetAddress.getLocalHost().getHostAddress());
            //设置connection
            headers.set(HttpHeaderNames.CONNECTION, HttpHeaders.Values.CLOSE);
            //设置编码
            headers.set(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP.toString() + ',' + HttpHeaderValues.DEFLATE.toString());
            //设置字符格式
            headers.set(HttpHeaderNames.ACCEPT_CHARSET, "ISO-8859-1,utf-8;q=0.7,*;q=0.7");
            //设置语言
            headers.set(HttpHeaderNames.ACCEPT_LANGUAGE, "zh");
            //设置客户端
            headers.set(HttpHeaderNames.USER_AGENT, "Netty json Http Client side");
            //设置接收数据的格式
            headers.set(HttpHeaderNames.ACCEPT, "text/html,application/json;q=0.9,*/*;q=0.8");
        }
        HttpUtil.setContentLength(request,body.readableBytes());
        out.add(request);
    }
}

reponse端的解码器和编码器:

解码器:

package com.netty.http.json.codec.decode;

import com.netty.http.json.bean.http.HttpJsonResponse;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.FullHttpResponse;

import java.util.List;

/**
 * Created by jack on 2018/5/4.
 * 解码
 * 客户端解码
 * 字节流转换javabean
 */
public class HttpJsonResponseDecoder extends AbstractHttpJsonDecoder<FullHttpResponse> {
    public HttpJsonResponseDecoder(Class<?> clazz) {
        this(clazz, false);
    }

    /**
     * 构造器
     *
     * @param clazz   解码的对象信息
     * @param isPrint 是否需要打印
     */
    public HttpJsonResponseDecoder(Class<?> clazz, boolean isPrint) {
        super(clazz, isPrint);
    }

    @Override
    protected void decode(ChannelHandlerContext ctx, FullHttpResponse msg, List<Object> out) throws Exception {
        System.out.println("begin decode value....");
        HttpJsonResponse response = new HttpJsonResponse(msg,decode(ctx,msg.content()));
        out.add(response);
    }
}
 
 

编码器:

package com.netty.http.json.codec.encode;

import com.netty.http.json.bean.http.HttpJsonResponse;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpUtil;

import java.util.List;

import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Created by jack on 2018/5/4.
 * 编码
 * 服务端编码
 * java对象转换为字符串
 */
public class HttpJsonResponseEncoder extends AbstractHttpJsonEncoder<HttpJsonResponse>  {
    @Override
    protected void encode(ChannelHandlerContext ctx, HttpJsonResponse msg, List<Object> out) throws Exception {
        //获取服务端的字节流
        ByteBuf body = encode(ctx, msg.getResult());
        //创建一个服务端响应对象
        FullHttpResponse response = msg.getHttpResponse();
        if (response == null) {
            //实例化一个
            response = new DefaultFullHttpResponse(HTTP_1_1, OK, body);
        } else {
            response = new DefaultFullHttpResponse(msg.getHttpResponse()
                    .protocolVersion(), msg.getHttpResponse().status(),
                    body);
        }
        //设置内容格式为json
        response.headers().set(CONTENT_TYPE, "text/json");
        //设置内容的长度
        HttpUtil.setContentLength(response, body.readableBytes());
        //添加到结果中
        out.add(response);
    }
}

在看客户端的业务处理handler:

package com.netty.http.json.handler;

import com.netty.http.json.bean.http.HttpJsonRequest;
import com.netty.http.json.factory.OrderFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 * Created by jack on 2018/5/4.
 * 客户端处理业务的handler
 */
public class HttpJsonClientHandler extends ChannelInboundHandlerAdapter {

    /**
     * 客户端连接到服务端的时候 开始发送数据
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("连接上服务器:"+ctx.channel().remoteAddress());
        HttpJsonRequest request = new HttpJsonRequest(null, OrderFactory.createOrder(1001));
        ctx.writeAndFlush(request);
    }

    /**
     * 接收到服务端返回回来的数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(msg.getClass().getSimpleName());
        System.out.println("客户端接收到服务端返回的数据:"+msg.toString());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
 
 

服务端处理业务的Handler:

package com.netty.http.json.handler;

import com.netty.http.json.bean.Order;
import com.netty.http.json.bean.http.HttpJsonRequest;
import com.netty.http.json.bean.http.HttpJsonResponse;
import com.netty.http.json.factory.OrderFactory;
import io.netty.buffer.Unpooled;
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.util.CharsetUtil;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;

import static io.netty.handler.codec.http.HttpHeaders.Names.CONTENT_TYPE;
import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;

/**
 * Created by jack on 2018/5/4.
 * 服务端处理业务的handler
 */
public class HttpJsonServerHandler extends SimpleChannelInboundHandler<HttpJsonRequest> {

    /**
     * 服务端读取客户端的数据并修改获取到的数据
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(final ChannelHandlerContext ctx, HttpJsonRequest msg) throws Exception {
       //读取客户端的数据
        HttpRequest request = msg.getRequest();
        Order order = (Order)msg.getBody();
        System.out.println("Http server receive request : " + order);
        //修改客户端的数据
        OrderFactory.setOrder(order);
        //返回给客户端
        ChannelFuture future = ctx.writeAndFlush(new HttpJsonResponse(null,order));
        //后面需要仔细研读
        if (!HttpUtil.isKeepAlive(request)) {
            future.addListener(new GenericFutureListener<Future<? super Void>>() {
                public void operationComplete(Future future) throws Exception {
                    ctx.close();
                }
            });
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        if (ctx.channel().isActive()) {
            sendError(ctx, INTERNAL_SERVER_ERROR);
        }
    }

    private static void sendError(ChannelHandlerContext ctx,
                                  HttpResponseStatus status) {
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1,
                status, Unpooled.copiedBuffer("失败: " + status.toString()
                + "\r\n", CharsetUtil.UTF_8));
        response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }
}

辅助订单生产的工具类:

package com.netty.http.json.factory;

import com.netty.http.json.bean.Order;

/**
 * Created by jack on 2018/5/4.
 */
public class OrderFactory {

    /**
     * 实例化一个order
     * @param value
     * @return
     */
    public static Order createOrder(long value){
        Order order = new Order();
        order.setOrderNumber(value);
        order.setBillTo("南京");
        order.setShipping("国内快递");
        order.setShipTo("上海");
        order.setTotal(120);
        order.setCustomer("贾红平");
        return order;
    }

    /**
     * 修改一个order
     * @param order
     */
    public static void setOrder(Order order){
        order.setBillTo("北京");
        order.setShipping("国内快递");
        order.setShipTo("天津");
        order.setTotal(120);
        order.setCustomer("姚杰");
    }
}
 
 

初始化客户端的channelhander:

package com.netty.http.json.Initializer;

import com.netty.http.json.bean.Order;
import com.netty.http.json.codec.decode.HttpJsonRequestDecoder;
import com.netty.http.json.codec.decode.HttpJsonResponseDecoder;
import com.netty.http.json.codec.encode.HttpJsonRequestEncoder;
import com.netty.http.json.handler.HttpJsonClientHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpRequestEncoder;
import io.netty.handler.codec.http.HttpResponseDecoder;


/**
 * Created by jack on 2018/5/4.
 * 客户端初始化各种编解码器和具体的handlerchannelpiple
 */
public class ClientInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        //设置当前的解码器是基于http
        ch.pipeline().addLast(new HttpResponseDecoder());
        ch.pipeline().addLast(new HttpObjectAggregator(65536));
        //设置自定义解码器
        ch.pipeline().addLast(new HttpJsonResponseDecoder(Order.class,true));
        //设置当前的编码器是基于http
        ch.pipeline().addLast(new HttpRequestEncoder());
        //设置自定义编码器
        ch.pipeline().addLast(new HttpJsonRequestEncoder());
        //设置业务处理的Handler
        ch.pipeline().addLast(new HttpJsonClientHandler());

    }
}
 
 

服务端处初始化channelhander:

package com.netty.http.json.Initializer;

import com.netty.http.json.bean.Order;
import com.netty.http.json.codec.decode.HttpJsonRequestDecoder;
import com.netty.http.json.codec.encode.HttpJsonResponseEncoder;
import com.netty.http.json.handler.HttpJsonServerHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;

/**
 * Created by jack on 2018/5/4.
 * 服务端初始化各种编解码器和具体的handlerchannelpiple
 */
public class ServerInitializer  extends ChannelInitializer<SocketChannel>{
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(new HttpRequestDecoder());
        ch.pipeline().addLast(new HttpObjectAggregator(65536));
        ch.pipeline().addLast(new HttpJsonRequestDecoder(Order.class,true));

        ch.pipeline().addLast(new HttpResponseEncoder());
        ch.pipeline().addLast(new HttpJsonResponseEncoder());
        ch.pipeline().addLast(new HttpJsonServerHandler());
    }
}
 
 

定义client:

package com.netty.http.json.client;

import com.netty.http.json.Initializer.ClientInitializer;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * Created by jack on 2018/5/4.
 * 客户端工作
 */
public class HttpJsonClient {
    static EventLoopGroup group;
    static Bootstrap client;

    static {
        group = new NioEventLoopGroup();
        client = new Bootstrap();
        client.group(group).channel(NioSocketChannel.class);
        client.option(ChannelOption.SO_SNDBUF,128);
        client.option(ChannelOption.TCP_NODELAY,false);
        client.handler(new ClientInitializer());
    }

    /**
     * 启动客户端
     * @param port
     */
    public static  void connect(int port){
        try {
            ChannelFuture future = client.connect(new InetSocketAddress(port)).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        connect(8080);
    }
}
 
 

定义服务端:

package com.netty.http.json.server;
import com.netty.http.json.Initializer.ServerInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;


import java.net.InetSocketAddress;
/**
 * Created by jack on 2018/5/4.
 * 创建服务端
 */
@SuppressWarnings("all")
public class HttpJsonServer {

    static EventLoopGroup bossLoopGroup;//主线程
    static EventLoopGroup workLoopGroup;//从线程
    static ServerBootstrap serverBootstrap;//服务

    static {
        bossLoopGroup = new NioEventLoopGroup();
        workLoopGroup = new NioEventLoopGroup();
        serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossLoopGroup,workLoopGroup);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.option(ChannelOption.SO_BACKLOG,128);
        serverBootstrap.option(ChannelOption.SO_SNDBUF,128);
        serverBootstrap.option(ChannelOption.SO_KEEPALIVE,true);
        serverBootstrap.childHandler(new ServerInitializer());

    }

    /**
     * 启动服务器
     * @param port
     */
    public static void run(int port){
        try {
            ChannelFuture future = serverBootstrap.bind(new InetSocketAddress(port)).sync();
            System.out.println("HTTP订购服务器启动,网址是 : " + "http://localhost:" + port);
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossLoopGroup.shutdownGracefully();
            workLoopGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        run(8080);
    }
}

ok 到此为止代码功能就全部完成了,让我们先看一下效果再做总结:

服务端接受到客户端的数据是基于对象的:


客户端接受到服务端的数据是以json格式的:


这样就实现了基于Json+自定义编解码的功能的一个协议.

猜你喜欢

转载自blog.csdn.net/qq_18603599/article/details/80768409