netty5+websocket练习

1. 了解WebSocket知识
  略
2. websocket实现系统简单反馈时间

  WebSocketServerHandler.java

package com.jieli.nettytest.websocketserver;

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.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

public class WebSocketServer {
    
    public void run(int port){
        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 {
                    //HttpServerCodec将请求和应答消息编码或解码为HTTP消息
                    //通常接收到的http是一个片段,如果想要完整接受一次请求所有数据,我们需要绑定HttpObjectAggregator
                    //然后就可以收到一个FullHttpRequest完整的请求信息了
                    //ChunkedWriteHandler 向客户端发送HTML5文件,主要用于支持浏览器和服务器进行WebSocket通信
                    //WebSocketServerHandler自定义Handler
                    ch.pipeline().addLast("http-codec", new HttpServerCodec())
                                 .addLast("aggregator", new HttpObjectAggregator(65536)) //定义缓冲大小
                                 .addLast("http-chunked", new ChunkedWriteHandler())
                                 .addLast("handler", new WebSocketServerHandler());
                }
            });
            
            ChannelFuture f = b.bind(port).sync();
            System.out.println("start...");
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) {
        new WebSocketServer().run(7777);
    }
}

WebSocketServerHandler.java

package com.jieli.nettytest.websocketserver;

import java.util.logging.Level;
import java.util.logging.Logger;

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.SimpleChannelInboundHandler;
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.HttpHeaderUtil;
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;

