上一篇讲解一下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复杂多了。