轉載--netty開發聊天CS與BS

本文爲轉載後,加上了部分修改而成。具體入下:

Client-Server模式

使用netty寫客戶端與服務器,完成聊天。爲了優化用戶體驗,這裏加了一個bean類,ClientBean,用於描述客戶端。

Server端

netty本身就有很好的分層設計,框架和業務邏輯分界明顯,對於不同的業務,我們只需要實現不同的handler即可。

SimpleChatServerHandler.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package simplechat;

import java.util.HashMap;

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

/**
 * @author 郭  璞
 *
 */
public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> {

    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    public static HashMap<Channel, ClientBean> channelmap = new HashMap<Channel, ClientBean>();

    /* (non-Javadoc)
     * @see io.netty.channel.ChannelHandlerAdapter#handlerAdded(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        for(Channel channel: channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入啦!\n");
        }
        channels.add(ctx.channel());
    }

    /* (non-Javadoc)
     * @see io.netty.channel.ChannelHandlerAdapter#handlerRemoved(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        for(Channel channel: channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + "離開了\n");
        }
        channels.remove(ctx.channel());
    }

    /* (non-Javadoc)
     * @see io.netty.channel.SimpleChannelInboundHandler#channelRead0(io.netty.channel.ChannelHandlerContext, java.lang.Object)
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel incoming = ctx.channel();
        for(Channel channel: channels) {
            if(channel == incoming) {
                channel.writeAndFlush("[YOU] " + msg + "\n");
            }else{
                // 這裏其實放到外層感覺會更好一點,邏輯上
                ClientBean clientBean = this.channelmap.get(ctx.channel());
                if(clientBean.isIsfirstconnect() == true) {
                    // 第一次輸入,將msg作為用戶暱稱即可
                    clientBean.setIsfirstconnect(false);
                    clientBean.setNickname(msg);
                    clientBean.setRemoteaddress(incoming.remoteAddress().toString());

                }else{
                    String nickname = this.channelmap.get(ctx.channel()).getNickname();
                    String remoteaddress = this.channelmap.get(ctx.channel()).getRemoteaddress();
                    System.out.println("DEBUG:" + nickname);
                    channel.writeAndFlush("[" + nickname+"@"+ remoteaddress + "]" + msg + "\n");
                }
            }
        }
    }

    /* (non-Javadoc)
     * @see io.netty.channel.ChannelInboundHandlerAdapter#channelActive(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("SimpleChatClient:" + incoming.remoteAddress() + "在線!\n");
    }

    /* (non-Javadoc)
     * @see io.netty.channel.ChannelInboundHandlerAdapter#channelInactive(io.netty.channel.ChannelHandlerContext)
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("SimpleChatClient: " + incoming.remoteAddress() + "離線!\n");
    }

    /* (non-Javadoc)
     * @see io.netty.channel.ChannelInboundHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext, java.lang.Throwable)
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("SimpleChatClient: " + incoming.remoteAddress() + "異常連接!\n");
        cause.printStackTrace();
        ctx.close();
    }
}

SimpleChatServer.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package simplechat;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @author 郭  璞
 *
 */
public class SimpleChatServer {
    public void run() {
        int port = 8888;
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel.class)
                     .childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                            pipeline.addLast("decoder", new StringDecoder());
                            pipeline.addLast("encoder", new StringEncoder());
                            pipeline.addLast("handler", new SimpleChatServerHandler());
                            // 客戶端第一次連接會觸發此操作,然後是active,再是read
                            SimpleChatServerHandler.channelmap.put(ch, new ClientBean(true, ch.remoteAddress().toString(), ch.remoteAddress().toString()));
                            System.out.println("SimpleChatClient: " + ch.remoteAddress() + "連接上了!");
                        }
                    })
                     .option(ChannelOption.SO_BACKLOG, 128)
                     .childOption(ChannelOption.SO_KEEPALIVE, true);
            System.out.println("SimpleChatServer is running...");
            ChannelFuture f = bootstrap.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            throw new RuntimeException(" :\n" + e);
        }finally{
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            System.out.println("SimpleChatServer shutdown gracefully.\n");
        }
    }

    public static void main(String[] args) {
        new SimpleChatServer().run();
    }

}

