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

原文地址:https://www.cnblogs.com/lzxin/p/10054334.html