WebSocket快速上手

WebSocket快速上手文章目录WebSocket快速上手1.WebSocket是什么?1.1.WebSocket连接过程1.2.WebSocket与HTTP对比1.3.WebSocket协议2.快速上手2.1服务端2.2客户端2.2.1浏览器客户端2.2.2Java客户端WebSocket快速上手1.WebSocket是什么?WebSocket建立在TCP协议之上,并且与HTTP协议有着良好的…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

WebSocket快速上手

1. WebSocket是什么?

WebSocket建立在TCP协议之上,并且与HTTP协议有着良好的兼容性,最大的特点就是服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送消息。

在这里插入图片描述

1.1. WebSocket连接过程

  1. 客户端通过HTTP协议向服务端发送握手,服务端向客户端返回ACK,此时握手成功,建立连接并维持该连接;
  2. 后面服务端和客户端就可以基于建立的连接进行双向通信,直到连接关闭。

1.2. WebSocket与HTTP对比

HTTP WebSocket
通信方式 单工 全双工
端口 80/443 80/443
协议 HTTP/HTTPS WS/WSS
跨域问题
网络开销 较小

1.3. WebSocket协议

WebSocket的协议标识符是 ws,加密为 wss ,例如:

ws://ip:port/some/path

在这里插入图片描述

2. 快速上手

本次快速上手基于Netty实现。

2.1 服务端

WebSocketServer

主服务用于启动服务端,监听请求,并对请求信息进行解码,调用处理程序 WebSocketHandler 对请求进行处理。

public class WebSocketServer { 
   
    public void run() { 
   
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 5).childOption(ChannelOption.SO_KEEPALIVE, true).childHandler(new ChannelInitializer<SocketChannel>() { 
   
            @Override
            protected void initChannel(SocketChannel channel) throws Exception { 
   
                ChannelPipeline pipeline = channel.pipeline();
                pipeline.addLast("http-codec", new HttpServerCodec());
                pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
                pipeline.addLast("http-chunked", new ChunkedWriteHandler());
                pipeline.addLast("handler", new WebSocketHandler());
            }
        });

        try { 
   
            Channel channel = serverBootstrap.bind(8080).sync().channel();
            channel.closeFuture().sync();
        } catch (InterruptedException e) { 
   
            e.printStackTrace();
        } finally { 
   
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) { 
   
        new WebSocketServer().run();
    }
}

WebSocketHandler

作为请求处理器,主要接收并处理两种请求:

  • 客户端握手请求
  • 客户端消息请求,并给出响应。
public class WebSocketHandler extends ChannelInboundHandlerAdapter { 
   
    private WebSocketServerHandshaker handshaker;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
   
        if (msg instanceof FullHttpRequest) { 
   
            // websocket连接请求
            handleHttpRequest(ctx, (FullHttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) { 
   
            // websocket业务处理
            handleWebSocketRequest(ctx, (WebSocketFrame) msg);
        }
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 
   
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
   
        ctx.close();
    }

    /** * 获取WebSocket服务信息 * * @param req * @return */
    private static String getWebSocketLocation(FullHttpRequest req) { 
   
        String location = req.headers().get("Host") + "/ws";
        return "ws://" + location;
    }

    /** * 接收握手请求,并响应 * * @param ctx * @param request */
    private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) { 
   
        if (!request.decoderResult().isSuccess() || !StringUtils.equals(request.headers().get("Upgrade"), "websocket")) { 
   
            sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            return;
        }

        WebSocketServerHandshakerFactory handshakerFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(request), null, false);
        handshaker = handshakerFactory.newHandshaker(request);
        if (handshaker == null) { 
   
            // 不支持websocket
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else { 
   
            // 获取请求参数
            QueryStringDecoder decoder = new QueryStringDecoder(request.uri());
            Map<String, List<String>> parameters = decoder.parameters();

            String userid = parameters.get("userid").get(0);
            // 通过它构造握手响应消息返回给客户端
            ChannelFuture future = handshaker.handshake(ctx.channel(), request);
            if (future.isSuccess()) { 
   
                String msg = "客户端" + userid + "加入聊天室";
                ctx.channel().writeAndFlush(new TextWebSocketFrame(msg));
            }
        }
    }