Client端

ClientBean

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package simplechat;

/**
 * @author 郭  璞
 *
 */
public class ClientBean {
    private boolean isfirstconnect=true;
    private String nickname;
    private String remoteaddress;
    public boolean isIsfirstconnect() {
        return isfirstconnect;
    }
    public void setIsfirstconnect(boolean isfirstconnect) {
        this.isfirstconnect = isfirstconnect;
    }
    public String getNickname() {
        return nickname;
    }
    public void setNickname(String nickname) {
        this.nickname = nickname;
    }
    public String getRemoteaddress() {
        return remoteaddress;
    }
    public void setRemoteaddress(String remoteaddress) {
        this.remoteaddress = remoteaddress;
    }
    @Override
    public String toString() {
        return "ClientBean [isfirstconnect=" + isfirstconnect + ", nickname=" + nickname + ", remoteaddress="
                + remoteaddress + "]";
    }
    public ClientBean(boolean isfirstconnect, String nickname, String remoteaddress) {
        super();
        this.isfirstconnect = isfirstconnect;
        this.nickname = nickname;
        this.remoteaddress = remoteaddress;
    }



}

SimpleChatClientHandler.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package simplechat;

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

/**
 * @author 郭  璞
 *
 */
public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> {

    /* (non-Javadoc)
     * @see io.netty.channel.SimpleChannelInboundHandler#channelRead0(io.netty.channel.ChannelHandlerContext, java.lang.Object)
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg);
    }

}

SimpleChatClient.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package simplechat;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * @author 郭  璞
 *
 */
public class SimpleChatClient {

    public void run(String host, int port) {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {

                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        pipeline.addLast("decoder", new StringDecoder());
                        pipeline.addLast("encoder", new StringEncoder());
                        pipeline.addLast("handler", new SimpleChatClientHandler());
                  }
              });

            Channel channel = b.connect(host, port).sync().channel();
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            // \r\n很重要,否則可能會出現假死
            System.out.println("恭喜您鏈接服務器成功\n請輸入您的暱稱:");channel.writeAndFlush(in.readLine()+"\r\n");
            while(true){
                channel.writeAndFlush(in.readLine() + "\r\n");
            }
        } catch (Exception e) {
            throw new RuntimeException(" :\n" + e);
        }finally {
            group.shutdownGracefully();
            System.out.println("客戶端平滑關閉了\n");
        }
    }

    public static void main(String[] args) {
        new SimpleChatClient().run("localhost", 8888);
    }
}

測試效果

首先要將SimpleChatServer跑起來,然後再開啓多個SimpleChatClient實例,首次運行會提示輸入暱稱,後面輸入就可以很方便的實現聊天了。

SimpleChatServer is running...
SimpleChatClient: /127.0.0.1:58424連接上了!
SimpleChatClient:/127.0.0.1:58424在線!

SimpleChatClient: /127.0.0.1:58448連接上了!
SimpleChatClient:/127.0.0.1:58448在線!

DEBUG:李四
DEBUG:張三
SimpleChatClient: /127.0.0.1:58448異常連接!(此處省略cause.printStackTrace()日誌)
SimpleChatClient: /127.0.0.1:58448離線!

-----------------------------------------------
恭喜您鏈接服務器成功
請輸入您的暱稱:
張三
[YOU] 張三
[李四@/127.0.0.1:58424]哈嘍,我是李四
嗨,李四,我是張三
[YOU] 嗨,李四,我是張三
------------------------------------------
恭喜您鏈接服務器成功
請輸入您的暱稱:
[SERVER] - /127.0.0.1:58448 加入啦!
李四
[YOU] 李四
哈嘍,我是李四
[YOU] 哈嘍,我是李四
[張三@/127.0.0.1:58448]嗨,李四,我是張三
[SERVER] - /127.0.0.1:58448離開了

