Netty實戰一 Netty實現文件的上傳和下載

目錄

 

一、Netty應用場景

二、Netty實現文件的上傳和下載

三、程序演示

1、下載演示

2、上傳演示


一、Netty應用場景

講了一些Netty的組件,來聊一聊大家最關心的事情吧,他能夠做什麼?畢竟,我們學習就是拿來用的嘛。我可以簡單的概括一下,凡是牽扯到網絡相關的,都可以使用Neety去實現!

  1. 構建高性能、低時延的各種 Java 中間件,例如 MQ、分佈式服務框架、ESB 消息總線等,Netty 主要作爲基礎通信框架提供高性能、低時延的通信服務;
  2. 公有或者私有協議棧的基礎通信框架,例如可以基於 Netty 構建異步、高性能的 WebSocket 協議棧;
  3. 各領域應用,例如大數據、遊戲等,Netty 作爲高性能的通信框架用於內部各模塊的數據分發、傳輸和彙總等,實現模塊之間高性能通信。

接下來的幾篇,會圍繞Netty實現相關功能進行展開。

二、Netty實現文件的上傳和下載

 

1、MultipartRequest


import io.netty.handler.codec.http.multipart.FileUpload;
import org.json.simple.JSONObject;

import java.util.Map;

/**
 * <p>請求對象</p>
 *
 * @author DarkKing 
 */
public class MultipartRequest {
    private Map<String, FileUpload> fileUploads;
    private JSONObject params;

    public Map<String, FileUpload> getFileUploads() {
        return fileUploads;
    }

    public void setFileUploads(Map<String, FileUpload> fileUploads) {
        this.fileUploads = fileUploads;
    }

    public JSONObject getParams() {
        return params;
    }

    public void setParams(JSONObject params) {
        this.params = params;
    }
}

定義了一個http封裝的對象。保存對應的傳參數。

2、FileServer 



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;

import java.net.InetSocketAddress;

/**
 * 作者:DarkKIng
 * 創建日期:2019/12/17
 * 類說明:文件下載服務端
 */
public class FileServer {

    private final int port;

    public FileServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 9999;
        FileServer fileServer = new FileServer(port);
        System.out.println("服務器即將啓動");
        fileServer.start();
        System.out.println("服務器關閉");
    }

    public void start() throws InterruptedException {
        final FileServerHandle serverHandler = new FileServerHandle();
        /*線程組*/
        EventLoopGroup group = new NioEventLoopGroup();
        Pipeline pipeline = new Pipeline();
        try {
            /*服務端啓動必須*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)/*將線程組傳入*/
                    .channel(NioServerSocketChannel.class)/*指定使用NIO進行網絡傳輸*/
                    .localAddress(new InetSocketAddress(port))/*指定服務器監聽端口*/
                    /*服務端每接收到一個連接請求,就會新啓一個socket通信,也就是channel,
                    所以下面這段代碼的作用就是爲這個子channel增加handle*/
                    .childHandler(pipeline);
            ChannelFuture f = b.bind().sync();/*異步綁定到服務器,sync()會阻塞直到完成*/
            System.out.println("Netty server  start,port is " + port);
            f.channel().closeFuture().sync();/*阻塞直到服務器的channel關閉*/

        } finally {
            group.shutdownGracefully().sync();/*優雅關閉線程組*/
        }

    }


}

 

使用netty實現服文件服務器端。

3、Pipeline


import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;

/**
 * 作者:DarkKIng
 * 創建日期:2019/12/17
 * 作用:職責鏈
 */
public class Pipeline extends ChannelInitializer<SocketChannel> {


    private EventExecutorGroup businessEventExecutorGroup = new DefaultEventExecutorGroup(10);


    @Override
    protected void initChannel(SocketChannel ch) {

        ChannelPipeline pipeline = ch.pipeline();
        /**
         * http服務器端對response編碼
         */
        pipeline.addLast("encoder", new HttpResponseEncoder());

        /**
         * http服務器端對request解碼3.
         */
        pipeline.addLast("decoder", new HttpRequestDecoder());

        /**
         * 合併請求
         */
        pipeline.addLast("aggregator", new HttpObjectAggregator(655300000));

        /**
         * 正常業務邏輯處理
         */
        pipeline.addLast(businessEventExecutorGroup, new FileServerHandle());
    }

}

編寫職責鏈,請求會從入棧以次從上到下經過編解碼,請求和秉承HTTPObject,最後執行業務類FileServerHandle

4、FileServerHandle



import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.*;
import io.netty.util.CharsetUtil;
import org.json.simple.JSONObject;

import java.io.*;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * 作者:DarkKIng
 * 創建日期:2019/12/17
 * 類說明:文件下載handler
 */
@Sharable
public class FileServerHandle extends SimpleChannelInboundHandler<FullHttpRequest> {

