Netty入門(一)之webSocket聊天室

一:簡介

  • Netty 是一個提供 asynchronous event-driven (異步事件驅動)的網絡應用框架,是一個用以快速開發高性能、高可靠性協議的服務器和客戶端。
  • 換句話說,Netty 是一個 NIO 客戶端服務器框架,使用它能夠快速簡單地開發網絡應用程序,好比服務器和客戶端的協議。Netty 大大簡化了網絡程序的開發過程好比 TCP 和 UDP 的 socket 服務的開發。
  • 下載地址:http://netty.io/downloads.html

二:實例

1.自定義配置類javascript

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

/** * Created by 巔峯小學生 * 2018年3月3日 下午9:51:27 */
public class NettyConfig {

    /** * 儲存每個客戶端接入進來時的channel對象 */
    public static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    /** * 服務器端口號 */
    public static final int port = 8888;
    /** * WebSocket地址 */
    public static final String WEB_SOCKET_URL="ws://localhost:"+port+"/websocket";

}

2.接受/處理/響應webSocket請求的核心業務處理類html

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
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.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;

/** * Created by 巔峯小學生 * 2018年3月3日 下午10:00:52 * * 接受/處理/響應webSocket請求的核心業務處理類 */
public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {

    private WebSocketServerHandshaker handshaker;

    /** * 每當從服務端收到新的客戶端鏈接時, 客戶端的 Channel 存入ChannelGroup列表中, * 並通知列表中的其餘客戶端 Channel */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // (2)
        Channel incoming = ctx.channel();
        for (Channel channel : NettyConfig.group) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n");
        }
        NettyConfig.group.add(ctx.channel());
    }

    /* * 每當從服務端收到客戶端斷開時,客戶端的 Channel 移除 ChannelGroup 列表中, * 並通知列表中的其餘客戶端 Channel */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        for (Channel channel : NettyConfig.group) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n");
        }
        NettyConfig.group.remove(ctx.channel());
    }

    /** * 服務端監聽到客戶端活動 */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("Client:" + incoming.remoteAddress() + "在線");
    }

    /* * 客戶端與服務端斷開鏈接的時候調用 */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("Client:" + incoming.remoteAddress() + "掉線");
    }

    /** * 服務端接收客戶端發送過來的數據結束以後調用 */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /** * 當出現 Throwable 對象纔會被調用,即當 Netty 因爲 IO 錯誤或者處理器在處理事件時拋出的異常時。 * 在大部分狀況下,捕獲的異常應該被記錄下來而且把關聯的 channel 給關閉掉。 */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("Client:" + incoming.remoteAddress() + "異常");
        cause.printStackTrace();
        ctx.close();
    }

    /** * 服務端處理客戶端webSocket請求的核心方法。 * * 若是你使用的是 Netty 5.x 版本時,須要把 channelRead0() 重命名爲messageReceived() */
    @Override
    protected void channelRead0(ChannelHandlerContext context, Object msg) throws Exception {
        // 處理客戶端向服務端發起http握手請求的業務
        if (msg instanceof FullHttpRequest) {
            handHttpRequest(context, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {// 處理websocket鏈接業務
            handWebSocketFrame(context, (WebSocketFrame) msg);
        }
    }

    // -----------重寫方法結束-------------

    /** * 處理客戶端與服務端以前的websocket業務 * * @param ctx * @param frame */
    private void handWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判斷是不是關閉webSocket的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
        }
        // 判斷是否ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        // 每當從服務端讀到客戶端寫入信息時,將信息轉發給其餘客戶端的 Channel
        if (frame instanceof TextWebSocketFrame) {
            TextWebSocketFrame msg = (TextWebSocketFrame) frame;
            Channel incoming = ctx.channel();
            for (Channel channel : NettyConfig.group) {
                if (channel != incoming) {
                    channel.writeAndFlush(new TextWebSocketFrame("[" + incoming.remoteAddress() + "]" + msg.text()));
                } else {
                    channel.writeAndFlush(new TextWebSocketFrame("[you]" + msg.text()));
                }
            }
        }
        // 羣發消息
        // NettyConfig.group.writeAndFlush(tws);
    }

    /** * 處理客戶端向服務端發起http握手請求的業務 * * @param ctx * @param req */
    @SuppressWarnings("deprecation")
    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        if (!req.getDecoderResult().isSuccess() || !("websocket".equals(req.headers().get("Upgrade")))) {
            sendHttpResponse(ctx, req,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(NettyConfig.WEB_SOCKET_URL,
                null, false);
        handshaker = wsFactory.newHandshaker(req);
        if (handshaker == null) {
            WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
        } else {
            handshaker.handshake(ctx.channel(), req);
        }
    }

    /** * 服務端向客戶端響應消息 * * @param ctx * @param req * @param res */
    @SuppressWarnings("deprecation")
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse res) {
        if (res.getStatus().code() != 200) {
            ByteBuf buf = Unpooled.copiedBuffer(res.getStatus().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        // 服務端向客戶端發送數據
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (res.getStatus().code() != 200) {
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }

}

3.初始化鏈接時候的各個組件java

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

/** * Created by 巔峯小學生 * 2018年3月4日 下午12:15:13 * * 初始化鏈接時候的各個組件 */
public class MyWebSocketChannelHandler extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast("http-codec", new HttpServerCodec());
        ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
        ch.pipeline().addLast("http-chunket", new ChunkedWriteHandler());
        ch.pipeline().addLast("handler", new MyWebSocketHandler());
    }

}

4.程序啓動入口web

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

/** * Created by 巔峯小學生 * 2018年3月4日 * * 程序入口 負責啓動程序 */
public class Main {

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(new MyWebSocketChannelHandler());
            System.out.println("服務端開啓等待客戶端鏈接...");
            Channel ch = b.bind(NettyConfig.port).sync().channel();
            ch.closeFuture().sync();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            //優雅的退出程序
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

}

5.html聊天室頁面bootstrap

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
    <script type="text/javascript"> var socket; if (!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if (window.WebSocket) { socket = new WebSocket("ws://localhost: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 + "鏈接被關閉"; }; } else { alert("你的瀏覽器不支持 WebSocket!"); } function send(message) { if (!window.WebSocket) { return; } if (socket.readyState == WebSocket.OPEN) { socket.send(message); } else { alert("鏈接沒有開啓."); } } </script>
    <form onsubmit="return false;">
        <h3>WebSocket 聊天室:</h3>
        <textarea id="responseText" style="width: 500px; height: 300px;"></textarea>
        <br> <input type="text" name="message" style="width: 300px" value="Welcome to www.zyzpp.cn"> <input type="button" value="發送消息" onclick="send(this.form.message.value)"> <input  type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空聊天記錄">
    </form>
    <br>
    <br>
    <a href="http://www.zyzpp.cn/">更多例子請訪問 www.zyzpp.cn</a>
</body>
</html>

這裏寫圖片描述

三:擴展

  • 如何在讓不少客戶機進行聊天呢?
  • 好比:電腦版QQ。
  • 請閱讀:Netty入門(二)之PC聊天室
相關文章
相關標籤/搜索