Browser-Server模式

使用瀏覽器和服務器的模式進行聊天,一般會用websocket協議,netty對此支持的也不賴。

Server端

正如參考文獻中寫的,只有在服務器收到客戶端請求升級protocol的時候,纔會升級websocket協議,然後進行通信。其他情況,依舊是普通的http請求,所以server端要完成兩件事情,一個是普通的http協議,一個是websocket協議。

HTTP協議,HttpRequestHandler.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package websocket;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.URL;

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
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.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;

/**
 * @author 郭  璞
 *
 */
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

    private  String WS_URI;
    private static  File INDEX;

    static {
        URL location = HttpRequestHandler.class.getProtectionDomain().getCodeSource().getLocation();
        try {
            String path = location.toURI() + "WebsocketChatClient.html";
            path = !path.contains("file:")?path:path.substring(5);
            INDEX = new File(path);
        } catch (Exception e) {
            throw new RuntimeException("Unable to locate WebsocketChatClient.html :\n" + e);
        }
    }

    public HttpRequestHandler(String wsUri) {
        this.WS_URI = wsUri;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
        if(this.WS_URI.equalsIgnoreCase(request.getUri())) {
            ctx.fireChannelRead(request.retain());
        }else{
            //
            if(HttpHeaders.is100ContinueExpected(request)) {
                send1000Continue(ctx);
            }
            RandomAccessFile file = new RandomAccessFile(this.INDEX, "r");
            HttpResponse response = new DefaultHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK);
            response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html;charset=UTF-8");

            boolean keepAlive = HttpHeaders.isKeepAlive(request);

            if(keepAlive) {
                response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length());
                response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
            }
            ctx.writeAndFlush(response);

            if(ctx.pipeline().get(SslHandler.class) == null) {
                ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
            }else{
                ctx.write(new ChunkedNioFile(file.getChannel()));
            }
            ChannelFuture f = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            if(!keepAlive) {
                f.addListener(ChannelFutureListener.CLOSE);
            }
            file.close();
        }

    }

    /**
     * @param ctx
     */
    private void send1000Continue(ChannelHandlerContext ctx) {
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
    }

    /* (non-Javadoc)
     * @see io.netty.channel.ChannelInboundHandlerAdapter#exceptionCaught(io.netty.channel.ChannelHandlerContext, java.lang.Throwable)
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("Client: " + incoming.remoteAddress() + "異常:" + cause.getMessage());
        ctx.close();
    }

}

WebSocket協議,TextWebSocketFrameHandler.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package websocket;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

/**
 * @author 郭  璞
 *
 */
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        Channel incoming = ctx.channel();
        TextWebSocketFrame responsemsg = null;
        for(Channel channel: channels) {
            if(channel == incoming) {
                responsemsg = new TextWebSocketFrame("["+incoming.remoteAddress()+"]" + msg.text());
            }else{
                responsemsg = new TextWebSocketFrame("["+incoming.remoteAddress()+"]" + msg.text());
            }
            channel.writeAndFlush(responsemsg);
        }

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  // (2)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush(new TextWebSocketFrame("[SERVER] - " + incoming.remoteAddress() + " 加入"));
        }
        channels.add(ctx.channel());
        System.out.println("Client:"+incoming.remoteAddress() +"加入");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  // (3)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush(new TextWebSocketFrame("[SERVER] - " + incoming.remoteAddress() + " 離開"));
        }
        System.out.println("Client:"+incoming.remoteAddress() +"離開");
        channels.remove(ctx.channel());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5)
        Channel incoming = ctx.channel();
        System.out.println("Client:"+incoming.remoteAddress()+"在線");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6)
        Channel incoming = ctx.channel();
        System.out.println("Client:"+incoming.remoteAddress()+"掉線");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("Client:"+incoming.remoteAddress()+"異常: " + cause.getMessage());
        // 當出現異常就關閉連接
        cause.printStackTrace();
        ctx.close();
    }

}

