Netty整合Http與WebSocket的Demo 入門

Netty我就不多說了,是什麼能看到這篇文章的都很清楚

網上很多文章直接黏貼複製的不說,還基本沒辦法拿出來當個例子走一遍。

我這版雖然也是照着能用的修修改改,但最起碼保證能用,而且註釋很詳細

話不多說,直接搞重點。

我的需求是什麼:

用Netty搭建一個項目,能接到Http、WebSocket請求,處理它,返回它。

請求類型eg:

http://www.anyongliang.cn:8888/Organize/login?user=root&pwd=123456

ws://www.anyongliang.cn:8888/ws

實現需求我需要什麼:

電腦

Jdk1.8(推薦)

IDE(推薦idea,我也用idea做演示)

jar管理(推薦gradle,我也用gradle做演示)

開始實現:

我的註釋很多,就不一一細寫了,沒什麼卵用,demo搭起來,走個幾遍,打幾個斷點,什麼都懂了。

個別不懂的針對類去搜,實在不行去官網譯本:netty 4 官網譯文

1:創建項目,並添加依賴。

項目核心結構如圖:

依賴:

testCompile group: 'junit', name: 'junit', version: '4.12'
    //netty-4
    compile group: 'io.netty', name: 'netty-all', version: '4.1.19.Final'
    //mongo-Bson
    compile group: 'org.mongodb', name: 'mongo-java-driver', version: '3.8.2'
    //google-Gson
    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.5'
    //apache-commons工具包
    compile group: 'org.apache.commons', name: 'commons-dbcp2', version: '2.1.1'
    compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.6'
    compile group: 'commons-io', name: 'commons-io', version: '2.5'
    compile group: 'commons-codec', name: 'commons-codec', version: '1.10'
    //日誌-slf4j
    compile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'

核心代碼:

1:解碼器,用來解析請求

package cn.ayl.socket.decoder;

import cn.ayl.config.Const;
import cn.ayl.socket.handler.HeartBeatHandler;
import cn.ayl.socket.handler.HttpAndWebSocketHandler;
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.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * created by Rock-Ayl 2019-11-6
 * WebSocket請求的解碼器,一個請求需要先從這裏走,實現init
 */
public class HttpAndWebSocketDecoder extends ChannelInitializer<SocketChannel> {

    protected static Logger logger = LoggerFactory.getLogger(HttpAndWebSocketDecoder.class);

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        logger.info("解析請求.");
        ChannelPipeline pipeline = ch.pipeline();
        //http解碼
        initHttpChannel(pipeline);
        //心跳檢測
        initHeartBeat(pipeline);
        //基於http的WebSocket
        initWebSocket(pipeline);
        //處理器
        pipeline.addLast(new HttpAndWebSocketHandler());
    }

    //Http部分
    private void initHttpChannel(ChannelPipeline pipeline) throws Exception {
        //http解碼器(webSocket是http的升級)
        pipeline.addLast(new HttpServerCodec());
        //以塊的方式來寫的處理器,解決大碼流的問題,ChunkedWriteHandler:可以向客戶端發送HTML5文件
        pipeline.addLast(new ChunkedWriteHandler());
        //netty是基於分段請求的,HttpObjectAggregator的作用是將HTTP消息的多個部分合成一條完整的HTTP消息,參數是聚合字節的最大長度
        pipeline.addLast(new HttpObjectAggregator(Const.MaxContentLength));
    }

    //心跳部分
    private void initHeartBeat(ChannelPipeline pipeline) throws Exception {
        // 針對客戶端,如果在1分鐘時沒有向服務端發送讀寫心跳(ALL),則主動斷開,如果是讀空閒或者寫空閒,不處理
        pipeline.addLast(new IdleStateHandler(Const.ReaderIdleTimeSeconds, Const.WriterIdleTimeSeconds, Const.AllIdleTimeSeconds));
        // 自定義的空閒狀態檢測
        pipeline.addLast(new HeartBeatHandler());
    }

    //WebSocket部分
    private void initWebSocket(ChannelPipeline pipeline) throws Exception {
        /**
         * WebSocketServerProtocolHandler負責websocket握手以及處理控制框架(Close,Ping(心跳檢檢測request),Pong(心跳檢測響應))。
         * 參數爲ws請求的訪問路徑 eg:ws://127.0.0.1:8888/WebSocket。
         */
        pipeline.addLast(new WebSocketServerProtocolHandler(Const.WebSocketPath));
    }

}

