java_websocket详解(二)

本文详细解析了WebSocket协议中的握手过程及消息发送流程,包括客户端和服务端如何生成和验证Sec-WebSocket-Key1与Sec-WebSocket-Key2等关键字段。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇讲解一下draft_10,draft_17,这一篇讲解一下draft_76。
GET /chat HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: localhost:8080             (客户端请求主机)
Origin: http://127.0.0.1         (来源网页地址)
Sec-WebSocket-Key1: 23 asdfJKj,asdjk
Sec_WebSocket-Key2: wewerw234 jij998

draft_75是没有Sec-WebSocket-Key1和Sec-WebSocket-Key2这两个值得。”/chat”一般为uri的path或者query,首先客户端是发一个握手请求,握手的请求是没有数据的,java_websocket是随机生成的一个数据。

public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) {
        request.put( "Upgrade", "WebSocket" );
        request.put( "Connection", "Upgrade" );
        request.put( "Sec-WebSocket-Key1", generateKey() );
        request.put( "Sec-WebSocket-Key2", generateKey() );

        if( !request.hasFieldValue( "Origin" ) ) {
            request.put( "Origin", "random" + reuseableRandom.nextInt() );
        }

        byte[] key3 = new byte[ 8 ];
        reuseableRandom.nextBytes( key3 );
        request.setContent( key3 );
        return request;

    }
private static String generateKey() {
        Random r = new Random();
        long maxNumber = 4294967295L;
        long spaces = r.nextInt( 12 ) + 1;
        int max = new Long( maxNumber / spaces ).intValue();
        max = Math.abs( max );
        int number = r.nextInt( max ) + 1;
        long product = number * spaces;
        String key = Long.toString( product );
        // always insert atleast one random character
        int numChars = r.nextInt( 12 ) + 1;
        for( int i = 0 ; i < numChars ; i++ ) {
            int position = r.nextInt( key.length() );
            position = Math.abs( position );
            char randChar = (char) ( r.nextInt( 95 ) + 33 );
            // exclude numbers here
            if( randChar >= 48 && randChar <= 57 ) {
                randChar -= 15;
            }
            key = new StringBuilder( key ).insert( position, randChar ).toString();
        }
        for( int i = 0 ; i < spaces ; i++ ) {
            int position = r.nextInt( key.length() - 1 ) + 1;
            position = Math.abs( position );
            key = new StringBuilder( key ).insert( position, "\u0020" ).toString();
        }
        return key;
    }

意思就是生成有数字,字母,空格的字符串。

public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) {
        if( handshakedata.getFieldValue("Upgrade" ).equals("WebSocket") 
&& handshakedata.getFieldValue("Sec-WebSocket-Key1" ).length()>0 
&& !handshakedata.getFieldValue( "Sec-WebSocket-Key2" ).isEmpty() && handshakedata.hasFieldValue( "Origin" ) )
            return HandshakeState.MATCHED;
        return HandshakeState.NOT_MATCHED;
    }

也就是说,服务端只要验证握手请求有Upgrade,Sec-WebSocket-Key1,Sec-WebSocket-Key2,Origin这几个字段就可以了,然后服务端回一个握手请求,握手成功。而服务端接收到这个这个握手请求,没有取这个content的。

HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
Sec-WebSocket-Origin: http://127.0.0.1     (来源网页地址)
Sec-WebSocket-Location: ws://localhost:8080/WebSocket/LiveVideo

对应的代码如下:

public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException {
        response.setHttpStatusMessage( "WebSocket Protocol Handshake" );
        response.put( "Upgrade", "WebSocket" );
        response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive
        response.put( "Sec-WebSocket-Origin", request.getFieldValue( "Origin" ) );
        String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor();
        response.put( "Sec-WebSocket-Location", location );
        String key1 = request.getFieldValue( "Sec-WebSocket-Key1" );
        String key2 = request.getFieldValue( "Sec-WebSocket-Key2" );
        byte[] key3 = request.getContent();
        if( key1 == null || key2 == null || key3 == null || key3.length != 8 ) {
            throw new InvalidHandshakeException( "Bad keys" );
        }
        response.setContent( createChallenge( key1, key2, key3 ) );
        return response;
    }

