Netty篇(WebSocket)

目录

一、简介

二、特点

三、websock应用场景

四、websocket案例

[1. 服务端](#1. 服务端)

[2. 处理器](#2. 处理器)

[3. 页面端处理](#3. 页面端处理)

五、参考文献


一、简介

没有其他技术能够像WebSocket一样提供真正的双向通信,许多web开发者仍然是依赖于ajax的长轮询来

实现。(注:我认为长轮询是富于创造性和多功能性的,虽然这只是一个不太完美的解决办法(hack))

对Websocket缺少热情,也许是因为多年前他的安全性的脆弱,抑或者是缺少浏览器的支持,不管怎样,

这两个问题都已经被解决了。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动

向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。在WebSocket API中,浏览器

和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接

可以数据互相传送

二、特点

Websocket是应用层第七层上的一个应用层协议,它必须依赖HTTP协议进行一次握手 ,握手成功后,数

据就直接从TCP通道传输,与HTTP无关了。Websocket的数据传输是frame形式传输的,比如会将一条消

息分为几个frame,按照先后顺序传输出去。

这样做会有几个好处:

  • 建立在 TCP 协议之上,服务器端的实现比较容易。
  • 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手

时不容易屏蔽,能通过各种HTTP 代理服务器。

  • 数据格式比较轻量,性能开销小,通信高效。
  • 可以发送文本,也可以发送二进制数据。
  • 没有同源限制,客户端可以与任意服务器通信。
  • 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
  • 大数据的传输可以分片传输,不用考虑到数据大小导致的长度标志位不足够的情况。
  • 和http的chunk一样,可以边生成数据边传递消息,即提高传输效率。

三、websock应用场景

既然http无法满足用户的所有需求,那么为之诞生的websocket必然有其诸多应用场景。

如:

  • 实时显示网站在线人数
  • 账户余额等数据的实时更新
  • 多玩家网络游戏
  • 多媒体聊天,如:聊天室

四、websocket案例

1. 服务端

public class WebSocketServer {
    public static void main(String[] args) {

        //创建线程组  负责接收
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //线程组, 负责(读写)工作线程
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        //创建启动类
        ServerBootstrap bootstrap = new ServerBootstrap();
        //指定启动的参数
        bootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128) //option指的是bossGroup里面的可选项
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        //在通道的流水线上加入一个通道处理器
                        ch.pipeline().addLast("logging", new LoggingHandler())//设置log监听器,并且日志级别为debug,方便观察运行流程
                                    .addLast("http-codec", new HttpServerCodec())//设置解码器
                                    .addLast("aggregator", new HttpObjectAggregator(65536))//聚合器,使用websocket会用到
                                    .addLast("http-chunked", new ChunkedWriteHandler())//用于大数据的分区传输
                                    .addLast("hanlder", new WebSocketHandler());
                    }
                });
        System.out.println("===============服务器启动了==================");
        try {
            //启动的时候绑定端口,返回ChannelFuture,异步启动
            ChannelFuture channelFuture = bootstrap.bind(8081).sync();

            //添加监听器
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        System.out.println("端口: 8081启动成功");
                    } else {
                        System.out.println("端口: 8081启动失败");
                    }
                }
            });

            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }

    }
}

2. 处理器

public class WebSocketHandler extends SimpleChannelInboundHandler<Object> {

    //设置通道组
    static ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //日志对象
    private final Logger logger = Logger.getLogger(this.getClass());

    //创建握手对象
    private WebSocketServerHandshaker handshaker;

    /**
     * 通道激活
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        logger.debug(ctx.channel().remoteAddress()+"加入群聊");
        group.add(ctx.channel());
    }

    /**
     * 通道销毁
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.debug(ctx.channel().remoteAddress()+"退出群聊");
        group.add(ctx.channel());
    }

    /**
     * 服务器读处理
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.debug("收到消息");
        if(msg instanceof FullHttpRequest){
            //以http请求方式接入,升级websocket
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        }
        if(msg instanceof WebSocketFrame){
            //处理websocket客户端的消息
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }


    /**
     * websocket处理
     * @param ctx
     * @param frame
     */
    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
        Channel channel = ctx.channel();