2:處理器,一共兩個,一個用來控制心跳,一個用來分發請求並處理

package cn.ayl.socket.handler;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * created by Rock-Ayl 2019-11-17
 * WebSocket心跳處理程序
 */
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    protected static Logger logger = LoggerFactory.getLogger(HeartBeatHandler.class);

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 判斷evt是否是 IdleStateEvent(超時的事件,用於觸發用戶事件,包含讀空閒/寫空閒/讀寫空閒 )
        if (msg instanceof IdleStateEvent) {
            // 強制類型轉換
            IdleStateEvent event = (IdleStateEvent) msg;
            switch (event.state()) {
                case READER_IDLE:
                    logger.info("進入讀空閒...");
                    break;
                case WRITER_IDLE:
                    logger.info("進入寫空閒...");
                    break;
                case ALL_IDLE:
                    logger.info("開始殺死無用通道,節約資源");
                    Channel channel = ctx.channel();
                    channel.close();
                    break;
            }
        }

    }

}
package cn.ayl.socket.handler;

import cn.ayl.json.JsonObject;
import cn.ayl.json.JsonUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static io.netty.buffer.Unpooled.copiedBuffer;

/**
 * created by Rock-Ayl on 2019-11-7
 * Http請求和WebSocket請求的處理程序
 */
public class HttpAndWebSocketHandler extends ChannelInboundHandlerAdapter {

    protected static Logger logger = LoggerFactory.getLogger(HttpAndWebSocketHandler.class);

    private WebSocketServerHandshaker webSocketServerHandshaker;

    /**
     * 通道,請求過來從這裏分類
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //處理Http請求和WebSocket請求的分別處理
        if (msg instanceof FullHttpRequest) {
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof HttpContent) {
            //todo handleHttpContent
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketRequest(ctx, (WebSocketFrame) msg);
        }
    }

    /**
     * 每個channel都有一個唯一的id值
     * asLongText方法是channel的id的全名
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        //todo 連接打開時
        logger.info(ctx.channel().localAddress().toString() + " handlerAdded!, channelId=" + ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        //todo 連接關閉時
        logger.info(ctx.channel().localAddress().toString() + " handlerRemoved!, channelId=" + ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //todo 出現異常
        logger.error("Client:" + ctx.channel().remoteAddress() + "error", cause.getMessage());
        ctx.close();
    }

    // 處理Websocket的代碼
    private void handleWebSocketRequest(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判斷是否是關閉鏈路的指令
        if (frame instanceof CloseWebSocketFrame) {
            webSocketServerHandshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return;
        }
        // 判斷是否是Ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 文本消息,不支持二進制消息
        if (frame instanceof TextWebSocketFrame) {
            //請求text
            String request = ((TextWebSocketFrame) frame).text();
            logger.info("收到信息:" + request);
            //返回
            ctx.channel().writeAndFlush(new TextWebSocketFrame(JsonObject.Success().append("req", request).toString()));
        }
    }

    /**
     * 處理業務
     *
     * @param path   eg:  /Organize/login
     * @param params eg:  user:root pwd:123456
     * @return
     */
    private JsonObject handleServiceFactory(String path, Map<String, Object> params) {
        //todo 根據path和params處理業務並返回
        return JsonObject.Success();
    }

    private void handleHttpRequest(final ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
        //todo http請求內容分類進行細化,目前設定爲全部爲服務請求(可以存在頁面,資源,上傳等等)
        handleService(ctx, req);
    }

