Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

场景

Netty中实现多客户端连接与通信-以实现聊天室群聊功能为例(附代码下载):

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108623306

上面讲了使用使用Socket搭建多客户端的连接与通信。

那么如果在Netty中使用WebSocket进行长连接通信要怎么实现。

WebSocket

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

WebSocket是一种在单个TCP连接上进行全双工通信的协议。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。

当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。

WebSocket 属性

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

 

属性 描述
Socket.readyState

只读属性 readyState 表示连接状态,可以是以下值:

  • 0 – 表示连接尚未建立。

  • 1 – 表示连接已建立,可以进行通信。

  • 2 – 表示连接正在进行关闭。

  • 3 – 表示连接已经关闭或者连接不能打开。

Socket.bufferedAmount

只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。


WebSocket 事件

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

 

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

WebSocket 方法

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

 

方法 描述
Socket.send()

使用连接发送数据

Socket.close()

关闭连接

 

注:

博客:
https://blog.csdn.net/badao_liumang_qizhi
关注公众号
霸道的程序猿
获取编程相关电子书、教程推送与免费下载。

实现

在IDEA中搭建好Netty的项目并引入环境可以参照如下:

https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/108592418

在此基础上,在src下新建包com.badao.NettyWebSocket

然后新建服务端类WebSocketServer

package com.badao.NettyWebSocket;

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 io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

public class WebSocketServer {
    public static void main(String[] args) throws  Exception
    {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO))
                    .childHandler(new WebSocketInitializer());
            //绑定端口
            ChannelFuture channelFuture = serverBootstrap.bind(70).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            //关闭事件组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

服务端的搭建在上面已经讲解,这里又添加了Netty自带的日志处理器LoggingHandler

然后又添加了自定义的初始化器WebSocketInitializer

所以新建类WebSocketInitializer

package com.badao.NettyWebSocket;

import io.netty.channel.Channel;
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 WebSocketInitializer  extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(8192));
        pipeline.addLast(new WebSocketServerProtocolHandler("/badao"));

        pipeline.addLast(new WebSocketHandler());
    }
}

因为Netty也是基于Http的所以这里需要添加HttpServerCodec处理器等。

要想实现WebSocket功能,主要是添加了WebSocketServerProtocolHandler这个WebSocket服务端协议处理器。注意这里的参数需要添加一个WebSocket的路径,这里是/badao

最后添加自定义的处理器WebSocketHandler 用来做具体的处理

所以新建类WebSocketHandler

package com.badao.NettyWebSocket;

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

import java.time.LocalDateTime;

public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:"+msg.text());
        ctx.channel().writeAndFlush(new TextWebSocketFrame("WebSocket服务端在"+ LocalDateTime.now()+"发送消息(公众号:霸道的程序猿)"));
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded:"+ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved:"+ctx.channel().id().asLongText());
    }

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

使其继承SimpleChannelInboundHandler注意此时的泛型类型为TextWebSocketFrame

然后重写channelRead0方法用来对收到数据时进行处理

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到消息:"+msg.text());
        ctx.channel().writeAndFlush(new TextWebSocketFrame("WebSocket服务端在"+ LocalDateTime.now()+"发送消息(公众号:霸道的程序猿)"));
    }

在服务端将收到的消息进行输出并给客户端发送数据。

然后重写handlerAdded方法,在客户端与服务端建立连接时进行操作

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded:"+ctx.channel().id().asLongText());
    }

这里通过通道的id方法的asLongText方法获取连接的唯一标志。

然后在服务端输出。

同理重写handlerRemoved方法,在断掉连接时将连接的唯一标志进行输出。

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved:"+ctx.channel().id().asLongText());
    }

最后重写出现异常时的处理方法,在出现异常时将连接关闭。

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

至此WebSocket服务端搭建完成,然后客户端通过JS就能实现。

在项目目录下src下新建webapp目录,在此目录下新建badao.html

 

Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

修改html的代码为

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>公众号:霸道的程序猿</title>
</head>
<body>
<script type="text/javascript">
    var socket;
    if(window.WebSocket)
    {
        socket = new WebSocket("ws://localhost:70/badao")
        socket.onmessage=function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value+"\n"+ev.data;
        }

        socket.onopen = function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = "连接开启";
        }

        socket.onclose = function (ev) {
            var ta = document.getElementById("responseText");
            ta.value = ta.value+"\n连接关闭";
        }
    }
    else{
        alert("当前浏览器不支持WebSocket")
    }

    function send(message) {
        if(!window.WebSocket)
        {
            return;
        }else
        {
            if(socket.readyState = WebSocket.OPEN)
            {
                socket.send(message);
            }
            else
            {
                alert("连接尚未开启");
            }
        }
    }
</script>
<form>
    <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: 200px">

    </textarea>
</form>
</body>
</html>

在js中通过windows.WebSocket判断是否支持WebSocket

如果支持则

socket = new WebSocket("ws://localhost:70/badao")

建立连接,url的写法前面的ws://是固定的类似http://

后面的是跟的ip:端口号/上面配置的WebSocket路径

然后就是以这个WebSocket对象为中心进行连接和数据的显示。

下面的回调方法onopen会在建立连接成功后回调,onclose会在断掉连接后回调,

onmessage会在收到服务端发送的数据时回调并通过ev.data获取数据。

客户端向服务端发送数据时调用的是socket的send方法,通过

if(socket.readyState = WebSocket.OPEN)

判断连接已经成功建立。

实现长连接通信

运行WebSocketServer的main方法,然后在badao.html上右击选择运行

 

Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

建立连接成功后会在服务端输出连接的id,在客户端会显示连接开启。

此时如果刷新浏览器,服务端会输出一次断开连接和建立连接

 

Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

再将服务端停掉,客户端会输出连接关闭

 

Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

再重新启动服务端,在上面的输入框输入内容并点击发送数据

 

Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

服务端会收到消息并输出,并向客户端发送一个消息。

继续发送也是如此

 

Netty中使用WebSocket实现服务端与客户端的长连接通信发送消息

示例代码下载

https://download.csdn.net/download/BADAO_LIUMANG_QIZHI/12853829

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136538.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!