    /*客戶端讀到數據以後,就會執行*/
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request)
            throws Exception {
        //打印請求url
        System.out.println(request.uri());
        //下載任務處理
        if (request.uri().equals("/downFile")) {
            responseExportFile(ctx, "D://model.txt", "model.txt");
        }
        //上傳接口處理
        if (request.uri().equals("/upLoadFile")) {
            MultipartRequest MultipartBody = getMultipartBody(request);
            Map<String, FileUpload> fileUploads = MultipartBody.getFileUploads();
            //輸出文件信息
            for (String key : fileUploads.keySet()) {
                //獲取文件對象
                FileUpload file = fileUploads.get(key);
                System.out.println("fileName is" + file.getFile().getPath());
                //獲取文件流
                InputStream in = new FileInputStream(file.getFile());
                BufferedReader bf = new BufferedReader(new InputStreamReader(in));
                String content = bf.lines().collect(Collectors.joining("\n"));
                //打印文件
                System.out.println("content is \n" + content);
            }
            //輸出參數信息
            JSONObject params = MultipartBody.getParams();
            //輸出文件信息
            System.out.println(JSONObject.toJSONString(params));

        }
    }

    /*連接建立以後*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(
                "Hello Netty", CharsetUtil.UTF_8));
    }

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

    /**
     * <p>
     * 返回下載內容
     * </p>
     *
     * @param ctx
     * @author DarkKing 2019-12-17
     */
    public static void responseExportFile(ChannelHandlerContext ctx, String path, String name) {
        File file = new File(path);
        try {
            //隨機讀取文件
            final RandomAccessFile raf = new RandomAccessFile(file, "r");
            long fileLength = raf.length();
            //定義response對象
            HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            //設置請求頭部
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, fileLength);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/octet-stream; charset=UTF-8");
            response.headers().add(HttpHeaderNames.CONTENT_DISPOSITION,
                    "attachment; filename=\"" + URLEncoder.encode(file.getName(), "UTF-8") + "\";");
            ctx.write(response);
            //設置事件通知對象
            ChannelFuture sendFileFuture = ctx
                    .write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise());
            sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
                //文件傳輸完成執行監聽器
                @Override
                public void operationComplete(ChannelProgressiveFuture future)
                        throws Exception {
                    System.out.println("file {} transfer complete.");
                }

                //文件傳輸進度監聽器
                @Override
                public void operationProgressed(ChannelProgressiveFuture future,
                                                long progress, long total) throws Exception {

                    if (total < 0) {
                        System.out.println("file {} transfer progress: {}");
                    } else {
                        System.out.println("file {} transfer progress: {}/{}");
                    }
                }
            });
            //刷新緩衝區數據,文件結束標誌符
            ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 功能描述
     * <p>解析文件上傳</p>
     *
     * @author DarkKing 2019/10/9 15:24
     * @params [ctx, httpDecode]
     */
    private static MultipartRequest getMultipartBody(FullHttpRequest request) {
        try {
            //創建HTTP對象工廠
            HttpDataFactory factory = new DefaultHttpDataFactory(true);
            //使用HTTP POST解碼器
            HttpPostRequestDecoder httpDecoder = new HttpPostRequestDecoder(factory, request);
            httpDecoder.setDiscardThreshold(0);
            if (httpDecoder != null) {
                //獲取HTTP請求對象
                final HttpContent chunk = (HttpContent) request;
                //加載對象到加嗎器。
                httpDecoder.offer(chunk);
                if (chunk instanceof LastHttpContent) {
                    //自定義對象bean
                    MultipartRequest multipartRequest = new MultipartRequest();
                    //存放文件對象
                    Map<String, FileUpload> fileUploads = new HashMap<>();
                    //存放參數對象
                    JSONObject body = new JSONObject();
                    //通過迭代器獲取HTTP的內容
                    java.util.List<InterfaceHttpData> InterfaceHttpDataList = httpDecoder.getBodyHttpDatas();
                    for (InterfaceHttpData data : InterfaceHttpDataList) {
                        //如果數據類型爲文件類型,則保存到fileUploads對象中
                        if (data != null && InterfaceHttpData.HttpDataType.FileUpload.equals(data.getHttpDataType())) {
                            FileUpload fileUpload = (FileUpload) data;
                            fileUploads.put(data.getName(), fileUpload);
                        }
                        //如果數據類型爲參數類型,則保存到body對象中
                        if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                            Attribute attribute = (Attribute) data;
                            body.put(attribute.getName(), attribute.getValue());
                        }
                    }
                    //存放文件信息
                    multipartRequest.setFileUploads(fileUploads);
                    //存放參數信息
                    multipartRequest.setParams(body);

                    return multipartRequest;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}


業務執行類,實現了文件上傳和下載的接口。當請求爲downFile則下載文件,請求爲upLoadFile則爲上傳文件

三、程序演示

1、下載演示

啓動服務器端

瀏覽器執行下載文件,正確的下載到文件test.txt

2、上傳演示

使用apipost或者postman執行文件上傳操作

文件上傳成功併成功讀取文件內容

本博文主要演示瞭如何不使用spring或者tomcat當做服務器,使用netty實現自己的文件上傳和下載服務。並根據請求來實現對應的api接口操作。當然,如果想使用netty像spring那樣簡單並規範化的封裝自己的api,那麼就要考自己去封裝實現了。有興趣的朋友可以自己嘗試下

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章