    //處理http服務請求
    private void handleService(ChannelHandlerContext ctx, FullHttpRequest req) {
        FullHttpResponse response;
        JsonObject result;
        //獲得請求path
        String path = getPath(req);
        //根據請求類型處理請求 get post ...
        if (req.method() == HttpMethod.GET) {
            //獲取請求參數
            Map<String, Object> params = getGetParamsFromChannel(req);
            //業務
            result = handleServiceFactory(path, params);
            response = responseOKAndJson(HttpResponseStatus.OK, result);
        } else if (req.method() == HttpMethod.POST) {
            //獲取請求參數
            Map<String, Object> params = getPostParamsFromChannel(req);
            //處理業務
            result = handleServiceFactory(path, params);
            response = responseOKAndJson(HttpResponseStatus.OK, result);
        } else {
            //todo 處理其他類型的請求
            response = responseOKAndJson(HttpResponseStatus.INTERNAL_SERVER_ERROR, null);
        }
        // 發送響應並關閉連接
        ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * Http獲取請求Path
     *
     * @param req
     * @return
     */
    private String getPath(FullHttpRequest req) {
        String path = null;
        try {
            path = new URI(req.getUri()).getPath();
        } catch (Exception e) {
            logger.error("接口解析錯誤.");
        } finally {
            return path;
        }
    }

    /**
     * Http獲取GET方式傳遞的參數
     *
     * @param fullHttpRequest
     * @return
     */
    private Map<String, Object> getGetParamsFromChannel(FullHttpRequest fullHttpRequest) {
        //參數組
        Map<String, Object> params = new HashMap<>();
        //如果請求爲GET繼續
        if (fullHttpRequest.method() == HttpMethod.GET) {
            // 處理get請求
            QueryStringDecoder decoder = new QueryStringDecoder(fullHttpRequest.uri());
            Map<String, List<String>> paramList = decoder.parameters();
            for (Map.Entry<String, List<String>> entry : paramList.entrySet()) {
                params.put(entry.getKey(), entry.getValue().get(0));
            }
            return params;
        } else {
            return null;
        }

    }

    /**
     * Http獲取POST方式傳遞的參數
     *
     * @param fullHttpRequest
     * @return
     */
    private Map<String, Object> getPostParamsFromChannel(FullHttpRequest fullHttpRequest) {
        //參數組
        Map<String, Object> params;
        //如果請求爲POST
        if (fullHttpRequest.method() == HttpMethod.POST) {
            // 處理POST請求
            String strContentType = fullHttpRequest.headers().get("Content-Type").trim();
            if (strContentType.contains("x-www-form-urlencoded")) {
                params = getFormParams(fullHttpRequest);
            } else if (strContentType.contains("application/json")) {
                try {
                    params = getJSONParams(fullHttpRequest);
                } catch (UnsupportedEncodingException e) {
                    return null;
                }
            } else {
                return null;
            }
            return params;
        } else {
            return null;
        }
    }

    /**
     * Http解析from表單數據(Content-Type = x-www-form-urlencoded)
     *
     * @param fullHttpRequest
     * @return
     */
    private Map<String, Object> getFormParams(FullHttpRequest fullHttpRequest) {
        Map<String, Object> params = new HashMap<>();
        HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(new DefaultHttpDataFactory(false), fullHttpRequest);
        List<InterfaceHttpData> postData = decoder.getBodyHttpDatas();
        for (InterfaceHttpData data : postData) {
            if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) {
                MemoryAttribute attribute = (MemoryAttribute) data;
                params.put(attribute.getName(), attribute.getValue());
            }
        }
        return params;
    }

    /**
     * Http解析json數據(Content-Type = application/json)
     *
     * @param fullHttpRequest
     * @return
     * @throws UnsupportedEncodingException
     */
    private Map<String, Object> getJSONParams(FullHttpRequest fullHttpRequest) throws UnsupportedEncodingException {
        Map<String, Object> params = new HashMap<>();
        ByteBuf content = fullHttpRequest.content();
        byte[] reqContent = new byte[content.readableBytes()];
        content.readBytes(reqContent);
        String strContent = new String(reqContent, "UTF-8");
        JsonObject jsonParams = JsonUtil.parse(strContent);
        for (Object key : jsonParams.keySet()) {
            params.put(key.toString(), jsonParams.get((String) key));
        }
        return params;
    }

    /**
     * Http響應OK並返回Json
     *
     * @param status 狀態
     * @param result 返回值
     * @return
     */
    private FullHttpResponse responseOKAndJson(HttpResponseStatus status, JsonObject result) {
        ByteBuf content = copiedBuffer(result.toString(), CharsetUtil.UTF_8);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
        if (content != null) {
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json;charset=UTF-8");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        }
        return response;
    }

    /**
     * Http響應OK並返回文本
     *
     * @param status 狀態
     * @param result 返回值
     * @return
     */
    private FullHttpResponse responseOKAndText(HttpResponseStatus status, String result) {
        ByteBuf content = copiedBuffer(result, CharsetUtil.UTF_8);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, content);
        if (content != null) {
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        }
        return response;
    }
}