    /** * 接收WebSocket请求 * * @param ctx * @param req * @throws Exception */
    private void handleWebSocketRequest(ChannelHandlerContext ctx, WebSocketFrame req) throws Exception { 
   
        if (req instanceof CloseWebSocketFrame) { 
   //关闭socket连接请求
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) req.retain());
            return;
        }
        if (req instanceof PingWebSocketFrame) { 
   
            ctx.channel().write(new PongWebSocketFrame(req.content().retain()));
            return;
        }
        if (!(req instanceof TextWebSocketFrame)) { 
   
            throw new UnsupportedOperationException("当前只支持文本消息,不支持二进制消息");
        }
        if (ctx == null || this.handshaker == null || ctx.isRemoved()) { 
   
            throw new Exception("尚未握手成功,无法向客户端发送WebSocket消息");
        }
        ctx.channel().write(new TextWebSocketFrame(((TextWebSocketFrame) req).text()));
    }

    private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { 
   
        // BAD_REQUEST(400) 客户端请求错误返回的应答消息
        if (res.status().code() != 200) { 
   
            // 将返回的状态码放入缓存中,Unpooled没有使用缓存池
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
            HttpUtil.setContentLength(res, res.content().readableBytes());
        }
        // 发送应答消息
        ChannelFuture cf = ctx.channel().writeAndFlush(res);
        // 非法连接直接关闭连接
        if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) { 
   
            cf.addListener(ChannelFutureListener.CLOSE);
        }
    }
}

2.2 客户端

2.2.1 浏览器客户端

需要浏览器支持。

export default class WebSocketUtils { 
   
    constructor(url) { 
   
        if (!window.WebSocket) { 
   
            window.WebSocket = window.MozWebSocket
        }
        if (window.WebSocket) { 
   
            this.socket = new WebSocket(url)
            this.socket.onopen = function(event) { 
   
                var ta = document.getElementById('responseText')
                ta.value = '连接开启!'
                console.log(event)
            }
            this.socket.onclose = function(event) { 
   
                var ta = document.getElementById('responseText')
                ta.value = ta.value + '连接被关闭'
            }
            this.socket.onmessage = function(event) { 
   
                var ta = document.getElementById('responseText')
                console.log(event.data);
                ta.value = ta.value + '\n' + event.data
            }
        } else { 
   
            alert('你的浏览器不支持 WebSocket!')
        }
        this.send = this.send.bind(this)
    }

    send(message) { 
   
        if (!window.WebSocket) { 
   
            return
        }
        if (this.socket.readyState == WebSocket.OPEN) { 
   
            this.socket.send(message)
        } else { 
   
            alert('连接没有开启.')
        }
    }
}

2.2.2 Java客户端

WebSocketClient

用于与服务端建立通信连接,以及发送信息的主入口。

public class ChatRoomClient { 
   
    private URI uri;
    private Channel channel;
    private ChatRoomClientHandler handler;

    public ChatRoomClient(String uri) { 
   
        this.uri = URI.create(uri);
    }

    public void run() throws Exception { 
   
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        String protocal = uri.getScheme();
        if (!StringUtils.equals(protocal, "ws")) { 
   
            throw new ProtocolException("Unsupported protocal:" + protocal);
        }
        handler = new ChatRoomClientHandler(uri);
        bootstrap.channel(NioSocketChannel.class).group(workerGroup).option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS,20000).handler(new ChannelInitializer<SocketChannel>() { 
   
            @Override
            protected void initChannel(SocketChannel ch) throws Exception { 
   
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast("http-codec", new HttpClientCodec());
                pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
                pipeline.addLast(new ChunkedWriteHandler());
                pipeline.addLast(handler);
            }
        });

        this.channel = bootstrap.connect(uri.getHost(), uri.getPort()).sync().channel();
        ChannelFuture future = handler.handshakerFuture();//handshakerFuture用于等待握手结果,标识握手是否成功
        future.sync();//这里处理同步等待,一直等到握手成功,调用setSuccess()方法才会结束,终止等待状态
    }

    public void close() throws InterruptedException { 
   
        this.channel.writeAndFlush(new CloseWebSocketFrame());
        this.channel.closeFuture().sync();//等待调用close()方法
    }

    public void send(final String text) { 
   
        if (this.handler.handshakerFuture().isSuccess()) { 
   
            this.channel.writeAndFlush(new TextWebSocketFrame(text));
        } else { 
   
            System.out.println("没有握手成功!");
        }
    }
}

WebSocketClientHandler

1、在建立连接成功后发起握手请求;

2、在接收到消息时对消息进行处理。

public class ChatRoomClientHandler extends SimpleChannelInboundHandler<Object> { 
   
    private WebSocketClientHandshaker handshaker;
    private ChannelPromise handshakerFuture;

    public ChatRoomClientHandler(URI uri) { 
   
        this.handshaker = WebSocketClientHandshakerFactory.newHandshaker(uri, WebSocketVersion.V13, null, true, EmptyHttpHeaders.INSTANCE, 1280000);

    }

    public ChannelFuture handshakerFuture() { 
   
        return this.handshakerFuture;
    }

    @Override
    /** * 处理器加入到处理pipeline后,新建握手等待标识Future */
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 
   
