Websocket 프로토콜의 원리와 실현(一)
하나의 채팅 시스템은 복잡하지도 복잡하지도 않지만 안정적인 시스템을 실현하려면 고려해야 할 일이 매우 많다.가장 기본적인 것은 채팅 협의의 처리다.흔히 볼 수 있는 실시간 통신 프로토콜은 XMPP,Websocket이다. 대기업들은 일반적으로 스스로 프로토콜을 정의한다. 예를 들어 텐센트, 왕이 같은 것들은 모두 자신의 프로토콜을 사용한다.내가 본 원본 코드는 Leancloud의 실시간 통신 구성 요소이다. 그들의 채팅은 Websocket에 기반을 두고 있기 때문에 이 블로그의 주제는 Websocket이다.Leancloud의 안드로이드 실시간 통신 구성 요소에서 Websocket의 봉인은 Github의 소스 프로젝트인 Nathan Rajlich의 Java-Websocket입니다. 이것은'100% 자바가 쓴 간단한 Websocket 클라이언트와 서버 구현'입니다.
문장의 대략적인 틀
Websocket은 하나의 TCP 연결에서 듀플렉스 통신을 하는 프로토콜로 듀플렉스(duplex)는 두 통신 설비 간에 양방향의 자료 전송을 허용하는 것을 말한다.전이중은 두 설비 간에 양방향 자료 전송을 동시에 허용하는 것을 말한다.이것은 반이중에 비해 반이중은 양방향 전송을 동시에 할 수 없다. 이 기간의 차이는 휴대전화와 무전기의 차이에 해당한다. 휴대전화는 말을 하는 동시에 상대방의 말을 들을 수 있고 무전기는 하나만 말하고 다른 하나만 말할 수 있다.
긴 말은 짧게 말하자면 Websocket 프로토콜에서 클라이언트와 서비스 측은 악수하는 동작만 하면 하나의 통로를 형성하고 양자간에 데이터를 서로 전송할 수 있다.
WebSocket 프로토콜은
클라이언트에서 요청 보내기
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: null
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
서버 응답
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Origin: null
Sec-WebSocket-Location: ws://example.com/
악수를 할 때 클라이언트는 무작위로 Sec-WebSocket-Key를 보내는데 서비스 측은 이 키에 따라 처리를 하고 Sec-WebSocket-Accept의 값을 클라이언트에게 되돌려준다. 구체적인 원리는 뒷글에서 구체적으로 설명한다.
데이터 전송
이것은 Websocket의 데이터 전송 프로토콜이다. 채팅 정보는 일반적으로 이 프로토콜의 규칙에 따라 전송된다. 아래 그림의 모든 것을 하나의 데이터 프레임이라고 하는데 데이터 프레임의 프레임과 해석은 이 프로토콜을 처리할 때 가장 번거로운 부분이다.구체적으로 이 시계는 어떻게 보면 참조할 수 있다
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 ... |
+---------------------------------------------------------------+
bit
FIN 1bit
RSV 1-3 1bit each 0
Opcode 4bit ,
Mask 1bit , , 1 ( )
Payload 7bit
Masking-key 1 or 4 bit
Payload data (x + y) bytes
Extension data x bytes
Application data y bytes
프로토콜의 봉인과 전송
1. 악수 프로토콜의 봉인과 전송
Handshake 클래스는 요청 헤드에 따라
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: null
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
이 요청 헤더의 필드 순서가 임의이기 때문에, 우리는 하나의 맵으로 메시지를 저장할 수 있으며, 메시지를 보낼 때 Socket의 출력 흐름을 쓸 수 있다
다음은 Handshakedata 클래스입니다. 글을 쉽게 읽을 수 있도록 코드를 간소화했습니다.
public class Handshakedata
{
private byte[] content; // ,
private TreeMap map; //
}
악수 요청 헤더를 초기화하여 코드를 쉽게 이해하기 위해 자바-Websocket의 코드를 약간 수정했습니다.
public Handshakedata postProcessHandshakeRequestAsClient(Handshakedata request)
{
request.put("Upgrade", "websocket");
request.put("Connection", "Upgrade");
request.put("Sec-WebSocket-Version", "8");
byte[] random = new byte[16];
this.reuseableRandom.nextBytes(random); // Sec-WebSocket-Key
request.put("Sec-WebSocket-Key", Base64.encodeBytes(random));
return request;
}
데이터 프레임을 생성합니다. Socket을 통해 메시지를 전송하기 때문에 최종적으로 전송된 내용은 Socket의 OutputStream에 기록해야 합니다. 악수 메시지를bytebuffer로 변환하는 방법이 필요합니다. 이bytebuffer를 통해 흐름에 기록해야 합니다.
public ByteBuffer createHandshake(Handshakedata handshakedata) {
StringBuilder bui = new StringBuilder(100);
bui.append("GET ");
bui.append(handshakedata.getResourceDescriptor());
bui.append(" HTTP/1.1");
bui.append("\r
");
Iterator it = handshakedata.iterateHttpFields();
while (it.hasNext()) {
String fieldname = (String)it.next();
String fieldvalue = handshakedata.getFieldValue(fieldname);
bui.append(fieldname);
bui.append(": ");
bui.append(fieldvalue);
bui.append("\r
");
}
bui.append("\r
");
byte[] httpheader = Charsetfunctions.asciiBytes(bui.toString());
byte[] content = withcontent ? handshakedata.getContent() : null;
ByteBuffer bytebuffer = ByteBuffer.allocate((content == null ? 0 : content.length) + httpheader.length);
bytebuffer.put(httpheader);
bytebuffer.flip();
return bytebuffer;
}
마지막으로 Socket의 흐름에 쓰기
ByteBuffer buffer = (ByteBuffer)WebSocketClient.this.engine.outQueue.take(); // bytebuffer
WebSocketClient.this.ostream.write(buffer.array(), 0, buffer.limit()); //this.ostream = this.socket.getOutputStream() Socket
WebSocketClient.this.ostream.flush(); // ,
이상은 클라이언트가 악수 프로토콜을 보내는 과정입니다.
클라이언트 수신 서버 응답
서버에서 클라이언트의 악수 요청을 받은 후 응답을 되돌려야 합니다
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Origin: null
Sec-WebSocket-Location: ws://example.com/
이 응답을 받은 후 클라이언트는 Sec-WebSocket-Accept 값을 비교해야 한다. 이 값은 서버가 악수를 하고 연결을 맺는 것에 동의하는 것을 의미한다. 클라이언트가 전송한 Sec-WebSocket-Key와'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'을 연결한 후 SHA-1로 암호화하고 BASE-64로 인코딩한 것이다.
클라이언트가 Sec-WebSocket-Accept를 받은 후 로컬 Sec-WebSocket-Key를 같은 인코딩하여 비교합니다.
public Draft.HandshakeState acceptHandshakeAsClient(ClientHandshake request, ServerHandshake response)
throws InvalidHandshakeException
{
if ((!request.hasFieldValue("Sec-WebSocket-Key")) || (!response.hasFieldValue("Sec-WebSocket-Accept"))) {
return Draft.HandshakeState.NOT_MATCHED;
}
//Sec-WebSocket-Key Sec-WebSocket-Accept
String seckey_answere = response.getFieldValue("Sec-WebSocket-Accept");
String seckey_challenge = request.getFieldValue("Sec-WebSocket-Key");
seckey_challenge = generateFinalKey(seckey_challenge);
if (seckey_challenge.equals(seckey_answere))
return Draft.HandshakeState.MATCHED;
return Draft.HandshakeState.NOT_MATCHED;
}
// Sec-WebSocket-Accept
private String generateFinalKey(String in) {
String seckey = in.trim();
String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
MessageDigest sh1;
try {
sh1 = MessageDigest.getInstance("SHA1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
return Base64.encodeBytes(sh1.digest(acc.getBytes()));
}
2. 데이터의 봉인 및 전송
....계속
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
다양한 언어의 JSONJSON은 Javascript 표기법을 사용하여 데이터 구조를 레이아웃하는 데이터 형식입니다. 그러나 Javascript가 코드에서 이러한 구조를 나타낼 수 있는 유일한 언어는 아닙니다. 저는 일반적으로 '객체'{}...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.