Netty对WebSocket的整合

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_29807745/article/details/82558599

一 . WebSocket简介

         在HTTP1.0和HTTP1.1协议中,实现服务端主动的发送消息到网页或者APP上,是比较困难的,因为HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快,  尤其是现在IM(即时通信)几乎是很多APP都需要实现的功能,我们往往采用一种轮询的方式让终端去请求服务器获取对应的数据,相信很多做过IM通信的朋友应该深有感触,其实大多数的轮询都是无效的(即没有获得到任何的数据);另外一个方面,每一次轮询都是一个完整的Http请求,而根据Http协议,每一次请求都要在Header中携带大量的参数,这无疑对带宽也是一种极大的消耗。

  HTML5的诞生为我们带来的WebSocket,这是一个振奋人心的事情,WebSocket是基于Http协议的一种长连接协议,有了这种协议,我们就可以实现服务端主动往客户端发送消息的功能。有关WebSocket协议的相关信息请读者查询相关的文档,在笔者的博文中不再作过多的赘述。因为笔者最近在学习Netty, 所以今天我们就来说说Netty对WebSocket的支持。

二 . Netty对WebSocket的整合

         开发环境:   [注意,netty5已经被原著删除所以我们使用4.1版本,使用的同学需要注意]

                使用gradle进行版本管理

                JDK1.8

               IDEA 2018     

1. gradle配置     

plugins {
    id 'java'
}

group 'com.netty'
version '1.0'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}

dependencies {
    compile(
          "io.netty:netty-all:4.1.5.Final"
    )
}

2. 服务端代码

  2.1 服务端启动类

扫描二维码关注公众号,回复: 3124145 查看本文章
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import java.net.InetSocketAddress;

/**
 * 10.netty对webSocket的支持
 *
 * @author Driss
 * @time 2018/9/8 下午12:19
 * @email [email protected]
 */
public class MyServer {

    public static void main(String args[]) throws Exception {
        EventLoopGroup boosGrop = new NioEventLoopGroup();
        EventLoopGroup workerGrop = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //使用服务端初始化自定义类WebSocketChannelInitaializer
            serverBootstrap.group(boosGrop, workerGrop).channel(NioServerSocketChannel.class).childHandler(new WebSocketChannelInitaializer());

            //使用了不同的端口绑定方式
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
            //关闭连接
            channelFuture.channel().closeFuture().sync();
        } finally {
            //优雅关闭
            boosGrop.shutdownGracefully();
            workerGrop.shutdownGracefully();
        }
    }
}

  2.2 服务端管道初始化         

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;

/**
 * 初始化管道
 *
 * @author Driss
 * @time 2018/9/8 下午12:23
 * @email [email protected]
 */
public class WebSocketChannelInitaializer extends ChannelInitializer<SocketChannel> {


    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //HttpServerCodec: 针对http协议进行编解码
        pipeline.addLast(new HttpServerCodec());
        //ChunkedWriteHandler分块写处理,文件过大会将内存撑爆
        pipeline.addLast(new ChunkedWriteHandler());
        /**
         * 作用是将一个Http的消息组装成一个完成的HttpRequest或者HttpResponse,那么具体的是什么
         * 取决于是请求还是响应, 该Handler必须放在HttpServerCodec后的后面
         */
        pipeline.addLast(new HttpObjectAggregator(8192));
        //用于处理websocket, /ws为访问websocket时的uri
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));


        //自定义的处理器
        pipeline.addLast(new TextWebSocketFrameHandler());
    }
}

           2.3 自定义的处理类

package com.netty1.fifthexample;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

/**
 * 针对websocket的自定义处理器
 *
 * @author Driss
 * @time 2018/9/9 下午1:39
 * @email [email protected]
 */
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息: " + msg.text());


        //读取收到的信息写回到客户端
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now()));

    }

    /**
     * 连接建立时
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAddred " + ctx.channel().id().asLongText());
    }

    /**
     * 连接关闭时
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved " + ctx.channel().id().asLongText());
    }

    /**
     * 异常发生时
     *
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常发生");
        ctx.close();
    }
}

3. 页面代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>webSocket</title>
</head>
<body>
<script type="text/javascript">
    var socket;

    if (window.WebSocket) {
        socket = new WebSocket("ws://localhost:8899/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 + "\n" + "连接关闭";

        }
    } 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;">

    <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="javasvript: document.getElementById('responseText').value=''" value="清空内容">

</form>

<!--服务器端向客户端数据的返回-->
</body>
</html>

4. 测试 --后台打印效果结果

        注意: 页面每次刷子都没去关闭通道重新连接,见下面打印即知

/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51098:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/tools.jar:/Users/weitao/nettyDemo1/out/production/classes:/Users/weitao/.gradle/caches/modules-2/files-2.1/io.netty/netty-all/4.1.5.Final/b5ad7d9b6b866b37277498aec62b098b939e78b6/netty-all-4.1.5.Final.jar com.netty1.fifthexample.MyServer
handlerAddred acde48fffe001122-000005fa-00000001-d3dfac96feaf7f09-724f9e09
handlerRemoved acde48fffe001122-000005fa-00000001-d3dfac96feaf7f09-724f9e09
handlerAddred acde48fffe001122-000005fa-00000002-0b9ff20e16b0be45-edeaa52c
handlerRemoved acde48fffe001122-000005fa-00000002-0b9ff20e16b0be45-edeaa52c
handlerAddred acde48fffe001122-000005fa-00000003-55383c7e62b33bef-b9beeca6
handlerRemoved acde48fffe001122-000005fa-00000003-55383c7e62b33bef-b9beeca6
handlerAddred acde48fffe001122-000005fa-00000004-ea2f27506b7a9016-daaff20c
handlerRemoved acde48fffe001122-000005fa-00000004-ea2f27506b7a9016-daaff20c
handlerAddred acde48fffe001122-000005fa-00000005-e8468d72e37b8faa-b6ab030c
收到消息: 5656
收到消息: 哈哈哈
收到消息: 哈哈哈111

5. 测试--前台效果结果

猜你喜欢

转载自blog.csdn.net/qq_29807745/article/details/82558599
今日推荐