今天看tomcat 源碼的時候看見websocket了,之前在php弄websocket的時候也研究了一下,不過它的frame一看是字節與過來與過去直接就無視了,今天又重新研究了一下。
websocket 的協議在RFC6455中 http://tools.ietf.org/html/rfc6455#section-5.1
幀格式為:
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-------+-+-------------+-------------------------------+ |F|R|R|R| opcode|M| Payload len | Extended payload length | |I|S|S|S| (4) |A| (7) | (16/64) | |N|V|V|V| |S| | (if payload len==126/127) | | |1|2|3| |K| | | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + | Extended payload length continued, if payload len == 127 | + - - - - - - - - - - - - - - - +-------------------------------+ | |Masking-key, if MASK set to 1 | +-------------------------------+-------------------------------+ | Masking-key (continued) | Payload Data | +-------------------------------- - - - - - - - - - - - - - - - + : Payload Data continued ... : + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + | Payload Data continued ... | +---------------------------------------------------------------+
FIN: 1 bit Indicates that this is the final fragment in a message. The first fragment MAY also be the final fragment. RSV1, RSV2, RSV3: 1 bit each MUST be 0 unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is received and none of the negotiated extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_.
Opcode: 4 bits
Defines the interpretation of the "Payload data". If an unknown
opcode is received, the receiving endpoint MUST _Fail the
WebSocket Connection_. The following values are defined.
Mask: 1 bit
Defines whether the "Payload data" is masked. If set to 1, a masking key is present in masking-key, and this is used to unmask the "Payload data" . All frames sent from client to server have this bit set to 1
Payload length: 7 bits, 7+16 bits, or 7+64 bits
The length of the "Payload data", in bytes: if 0-125, that is the
payload length. If 126, the following 2 bytes interpreted as a
16-bit unsigned integer are the payload length. If 127, the
following 8 bytes interpreted as a 64-bit unsigned integer (the
most significant bit MUST be 0) are the payload length. Multibyte
length quantities are expressed in network byte order. Note that
in all cases, the minimal number of bytes MUST be used to encode
the length, for example, the length of a 124-byte-long string
can't be encoded as the sequence 126, 0, 124. The payload length
is the length of the "Extension data" + the length of the
"Application data". The length of the "Extension data" may be
zero, in which case the payload length is the length of the
"Application data".
Masking-key: 0 or 4 bytes All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame. This field is present if the mask bit is set to 1 and is absent if the mask bit is set to 0. See Section 5.3 for further information on client- to-server masking. Payload data: (x+y) bytes The "Payload data" is defined as "Extension data" concatenated with "Application data". Extension data: x bytes The "Extension data" is 0 bytes unless an extension has been negotiated. Any extension MUST specify the length of the "Extension data", or how that length may be calculated, and how the extension use MUST be negotiated during the opening handshake. If present, the "Extension data" is included in the total payload length. Application data: y bytes Arbitrary "Application data", taking up the remainder of the frame after any "Extension data". The length of the "Application data" is equal to the payload length minus the length of the "Extension data".
下面是tomcat中的WSFrame類,在org\apache\catalina\websocket 中
private WsFrame(byte first, UpgradeProcessor<?> processor) throws IOException { //RFC6455文檔解釋了frame的格式 http://tools.ietf.org/html/rfc6455#section-5.1 int b = first & 0xFF; //轉換成32位 fin = (b & 0x80) > 0; //得到第8位 10000000>0 rsv = (b & 0x70) >>> 4; //得到5、6、7 為01110000 然后右移四位為00000111 opCode = (byte) (b & 0x0F); //后四位為opCode 00001111 b = blockingRead(processor); //向后讀取一個字節 // Client data must be masked if ((b & 0x80) == 0) { //第9為為mask,必須為1 throw new IOException(sm.getString("frame.notMasked")); } payloadLength = b & 0x7F; //讀取后7位 Payload legth,如果為125則payloadLength if (payloadLength == 126) { //為126讀2個字節,后兩個字節為payloadLength byte[] extended = new byte[2]; blockingRead(processor, extended); payloadLength = Conversions.byteArrayToLong(extended); //讀取的2個字節轉換成LONG類型 } else if (payloadLength == 127) { //127讀8個字節,后8個字節為payloadLength byte[] extended = new byte[8]; blockingRead(processor, extended); payloadLength = Conversions.byteArrayToLong(extended);//8個字節轉換成LONG } if (isControl()) { if (payloadLength > 125) { throw new IOException(); } if (!fin) { throw new IOException(); } } blockingRead(processor, mask);//接着為MASK-KEY:0or4個字節,mask為1讀4個字節 if (isControl()) { // Note: Payload limited to <= 125 bytes by test above payload = ByteBuffer.allocate((int) payloadLength); blockingRead(processor, payload); //讀取payloadLength個字節長度放進payload if (opCode == Constants.OPCODE_CLOSE && payloadLength > 2) { // Check close payload - if present - is valid UTF-8 CharBuffer cb = CharBuffer.allocate((int) payloadLength); Utf8Decoder decoder = new Utf8Decoder(); //將字節解碼成UTF-8格式 payload.position(2); CoderResult cr = decoder.decode(payload, cb, true);//從第3個開始 payload.position(0); if (cr.isError()) { throw new IOException(sm.getString("frame.invalidUtf8")); } } } else { payload = null; } }
private int blockingRead(UpgradeProcessor<?> processor) throws IOException { int result = processor.read(); if (result == -1) { throw new IOException(sm.getString("frame.eos")); } return result; } /* * Blocks until the byte array has been filled. */ private void blockingRead(UpgradeProcessor<?> processor, byte[] bytes) //向后讀bytes.length個字節 throws IOException { int read = 0; int last = 0; while (read < bytes.length) { last = processor.read(true, bytes, read, bytes.length - read); if (last == -1) { throw new IOException(sm.getString("frame.eos")); } read += last; } } /* * Intended to read whole payload and blocks until it has. Therefore able to * unmask the payload data. */ private void blockingRead(UpgradeProcessor<?> processor, ByteBuffer bb) throws IOException { int last = 0; while (bb.hasRemaining()) { last = processor.read(); if (last == -1) { throw new IOException(sm.getString("frame.eos")); } bb.put((byte) (last ^ mask[bb.position() % 4])); } bb.flip(); }