public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{

    /**
     * 日志
     */
    private static final Logger logger = 
            Logger.getLogger(WebSocketServerHandler.class.getName());
    /**
     * 全局websocket
     */
    private WebSocketServerHandshaker handshaker;
    
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        //普通HTTP接入
        if(msg instanceof FullHttpRequest){
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        }else if(msg instanceof WebSocketFrame){ //websocket帧类型 已连接
            //BinaryWebSocketFrame CloseWebSocketFrame ContinuationWebSocketFrame 
            //PingWebSocketFrame PongWebSocketFrame TextWebScoketFrame
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }
    
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
    
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){
        //如果http解码失败 则返回http异常 并且判断消息头有没有包含Upgrade字段(协议升级)
        if(!request.decoderResult().isSuccess() 
                || (!"websocket".equals( request.headers().get("Upgrade")))    ){
            sendHttpResponse(ctx, request, new DefaultFullHttpResponse(
                    HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return ;
        }
        //构造握手响应返回
        WebSocketServerHandshakerFactory ws = new WebSocketServerHandshakerFactory("", null, false);
        handshaker = ws.newHandshaker(request);
        if(handshaker == null){
            //版本不支持
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else{
            handshaker.handshake(ctx.channel(), request);
        }
    }
    /**
     * websocket帧
     * @param ctx
     * @param frame
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
        //判断是否关闭链路指令
        if(frame instanceof CloseWebSocketFrame){
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
            return ;
        }
        //判断是否Ping消息 -- ping/pong心跳包
        if(frame instanceof PingWebSocketFrame){
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            return ;
        }
        //本程序仅支持文本消息, 不支持二进制消息
        if(!(frame instanceof TextWebSocketFrame)){
            throw new UnsupportedOperationException(
                    String.format("%s frame types not supported", frame.getClass().getName()));
        }
        
        //返回应答消息 text文本帧
        String request = ((TextWebSocketFrame) frame).text();
        //打印日志
        if(logger.isLoggable(Level.FINE)){
            logger.fine(String.format("%s received %s", ctx.channel(), request));
        }
        //发送到客户端websocket
        ctx.channel().write(new TextWebSocketFrame(request 
                + ", 欢迎使用Netty WebSocket服务, 现在时刻:" 
                + new java.util.Date().toString()));
    }
    
    /**
     * response
     * @param ctx
     * @param request
     * @param response
     */
    private static void sendHttpResponse(ChannelHandlerContext ctx, 
            FullHttpRequest request, FullHttpResponse response){
        //返回给客户端
        if(response.status().code() != HttpResponseStatus.OK.code()){
            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
            HttpHeaderUtil.setContentLength(response, response.content().readableBytes());
        }
        //如果不是keepalive那么就关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(response);
        if(!HttpHeaderUtil.isKeepAlive(response) 
                || response.status().code() != HttpResponseStatus.OK.code()){
            f.addListener(ChannelFutureListener.CLOSE);
        }
    }
    
    /**
     * 异常 出错
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

WebSocketServer.html (这个随便放都可以,到时候双击打开这个即可,不由服务器提供)

<html>
    <head>
        <meta charset="utf-8">
        <title>Netty websocket 时间服务器</title>
    </head>
    <body>
        <form action="" onsubmit="return false;">
            <input type="text" name="message" value="..."/>
            <br>
            <input type="button" value="send" value="发送websocket请求消息" onclick="send(this.form.message.value);" /> 
            <hr color="blue">
            <h3>服务器返回信息</h3>
            <textarea id="responseText" rows="10" cols=""></textarea>
        </form>
    </body>
    
    <script type="text/javascript">
        var socket;
        if(!window.WebSocket){
            window.WebSocket = window.MozWebSocket;
        }
        if(window.WebSocket){
            socket = new WebSocket("ws://localhost:7777/websocket");
            socket.onmessage = function(event){
                var ta = document.getElementById('responseText');
                ta.value="";
                ta.value=event.data;
            };
            socket.onopen = function(event){
                var ta = document.getElementById('responseText');
                ta.value = "打开websocket服务正常";
            }
            socket.onclose = function(event){
                var ta = document.getElementById('responseText');
                ta.value="";
                ta.value="websocket关闭";
            }
        }else{
            alert("对不起,您的浏览器不支持WebSocket.");
        }
        
        function send(message){
            if(!window.WebSocket){
                return ;
            }
            if(socket.readyState == WebSocket.OPEN){
                socket.send(message);
            }else{
                alert("WebSocket 连接创建失败.");
            }
        }
    </script>
</html>

运行结果

3. websocket实现简单聊天室

  WebSocketChatServer.java

package com.jieli.nettytest.websocket;

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;

public class WebsocketChatServer {
    
    private int port;
    
    public WebsocketChatServer(int port){
        this.port = port;
    }
    
    public void run() throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new WebsocketChatServerInitializer())
             .option(ChannelOption.SO_BACKLOG, 128)
             .childOption(ChannelOption.SO_KEEPALIVE, true);
            
            System.out.println("websocket start..");

            ChannelFuture f = b.bind(port).sync();
            
            f.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("websocket close.");
        }
    }
    
    public static void main(String[] args) throws Exception{
        new WebsocketChatServer(8080).run();
    }
}

 WebsocketChatServerInitializer.java

package com.jieli.nettytest.websocket;

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;

public class WebsocketChatServerInitializer extends ChannelInitializer<SocketChannel>{

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new HttpServerCodec())
                .addLast(new HttpObjectAggregator(64*1024))
                .addLast(new ChunkedWriteHandler())
                .addLast(new HttpRequestHandler("/ws")) //如果访问的是RUI"/ws",处理WebSocket升级
                .addLast(new WebSocketServerProtocolHandler("/ws"))
                .addLast(new TextWebSocketFrameHandler());
    }
    
}

HttpRequestHandler.java

package com.jieli.nettytest.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.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaderValues;
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;

public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
    //扩展SimpleChannelInboundHandler用于处理FullHttpRequest信息
    private final String wsuri;
//    private static final File index;
//    
//    static {
//        URL location = HttpRequestHandler.class
//                .getProtectionDomain().getCodeSource().getLocation();
//        try {
//            String path = location.toURI() + "html/index.html";
//            path = !path.contains("file:") ? path : path.substring(5);
//            index = new File(path);
//        } catch (Exception e) {
//            e.printStackTrace();
//            throw new IllegalStateException("can't find index.html");
//        }
//    }
    
    public HttpRequestHandler(String wsuri){
        this.wsuri = wsuri;
    }
    
    @Override
    protected void messageReceived(ChannelHandlerContext ctx,
            FullHttpRequest request) throws Exception {
        if(wsuri.equalsIgnoreCase(request.uri())){
            //如果请求的是WebSocket升级,将其传递给在ChannelPipeline中的下一个ChannelInboundHandler处理
            //这里跟第一个例子的websocket协议升级判断方式是不同的 因为只只是判断uri路径而已
            //对应的js请求路径 socket = new WebSocket("ws://localhost:8080/ws");
            ctx.fireChannelRead(request.retain());
        }else{
            //处理100continue
            if(HttpHeaderUtil.is100ContinueExpected(request)){
                send100Continue(ctx);
            }
            //读取默认页
            RandomAccessFile file = new RandomAccessFile("C:\\html\\index.html", "r");
            
            HttpResponse response = new DefaultHttpResponse(
                    request.protocolVersion(), HttpResponseStatus.OK);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, 
                    "text/html; charset=UTF-8");
            
            boolean keepAlive = HttpHeaderUtil.isKeepAlive(request);
            if(keepAlive){
                response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, (int) file.length());
                response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
            }
            ctx.write(response);
            
            if(ctx.pipeline().get(SslHandler.class) == null){
                //如果不是https安全连接的话 要达到最大效率的话,可以通过把index.html直接存储在DefaultFileRegion中
                //实现零拷贝传输 就是不拷贝到内存,直接读取文件通过文件输出流进行处理
                ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
            }else{
                //否则要读取全部文件,然后处理加密,在发送,只不过这一切都有netty内部处理
                //A ChunkedInput that fetches data from a file chunk by chunk using NIO FileChannel.
                //If your operating system supports zero-copy file transfer such as sendfile(), 
                //you might want to use FileRegion instead.
                ctx.write(new ChunkedNioFile(file.getChannel()));
            }
            ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
            
            if(!keepAlive){
                future.addListener(ChannelFutureListener.CLOSE);
            }
            file.close();
        }
    }

    private static void send100Continue(ChannelHandlerContext ctx){
        FullHttpResponse response = (FullHttpResponse) new DefaultHttpResponse(
                HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        Channel incoming = ctx.channel();
        System.out.println("Client:"+incoming.remoteAddress()+"exception.");
        cause.printStackTrace();
        ctx.close();
    }
}

html/index.html

<!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:8080/ws");
            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 localhost">
        <input type="button" value="发送消息" onclick="send(this.form.message.value)">
        <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空聊天记录">
    </form>
    <br>
        Netty SEO 优化 
        Netty 是什么
        Netty 怎么样
        Netty4 Netty5 区别
        Netty 效率
        Netty 版本区别
        Netty 和 Mina
        Netty 网络编程
        Netty Java 网络编程
        Netty Java Socket NIO
        NIO 编程
        Netty NIO 开发
        Netty3 Netty4 Netty5
        Netty 好处
        Netty 一般注意什么
        Netty 例子程序
        Netty Hello World
        Netty 聊天程序
        Netty Web HTML HTTP FTP SSL 
        Netty UDP TCP WebSocket 练习
        Netty 连接数
        Netty 源码
    <br>
</body>
</html>

运行结果,依次运行1,2,3

  服务器打印结果

Netty实现简单WebSocket服务器
https://www.cnblogs.com/lanqie/p/8250302.html

一款基于Netty开发的WebSocket服务器
https://www.cnblogs.com/demodashi/p/9443544.html


Netty实现WebSocket通信
https://blog.csdn.net/naruto_Mr/article/details/81452640

轻量级、高性能的WebSocket框架
netty-websocket-spring-boot-starter
https://gitee.com/Yeauty/netty-websocket-spring-boot-starter

Netty+WebSocket简单实现网页聊天
https://www.cnblogs.com/ggwow/p/7921136.html


一款基于Netty开发的WebSocket服务器
https://www.cnblogs.com/demodashi/p/9443544.html


Netty实现WebSocket通信案例
https://blog.csdn.net/Java_Mrsun/article/details/81911214

netty学习----Netty对WebSocket的整合
https://blog.csdn.net/qq_29807745/article/details/82558599


Netty入门(一)环境搭建及使用
https://www.cnblogs.com/coderJiebao/p/Netty01.html

Netty框架入门
https://www.cnblogs.com/baronzhao/p/netty_1.html


《跟闪电侠学Netty》开篇:Netty是什么?
https://www.jianshu.com/p/a4e03835921a

Netty处理WebSocket,Nginx配置
http://liweidan.cn/java/frame/netty/2019/08/04/346/

参考资料:
  《Netty 权威指南》
  https://zh.wikipedia.org/wiki/WebSocket
  https://www.zhihu.com/question/20215561
  http://waylau.com/netty-websocket-chat/

原文地址:http://www.cnblogs.com/wunaozai/p/5240006.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值