“WebSocket Protocol Handshake”这个完全是response.setHttpStatusMessage( “WebSocket Protocol Handshake” );代码写上去的,握手的时候,把客户端发的content进行了一些运算然后发回去了。
再看一下握手成功之后发消息。

    public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) {
        if( failed ) {
            return HandshakeState.NOT_MATCHED;
        }

        try {
            if( !response.getFieldValue( "Sec-WebSocket-Origin" ).equals( request.getFieldValue( "Origin" ) ) || !basicAccept( response ) ) {
                return HandshakeState.NOT_MATCHED;
            }
            byte[] content = response.getContent();
            if( content == null || content.length == 0 ) {
                throw new IncompleteHandshakeException();
            }
            if( Arrays.equals( content, createChallenge( request.getFieldValue( "Sec-WebSocket-Key1" ), request.getFieldValue( "Sec-WebSocket-Key2" ), request.getContent() ) ) ) {
                return HandshakeState.MATCHED;
            } else {
                return HandshakeState.NOT_MATCHED;
            }
        } catch ( InvalidHandshakeException e ) {
            throw new RuntimeException( "bad handshakerequest", e );
        }
    }

key1,key2的值进行了一些运算跟发送的内容相同,服务器就会处理那个消息。算法如下:

public static byte[] createChallenge( String key1, String key2, byte[] key3 ) throws InvalidHandshakeException {
        byte[] part1 = getPart( key1 );
        byte[] part2 = getPart( key2 );
        byte[] challenge = new byte[ 16 ];
        challenge[ 0 ] = part1[ 0 ];
        challenge[ 1 ] = part1[ 1 ];
        challenge[ 2 ] = part1[ 2 ];
        challenge[ 3 ] = part1[ 3 ];
        challenge[ 4 ] = part2[ 0 ];
        challenge[ 5 ] = part2[ 1 ];
        challenge[ 6 ] = part2[ 2 ];
        challenge[ 7 ] = part2[ 3 ];
        challenge[ 8 ] = key3[ 0 ];
        challenge[ 9 ] = key3[ 1 ];
        challenge[ 10 ] = key3[ 2 ];
        challenge[ 11 ] = key3[ 3 ];
        challenge[ 12 ] = key3[ 4 ];
        challenge[ 13 ] = key3[ 5 ];
        challenge[ 14 ] = key3[ 6 ];
        challenge[ 15 ] = key3[ 7 ];
        MessageDigest md5;
        try {
            md5 = MessageDigest.getInstance( "MD5" );
        } catch ( NoSuchAlgorithmException e ) {
            throw new RuntimeException( e );
        }
        return md5.digest( challenge );
    }
    private static byte[] getPart( String key ) throws InvalidHandshakeException {
        try {
            long keyNumber = Long.parseLong( key.replaceAll( "[^0-9]", "" ) );
            long keySpace = key.split( "\u0020" ).length - 1;
            if( keySpace == 0 ) {
                throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)" );
            }
            long part = new Long( keyNumber / keySpace );
            return new byte[]{ (byte) ( part >> 24 ), (byte) ( ( part << 8 ) >> 24 ), (byte) ( ( part << 16 ) >> 24 ), (byte) ( ( part << 24 ) >> 24 ) };
        } catch ( NumberFormatException e ) {
            throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)" );
        }
    }
算法步骤key1的逻辑:取出key1中的数字keyNumber跟空格的个数keySpace,相除之后得到long型part,转成byte,例如最后算出来part=0xa123e456,得到的就是[a1,23,e4,56]。key2的算法也一下,把key1,key2,key3串起来就是。
总结,websocket协议网络上一抓一大把,没有的版本不尽相同,对协议没有进行归纳,看起来很头疼。现在协议最新的是draft_17也是最多的。draft_76比draft_17复杂多了。
传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)和Comet技术。其实后者本质上也是一种轮询,只不过有所改进。 轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。 Comet技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。 这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。 伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过JavaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocketJava Web中的实现。 环境:jdk1.8.0_111,apache-tomcat-8.0.51
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值