zoukankan      html  css  js  c++  java
  • Netty实现对Websocket的支持

    一、WebSocket的简介及优势

    WebSocket 是一种网络通信协议。RFC6455 定义了它的通信标准。WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

    首先可以看下HTTP协议的有哪些不好的地方:HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。

    为了解决这些痛点,WebSocket就应运而生了。WebSocket 连接允许客户端和服务器之间进行全双工通信,以便任一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。

    二、WebSocket客户端API

    2.1 WebSocket 构造函数

           var Socket=new WebSocket("ws://localhost:20000/web");

    2.2 WebSocket 属性

    以下是 WebSocket 对象的属性。假定我们使用了以上代码创建了 Socket 对象:

    属性描述
    Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:0 - 表示连接尚未建立。1 - 表示连接已建立,可以进行通信。2 - 表示连接正在进行关闭。3 - 表示连接已经关闭或者连接不能打开。
    Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。

     

     

     

    2.3 WebSocket 事件

    以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

    事件事件处理程序描述
    open Socket.onopen 连接建立时触发
    message Socket.onmessage 客户端接收服务端数据时触发
    error Socket.onerror 通信发生错误时触发
    close Socket.onclose 连接关闭时触发

     

     

     

     

     

     

     

     

     

    2.4 WebSocket 方法

    以下是 WebSocket 对象的相关方法。假定我们使用了以上代码创建了 Socket 对象:

    方法描述
    Socket.send() 使用连接发送数据
    Socket.close() 关闭连接

     

    三、WebSocket客户端代码

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Netty WebSocket</title>
    </head>
    <body>
    <br>
    <script type="text/javascript">
        var websocket;
        if(!window.WebSocket){
            window.WebSocket=window.MozWebSocket;
        }
        if(window.WebSocket){
            websocket=new WebSocket("ws://localhost:20000/web");
            websocket.onmessage=function (event) {
                console.log("websocket接受消息"+event.data);
                var text=document.getElementById('responseText');
                text.value="";
                text.value=event.data;
            }
            websocket.onopen=function (event) {
                console.log("websocket打开");
                var text=document.getElementById('responseText');
                text.value="";
                text.value="打开websocket服务正常";
            }
            websocket.onclose=function (event) {
                console.log("websocket关闭");
                var text=document.getElementById('responseText');
                text.value="";
                text.value="关闭websocket服务";
            }
            websocket.onerror=function (event) {
                console.log("websocket异常");
                var text=document.getElementById('responseText');
                text.value="";
                text.value="websocket服务异常";
    
            }
        }else{
            alert("你的浏览器不支持WebSocket");
        }
    
        function send(message) {
            if(websocket){
                if(websocket.readyState==WebSocket.OPEN){
                    console.log("通过websocket发送消息");
                    websocket.send(message);
                }
            }else{
                alert("未建立websocket连接");
            }
        }
    </script>
    
    <form onsubmit="return false;">
        <input type="text" name="message" value="Netty实践"/>
        <br><br>
        <input type="button" value="发送消息" onclick="send (this.form.message.value)"/>
        <h3>应答消息</h3>
        <textarea id="responseText" style=" 500px;height: 300px;"></textarea>
    </form>
    
    </body>
    </html>

    四、netty服务端代码

    4.1 netty启动类

    public class NettyServer {
    
        public static void main(String[] args) throws Exception{
            //服务器启动
            new NettyServer().start(20000);
        }
    
        public void start(int port) throws Exception{
            //用于监听连接的线程组
            EventLoopGroup bossGroup=new NioEventLoopGroup();
            //用于发送接收消息的线程组
            EventLoopGroup workGroup=new NioEventLoopGroup();
    
            try{
                //启动类引导程序
                ServerBootstrap b=new ServerBootstrap();
                //绑定两个线程组
                b.group(bossGroup,workGroup);
                //设置非阻塞,用它来建立新accept的连接,用于构造serverSocketChannel的工厂类
                b.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel){
                        ChannelPipeline channelPipeline=channel.pipeline();
                        // HttpServerCodec:将请求和应答消息解码为HTTP消息
                        channelPipeline.addLast(new HttpServerCodec());
                        // HttpObjectAggregator:将HTTP消息的多个部分合成一条完整的HTTP消息
                        channelPipeline.addLast(new HttpObjectAggregator(65536));
                        // ChunkedWriteHandler:向客户端发送HTML5文件
                        channelPipeline.addLast(new ChunkedWriteHandler());
                        //在管道中添加自己实现的Handler处理类
                        channelPipeline.addLast(new WebsocketServerHandler());
                    }
                });
                Channel channel=b.bind(port).sync().channel();
                System.out.println("服务器启动端口:"+port);
                channel.closeFuture().sync();
            }finally {
                workGroup.shutdownGracefully();
                bossGroup.shutdownGracefully();
            }
    
    
        }
    }

    4.2 netty的业务处理的Handler类

    public class WebsocketServerHandler extends SimpleChannelInboundHandler<Object> {
    
        private WebSocketServerHandshaker handshaker;
    
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
            //传统的http接入
            if(o instanceof FullHttpRequest){
                handleHttpRequest(channelHandlerContext,(FullHttpRequest) o);
            }
            //webSocket接入
            else if(o instanceof WebSocketFrame){
                handleWebsocketFrame(channelHandlerContext,(WebSocketFrame) o);
            }
    
        }
    
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    
    
        private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){
            //构造握手响应返回
            WebSocketServerHandshakerFactory webSocketServerHandshakerFactory=new WebSocketServerHandshakerFactory("ws://localhost:20000/web",null,false);
            handshaker=webSocketServerHandshakerFactory.newHandshaker(req);
            handshaker.handshake(ctx.channel(),req);
    
        }
    
        private void handleWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
            //判断是否是链路关闭消息
            if(frame instanceof CloseWebSocketFrame){
                handshaker.close(ctx.channel(),(CloseWebSocketFrame) frame.retain());
                return;
            }
            //判断是否是ping消息
            if(frame instanceof PingWebSocketFrame){
                ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
                return;
            }
            //文本消息处理
            String request=((TextWebSocketFrame)frame).text();
            System.out.println("接受的信息是:"+request);
            String date=new Date().toString();
            //将接收消息写回给客户端
            ctx.channel().write(new TextWebSocketFrame("现在时刻:"+date+"发送了:"+request));
        }
    }

    五、运行成功截图

     

    参考资料:

    https://www.cnblogs.com/jingmoxukong/p/7755643.html

    http://www.ruanyifeng.com/blog/2017/05/websocket.html

  • 相关阅读:
    mysql-Invalid use of group function-聚合函数不能直接使用在where后面-使用exists,外查询与子查询应条件关联
    python-数据库之pymysql模块(连接对象-游标对象-执行-获取值或者提交事务)
    python作业-99乘法表作业,注意制表符合print结束符以及外层和里层怎么确定的,以及闰年
    python学习笔记-if_while_for_break_continue
    python-python中的Debug大法
    python-常用的几种格式化方法
    python学习一周总结
    python学习笔记-列表,字典,以及函数返回值
    python-注释,变量命名和规范笔记
    OpenJudge 求重要逆序对数
  • 原文地址:https://www.cnblogs.com/lzxin/p/10054334.html
Copyright © 2011-2022 走看看