WebSocketServer.java

/**
 * @Date 2018年5月16日
 *
 * @author 郭  璞
 *
 */
package websocket;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
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.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

/**
 * @author 郭  璞
 *
 */
public class WebSocketChatServer {

    public void run() {
        int port = 9999;
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new HttpServerCodec());
                    pipeline.addLast(new HttpObjectAggregator(64*1024));
                    pipeline.addLast(new ChunkedWriteHandler());
                    pipeline.addLast(new HttpRequestHandler("/ws"));
                    pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
                    pipeline.addLast(new TextWebSocketFrameHandler());
                }
            })
             .option(ChannelOption.SO_BACKLOG, 128)
             .childOption(ChannelOption.SO_KEEPALIVE, true);
            System.out.println("WebSocketChatServer is running...");
            //
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            throw new RuntimeException(" :\n" + e);
        }finally{
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
            System.out.println("WebSocketChatServer finally closed!");
        }
    }

    public static void main(String[] args) {
        new WebSocketChatServer().run();
    }

}

Browser端

根據剛纔HTTPRequestHandler.java設置的uri以及對應的HTML路徑,我們需要把WebsocketChatClient.html文件放到項目的bin目錄下,否則不能被正常找到。

WebsocketChatClient.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Chat</title>
</head>
<body>
    <script type="text/javascript">
        var socket;
        //檢查瀏覽器是否支持WebSocket
        if(window.WebSocket){
            console.log('This browser supports WebSocket');
        }else{
            console.log('This browser does not supports WebSocket');
        }

        if (!window.WebSocket) {
            window.WebSocket = window.MozWebSocket;
        }
        if (window.WebSocket) {
            socket = new WebSocket("ws://localhost:9999/ws");
            socket.onmessage = function(event) {
                var ta = document.getElementById('responseText');
                console.log("來自客戶端的消息:" + event.data);
                ta.value = ta.value + '\n' + event.data
            };
            socket.onopen = function(event) {
                var ta = document.getElementById('responseText');
                console.log(event);
                ta.value = "連接開啓!";
            };
            socket.onclose = function(event) {
                var ta = document.getElementById('responseText');
                console.log(event);
                ta.value = ta.value + "連接被關閉";
            };
        } else {
            alert("你的瀏覽器不支持 WebSocket!");
        }

        function send() {
            if (!window.WebSocket) {
                return;
            }
            if (socket.readyState == WebSocket.OPEN) {
                message = document.getElementById("message").value;
                console.log("\n======\n");
                console.log(message);
                console.log("\n======\n");
                socket.send(message);
            } else {
                alert("連接沒有開啓.");
            }
        }
    </script>
    <form onsubmit="return false;">
        <h3>WebSocket 聊天室:</h3>
        <textarea id="responseText" style="width: 500px; height: 300px;"></textarea>

        <input type="text" name="message" id="message" style="width: 300px" value="ping">
        <input type="button" value="發送消息" onclick="send()">
        <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空聊天記錄">
    </form>
</body>
</html>

測試效果

netty實現websocket聊天效果圖

對這個例子而言,同樣可以通過一個HashMap來實現用戶可自由填寫暱稱的方式來優化用戶體驗。具體的實現方式和上文類似,這裏就不過多的敘述了。


總結

經過這兩個小例子,頂多也就是對netty整體的工作流程瞭解下,至於實際的開發,相比還遠遠不夠。可以多想想如何基於上面的例子實現多人聊天,多房間聊天。

參考鏈接:

  1. https://my.oschina.net/waylau/blog/380957
  2. http://www.importnew.com/21561.html
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章