websocket之旅

 一次偶然的机会在群里有人提问到这样的问题,一台socket通信服务器,用其他客户端联接正常,用websocket就不行。

于是自己写了个程序验证一下,也就开始了websocket 的hello word之旅。

先了解下websocket的通信原理。

 

这里简单说明一下WebSocket握手的过程。

Web应用程序调用new WebSocket(url)接口时,Browser就开始了与地址为urlWebServer建立握手连接的过程。

1.   TCP握手阶段, BrowserWebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。

2.   websocket 握手阶段,  TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。

例如:

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==

Origin: http://example.com

Sec-WebSocket-Protocol: chat,superchat

Sec-WebSocket-Version: 13

3.     WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。

HTTP/1.1 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Protocol: chat

4.     Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到 onerror消息,并且能知道连接失败的原因。

 

第一步是走TCP底层三次握手,第二步和第三步是http协议。

回到之前的问题:一台socket通信服务器,用其他客户端联接正常,用websocket就不行。

是因为其他客户端没有第二步和第三步的websocket握手,直接进入传输业务数据,用websocket传输需要websocket握手,socket服务端把握手的报文当做业务数据格式处理,服务端报错了,服务端没有响应websocket握手包给websocket客户端,websocket客户端当做连接失败处理。

 

要让socket服务端支持websocket的通讯,必须先支持第二步和第三步的websocket握手,接下来看netty是怎么支持的。

以netty 5.0版本为例

   privateclassChildChannelHandler extends ChannelInitializer<SocketChannel> {

 

        @Override

        protected void initChannel(SocketChannel ch) throws Exception {

            ch.pipeline().addLast("http-codec",newHttpServerCodec());

            ch.pipeline().addLast("aggregator",newHttpObjectAggregator(65536)) ;

        ch.pipeline().addLast("http-chunked",newChunkedWriteHandler());

        ch.pipeline().addLast("handler",newWebSocketServerHandler());         

        }

 

   }

Websocket握手是基于http协议,先添加一些http编解码器。下面是WebSocketServerHandler的实现

 

public classWebSocketServerHandler extends SimpleChannelInboundHandler<Object>{

 

   /**

     * 日志

     */

   privatestaticfinalLogger logger=

           Logger.getLogger(WebSocketServerHandler.class.getName());

   /**

     * 全局websocket

     */

   privateWebSocketServerHandshaker handshaker;

   

   @Override

   protectedvoidmessageReceived(ChannelHandlerContext ctx, Object msg)

            throws Exception {

        //普通HTTP接入

        if(msg instanceof FullHttpRequest){

            handleHttpRequest(ctx,(FullHttpRequest) msg);

        }else if(msg instanceofWebSocketFrame){ //websocket帧类型已连接

            handleWebSocketFrame(ctx,(WebSocketFrame) msg);

        }

   }

   

   @Override

   publicvoidchannelReadComplete(ChannelHandlerContext ctx) throws Exception {

        ctx.flush();

   }

   

   privatevoidhandleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){

        //如果http解码失败则返回http异常并且判断消息头有没有包含Upgrade字段(协议升级)

        if(!request.decoderResult().isSuccess()

                || (!"websocket".equals(request.headers().get("Upgrade")))    ){

            sendHttpResponse(ctx,request, newDefaultFullHttpResponse(

                    HttpVersion.HTTP_1_1,HttpResponseStatus.BAD_REQUEST));

            return ;

        }

        //构造握手响应返回

        WebSocketServerHandshakerFactory ws = newWebSocketServerHandshakerFactory("", null,false);

        handshaker = ws.newHandshaker(request);

        if(handshaker == null){

            //版本不支持

            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());

        }else{

            handshaker.handshake(ctx.channel(), request);

        }

   }

   /**

     * websocket

     * @param ctx

     * @param frame

     */

   privatevoidhandleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){

        //判断是否关闭链路指令

        if(frame instanceof CloseWebSocketFrame){

            handshaker.close(ctx.channel(),(CloseWebSocketFrame) frame.retain());

            return ;

        }

        //判断是否Ping消息 -- ping/pong心跳包

        if(frame instanceof PingWebSocketFrame){

            ctx.channel().write(newPongWebSocketFrame(frame.content().retain()));

            return ;

        }

        //本程序仅支持文本消息,不支持二进制消息

        if(!(frame instanceof TextWebSocketFrame)){

            throw new UnsupportedOperationException(

                   String.format("%s frame types not supported", frame.getClass().getName()));

        }

       

        //返回应答消息 text文本帧

        String request = ((TextWebSocketFrame)frame).text();

        //打印日志

        if(logger.isLoggable(Level.FINE)){

            logger.fine(String.format("%s received %s",ctx.channel(), request));

        }

        //发送到客户端websocket

        ctx.channel().write(newTextWebSocketFrame(request

                + ", 欢迎使用Netty WebSocket服务,现在时刻:"

                + new java.util.Date().toString()));

   }

   

   /**

     * response

     * @param ctx

     * @param request

     * @param response

     */

   privatestaticvoidsendHttpResponse(ChannelHandlerContext ctx,

            FullHttpRequest request,FullHttpResponse response){

        //返回给客户端

   

        if(response.status().code() !=HttpResponseStatus.OK.code()){

            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(),CharsetUtil.UTF_8);

           response.content().writeBytes(buf);

            buf.release();

            HttpHeaderUtil.setContentLength(response,response.content().readableBytes());

        }

        //如果不是keepalive那么就关闭连接

        ChannelFuture f =ctx.channel().writeAndFlush(response);

        if(!HttpHeaderUtil.isKeepAlive(response)

                || response.status().code() !=HttpResponseStatus.OK.code()){

           f.addListener(ChannelFutureListener.CLOSE);

        }

      

   }

   

   /**

     * 异常出错

     */

   @Override

   publicvoidexceptionCaught(ChannelHandlerContext ctx, Throwable cause)

            throws Exception {

        cause.printStackTrace();

        ctx.close();

   }

}

 

重点讲述下messageReceived的实现

   protectedvoidmessageReceived(ChannelHandlerContext ctx, Object msg)

            throws Exception {

        //普通HTTP接入,websocket握手协议是http请求,返回websocket握手包

        if(msg instanceof FullHttpRequest){

            handleHttpRequest(ctx,(FullHttpRequest) msg);

        }else if(msg instanceofWebSocketFrame){ //websocket连接已建立websocket帧类型编解码

            handleWebSocketFrame(ctx,(WebSocketFrame) msg);

        }

   }

   

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值