        if(frame instanceof CloseWebSocketFrame){
            handshaker.close(channel, (CloseWebSocketFrame)frame.retain());
            return;
        }
        if(frame instanceof PingWebSocketFrame){
            channel.write(new PongWebSocketFrame(frame.content().retain()));
            return;
        }
        if(frame instanceof TextWebSocketFrame){
            String text = ((TextWebSocketFrame) frame).text();
            logger.debug("服务端收到:"+text);
            TextWebSocketFrame tws = new TextWebSocketFrame(ctx.channel().remoteAddress()+"说:"+text);
            group.writeAndFlush(tws);
        }else{
            logger.debug("不支持非文本格式");
            throw new UnsupportedOperationException("不支持非文本格式"+ctx.channel().id());
        }
    }

    /**
     * http请求处理
     * @param ctx
     * @param req
     */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
        //如果请求解码失败,或者不是websocket
        if(req.decoderResult().isFailure() || !"websocket".equals(req.headers().get("Upgrade"))){
            sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }
        //创建websocket握手工厂
        WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory("ws://localhost:8081/websocket", null, false);
        //创建新的握手
        handshaker = factory.newHandshaker(req);
        if(handshaker == null){
            //不支持
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        }else{
            //握手
            handshaker.handshake(ctx.channel(), req);
        }
    }

    //发送响应
    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse resp){
        //如果响应码不是200
        if(resp.status().code() != 200){
            //创建错误状态码数据
            ByteBuf buf = Unpooled.copiedBuffer(resp.status().toString(), CharsetUtil.UTF_8);
            resp.content().writeBytes(buf);
            buf.release();
        }
        //回写状态码
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(resp);
        if(!HttpUtil.isKeepAlive(req) || resp.status().code() != 200){
            channelFuture.addListener(ChannelFutureListener.CLOSE);
        }
    }


}

3. 页面端处理

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    var socket;
    //判断当前浏览器是否支持websocket
    if(window.WebSocket) {
        //go on
        socket = new WebSocket("ws://localhost:7000/hello2");
        //相当于channelReado, ev 收到服务器端回送的消息
        socket.onmessage = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + ev.data;
        }

        //相当于连接开启(感知到连接开启)
        socket.onopen = function (ev) {
            var rt = document.getElementById("responseText");
            rt.value = "连接开启了.."
        }

        //相当于连接关闭(感知到连接关闭)
        socket.onclose = function (ev) {

            var rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + "连接关闭了.."
        }
    } else {
        alert("当前浏览器不支持websocket")
    }

    //发送消息到服务器
    function send(message) {
        if(!window.socket) { //先判断socket是否创建好
            return;
        }
        if(socket.readyState == WebSocket.OPEN) {
            //通过socket 发送消息
            socket.send(message)
        } else {
            alert("连接没有开启");
        }
    }
</script>
    <form onsubmit="return false">
        <textarea name="message" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="发生消息" onclick="send(this.form.message.value)">
        <textarea id="responseText" style="height: 300px; width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    </form>
</body>
</html>

五、参考文献

相关推荐
njnu@liyong4 小时前
图解HTTP-HTTP状态码
网络协议·计算机网络·http
maimang094 小时前
关于UDP缓冲区和丢包统计
网络
tjjingpan4 小时前
HCIA-Access V2.5_4_2_静态路由介绍
网络
这题怎么做?!?6 小时前
ARP协议及其具体过程
运维·服务器·网络
无线认证x英利检测6 小时前
进网许可认证、交换路由设备检测项目更新25年1月起
网络·智能路由器
卡卡大怪兽6 小时前
fastAPI接口的请求与响应——基础
服务器·网络·fastapi
昌sit!6 小时前
监控IP频繁登录服务器脚本
服务器·网络·tcp/ip
代码洁癖症患者7 小时前
HTTP请求的奇幻旅程:从发起至响应的全方位探索
网络·网络协议·http
凹凸撒man7 小时前
AUTOSAR TCP中的MSS和MTU的关系
网络·tcp/ip·autosar
岳不谢7 小时前
华为DHCP高级配置学习笔记
网络·笔记·网络协议·学习·华为