        System.out.println("准备好处理事件");
        handshakerFuture = ctx.newPromise();
    }

    @Override
    /** * 连接建立成功后,发起握手请求 */
    public void channelActive(ChannelHandlerContext ctx) throws Exception { 
   
        System.out.println("连接成功!" + ctx.name());
        this.handshaker.handshake(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception { 
   
        System.out.println("连接断开!");
        System.err.println("WebSocket client disconnected!");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 
   
        if (!this.handshakerFuture.isDone()) { 
   
            this.handshakerFuture.cancel(true);
        }
        ctx.close();
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { 
   
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
   
        cause.printStackTrace();
        if (!this.handshakerFuture.isDone()) { 
   
            this.handshakerFuture.setFailure(cause);
        }
        ctx.close();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { 
   
        final Channel channel = ctx.channel();
        if (msg instanceof FullHttpResponse) { 
   
            FullHttpResponse response = (FullHttpResponse) msg;
            if (!response.decoderResult().isSuccess()) { 
   
                throw new ProtocolException("响应内容解析失败!");
            } else if (!this.handshaker.isHandshakeComplete()) { 
   
                this.handshaker.finishHandshake(channel, (FullHttpResponse) msg);
                handshakerFuture.setSuccess();//标识握手成功
                System.out.println("握手成功");
                return;
            }
        }

        if (msg instanceof FullHttpResponse) { 
   
            FullHttpResponse response = (FullHttpResponse) msg;
            System.out.println(response.toString());
        }

        final WebSocketFrame frame = (WebSocketFrame) msg;
        if (frame instanceof TextWebSocketFrame) { 
   
            System.out.println(((TextWebSocketFrame) frame).text());
        } else if (frame instanceof CloseWebSocketFrame) { 
   
            channel.close();
        } else if (frame instanceof PongWebSocketFrame) { 
   
            System.out.println(((PongWebSocketFrame) frame).toString());
        }
    }
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/157636.html原文链接:https://javaforall.net

(0)
上一篇 2022年7月27日 下午4:36
下一篇 2022年7月27日 下午4:36


相关推荐

  • Maven 生成打包可执行jar包

    Maven 生成打包可执行jar包文章目录1.需求2.开发环境3.Maven打包插件介绍4.Maven使用maven-jar-plugin打可执行jar包5.Maven使用maven-assembly-plugin装需要打包的文件打进zip包6.Maven生成可执行jar包及zip项目压缩包7.执行jar包8.pom.xml配置9.package.xml文件最近IDEA打可执行Jar包搞了三天,一直失败,好好学…

    2022年5月30日
    33
  • Hadoop入门(八)——本地运行模式+完全分布模式案例详解,实现WordCount和集群分发脚本xsync快速配置环境变量 (图文详解步骤2021)[通俗易懂]

    Hadoop入门(八)——本地运行模式+完全分布模式案例详解,实现WordCount和集群分发脚本xsync快速配置环境变量 (图文详解步骤2021)[通俗易懂]Hadoop运行模式1)Hadoop官方网站:http://hadoop.apache.org/2)Hadoop运行模式包括:本地模式、伪分布式模式以及完全分布式模式。本地模式:单机运行,只是用来演示一下官方案例。生产环境不用。伪分布式模式:也是单机运行,但是具备Hadoop集群的所有功能,一台服务器模拟一个分布式的环境。个别缺钱的公司用来测试,生产环境不用。完全分布式模式:多台服务器组成分布式环境。生产环境使用。本地运行模式(官方WordCount案例)1

    2022年6月2日
    44
  • VUE父组件与子组件的数据传递

    VUE父组件与子组件的数据传递父组件与子组件侦听 watch 引用基本数据类型 引用的是地址 地址里面存放值 基本数据类型 引用的是值 每一个值所对应的地址是不一样的 侦听的本质是 侦听地址是否发生改变 watch 侦听基本数据类型 value function console log 侦听到基本数据类型 侦听引用数据类型 obj function console log 侦听到引用数据类型

    2026年3月17日
    2
  • 适配器模式(Adapter模式)详解

    适配器模式(Adapter模式)详解http://c.biancheng.net/view/1361.html在现实生活中,经常出现两个对象因接口不兼容而不能在一起工作的实例,这时需要第三者进行适配。例如,讲中文的人同讲英文的人对话时需要一个翻译,用直流电的笔记本电脑接交流电源时需要一个电源适配器,用计算机访问照相机的SD内存卡时需要一个读卡器等。在软件设计中也可能出现:需要开发的具有某种业务功能的组件在现有的组件库中已经存在,但它们与当前系统的接口规范不兼容,如果重新开发这些组件成本又很高,这时用适配器模式能很好地解决这些问题。

    2022年7月25日
    13
  • phpstorm激活码2021.3破解方法

    phpstorm激活码2021.3破解方法,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月15日
    118
  • pycharm python interpreter_python中assertionerror解决

    pycharm python interpreter_python中assertionerror解决Python里SQLAlchemy运行时报错InvalidRequestE:VARCHAR requires a length on dialect mysql

    2022年4月21日
    135

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号