3:服務管控

package cn.ayl.socket.server;

import cn.ayl.config.Const;
import cn.ayl.socket.decoder.HttpAndWebSocketDecoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.Channel;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * created by Rock-Ayl 2019-11-4
 * 通信服務
 */
public class SocketServer {

    protected static Logger logger = LoggerFactory.getLogger(SocketServer.class);

    private Channel channel;
    /**
     * NioEventLoopGroup是一個處理I/O操作的事件循環器 (其實是個線程池)。
     * netty爲不同類型的傳輸協議提供了多種NioEventLoopGroup的實現。
     * 在本例中我們要實現一個服務端應用,並使用了兩個NioEventLoopGroup。
     * 第一個通常被稱爲boss,負責接收已到達的 connection。
     * 第二個被稱作 worker,當 boss 接收到 connection 並把它註冊到 worker 後,worker 就可以處理 connection 上的數據通信。
     * 要創建多少個線程,這些線程如何匹配到Channel上會隨着EventLoopGroup實現的不同而改變,或者你可以通過構造器去配置他們。
     */
    EventLoopGroup bossGroup = new NioEventLoopGroup();
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    /**
     * 創建一個默認配置的HttpServerBootstrap
     *
     * @param bossGroup   netty-boss
     * @param workerGroup netty-work-IO
     * @return
     */
    public static ServerBootstrap createDefaultServerBootstrap(EventLoopGroup bossGroup, EventLoopGroup workerGroup) {
        return new ServerBootstrap()
                /**
                 * 組裝Boss和IO
                 */
                .group(bossGroup, workerGroup)
                /**
                 * 這裏我們指定NioServerSocketChannel類,用來初始化一個新的Channel去接收到達的connection。
                 */
                .channel(NioServerSocketChannel.class)
                /**
                 * 你可以給Channel配置特有的參數。
                 * 這裏我們寫的是 TCP/IP 服務器,所以可以配置一些 socket 選項,例如 tcpNoDeply 和 keepAlive。
                 * 請參考ChannelOption和ChannelConfig文檔來獲取更多可用的 Channel 配置選項,並對此有個大概的瞭解。
                 * BACKLOG用於構造服務端套接字ServerSocket對象,標識當服務器請求處理線程全滿時,用於臨時存放已完成三次握手的請求的隊列的最大長度。如果未設置或所設置的值小於1,Java將使用默認值50。
                 */
                .option(ChannelOption.SO_BACKLOG, Const.ChannelOptionSoBacklogValue)
                /**
                 * 注意到option()和childOption()了嗎?
                 * option()用來配置NioServerSocketChannel(負責接收到來的connection),
                 * 而childOption()是用來配置被ServerChannel(這裏是NioServerSocketChannel) 所接收的Channel
                 *
                 * ChannelOption.SO_KEEPALIVE表示是否開啓TCP底層心跳機制,true爲開啓
                 * ChannelOption.SO_REUSEADDR表示端口釋放後立即就可以被再次使用,因爲一般來說,一個端口釋放後會等待兩分鐘之後才能再被使用
                 * ChannelOption.TCP_NODELAY表示是否開始Nagle算法,true表示關閉,false表示開啓,通俗地說,如果要求高實時性,有數據發送時就馬上發送,就關閉,如果需要減少發送次數減少網絡交互就開啓
                 */
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childOption(ChannelOption.SO_REUSEADDR, true)
                .childOption(ChannelOption.TCP_NODELAY, true)
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
    }

    /**
     * 開啓Http與WebSocket
     */
    public void startup() {
        try {
            try {
                /**
                 * ServerBootstrap是用來搭建 server 的協助類。
                 * 你也可以直接使用Channel搭建 server,然而這樣做步驟冗長,不是一個好的實踐,大多數情況下建議使用ServerBootstrap。
                 */
                ServerBootstrap bootstrap = SocketServer.createDefaultServerBootstrap(bossGroup, workerGroup);
                /**
                 * 這裏的 handler 會被用來處理新接收的Channel。
                 * ChannelInitializer是一個特殊的 handler,
                 * 幫助開發者配置Channel,而多數情況下你會配置Channel下的ChannelPipeline,
                 * 往 pipeline 添加一些 handler (例如DiscardServerHandler) 從而實現你的應用邏輯。
                 * 當你的應用變得複雜,你可能會向 pipeline 添加更多的 handler,並把這裏的匿名類抽取出來作爲一個單獨的類。
                 */
                bootstrap.childHandler(new HttpAndWebSocketDecoder());
                /**
                 * 剩下的事情就是綁定端口並啓動服務器,這裏我們綁定到機器的8080端口。你可以多次調用bind()(基於不同的地址)。
                 * Bind and start to accept incoming connections.(綁定並開始接受傳入的連接)
                 */
                ChannelFuture f = bootstrap.bind(Const.SocketPort).sync();
                /**
                 * Wait until the server socket is closed.(等待,直到服務器套接字關閉)
                 * In this example, this does not happen, but you can do that to gracefully(在本例中,這種情況不會發生,但是您可以優雅地這樣做)
                 * shut down your server.(關閉你的服務)
                 */
                channel = f.channel();
                channel.closeFuture().sync();
            } finally {
                workerGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
        } catch (Exception e) {
            logger.error("Run Socket Fail!");
        }
    }

    //關閉Socket
    public void destroy() {
        if (channel != null) {
            channel.close();
        }
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
        System.out.println("WebsocketChatServer Destroy:" + Const.SocketPort);
    }

    public static void main(String[] args) {
        new SocketServer().startup();
    }


}

4:常量池:

package cn.ayl.config;

/**
 * created by Rock-Ayl 2019-11-5
 * 常量
 */
public class Const {

    //SocketPort
    public static int SocketPort = 8888;
    //http請求聚合字節最大長度
    public static int MaxContentLength = 65535;
    //WebSocket讀空閒時間閒置/秒
    public static int ReaderIdleTimeSeconds = 8;
    //WebSocket寫空閒時間閒置/秒
    public static int WriterIdleTimeSeconds = 10;
    //WebSocket所有空閒時間閒置/秒
    public static int AllIdleTimeSeconds = 12;
    public static String WebSocketPath = "/WebSocket";
    //BACKLOG值用於構造服務端套接字ServerSocket對象,標識當服務器請求處理線程全滿時,用於臨時存放已完成三次握手的請求的隊列的最大長度。如果未設置或所設置的值小於1,Java將使用默認值50。
    public static int ChannelOptionSoBacklogValue = 1024;

}

5:WebSocket測試

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebSocket客戶端</title>
</head>
<body>
<script type="text/javascript">
    var socket;
    //如果瀏覽器支持WebSocket
    if (window.WebSocket) {
        //參數就是與服務器連接的地址
        socket = new WebSocket("ws://127.0.0.1:8888/WebSocket");

        //客戶端收到服務器消息的時候就會執行這個回調方法
        socket.onmessage = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "\n" + event.data;
        }

        //連接建立的回調函數
        socket.onopen = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = "連接開啓";
        }

        //連接斷掉的回調函數
        socket.onclose = function (event) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value + "\n" + "連接關閉";
        }
    } else {
        alert("瀏覽器不支持WebSocket!");
    }

    //發送數據
    function send(message) {
        if (!window.WebSocket) {
            return;
        }
        //當websocket狀態打開
        if (socket.readyState == WebSocket.OPEN) {
            socket.send(message);
        } else {
            alert("連接沒有開啓");
        }
    }
</script>
<form onsubmit="return false">
    <textarea name="message" style="width: 400px;height: 200px"></textarea>
    <input type="button" value="發送數據" onclick="send(this.form.message.value);">
    <h3>服務器輸出:</h3>
    <textarea id="responseText" style="width: 400px;height: 300px;"></textarea>
    <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空數據">
</form>
</body>
</html>

-----------------------------------------------End-----------------------------------------------------------------------------------------------------------------

所有的核心代碼都在這裏,替換掉裏面Json就可以拿來去用,啓動後調用接口。

http://127.0.0.1:8888

就可以測試http請求

HTML5代碼直接打開可以測試WebSocket接口

如果你照着我這個還是搭不上,好,gitHub會用吧?Demo-GitHub地址

代碼思想和一些註釋都是網上彙總的

我也不太瞭解Netty比較核心的東西,本博客也只是學習自用,目的是讓我這種人不看官網的能夠快速上手Netty大概怎麼玩的。

 

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