프로토콜에 따라 WebSocket을 사용하여 서버 측에서 메시지를 어떻게 보내고받을 수 있습니까?
브라우저에서 서버로 데이터를 보낼 때 서버에서 임의의 바이트를받는 이유는 무엇입니까? 어떻게 든 인코딩 된 데이터입니까?
프레이밍은 서버 → 클라이언트 및 클라이언트 → 서버 방향 모두에서 어떻게 작동합니까?
답변:
참고 : 이것은 최종 프레임 형식에 따라 수신 및 발신 WebSocket 메시지를 처리 할 수있는 매우 간단한 서버를 구현하는 방법에 대한 설명 및 의사 코드입니다. 핸드 셰이 킹 프로세스는 포함되지 않습니다. 또한이 답변은 교육 목적으로 작성되었습니다. 완전한 기능을 갖춘 구현이 아닙니다.
(즉, 서버 → 브라우저)
보내는 프레임은 WebSocket 프레임 형식에 따라 형식을 지정해야합니다. 메시지를 보내는 경우이 형식은 다음과 같습니다.
첫 번째 바이트는 텍스트 프레임의 경우 1000 0001
(또는 129
)입니다.
두 번째 바이트는 0
데이터를 인코딩하지 않기 때문에 첫 번째 비트가로 설정됩니다 (서버에서 클라이언트로 인코딩하는 것은 필수가 아님).
길이 바이트를 올바르게 전송하려면 원시 데이터의 길이를 결정해야합니다.
0 <= length <= 125
추가 바이트가 필요하지 않습니다.126 <= length <= 65535
두 개의 추가 바이트가 필요하고 두 번째 바이트는126
length >= 65536
8 바이트가 추가로 필요하고 두 번째 바이트는127
길이는 별도의 바이트로 분할되어야합니다. 즉, 오른쪽으로 비트 이동 (8 비트) 한 다음 수행하여 마지막 8 비트 만 유지해야합니다 AND 1111 1111
(즉 255
).
길이 바이트 뒤에 원시 데이터가 나옵니다.
이로 인해 다음 의사 코드가 생성됩니다.
bytesFormatted[0] = 129
indexStartRawData = -1 // it doesn't matter what value is
// set here - it will be set now:
if bytesRaw.length <= 125
bytesFormatted[1] = bytesRaw.length
indexStartRawData = 2
else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
bytesFormatted[1] = 126
bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[3] = ( bytesRaw.length ) AND 255
indexStartRawData = 4
else
bytesFormatted[1] = 127
bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
bytesFormatted[8] = ( bytesRaw.length >> 8 ) AND 255
bytesFormatted[9] = ( bytesRaw.length ) AND 255
indexStartRawData = 10
// put raw data at the correct index
bytesFormatted.put(bytesRaw, indexStartRawData)
// now send bytesFormatted (e.g. write it to the socket stream)
(즉, 브라우저 → 서버)
획득 한 프레임은 다음 형식입니다.
첫 번째 바이트는 일반적으로 중요하지 않습니다. 텍스트를 보내는 경우에만 텍스트 유형을 사용하는 것입니다. 이 경우 1000 0001
(또는 129
)이됩니다.
두 번째 바이트와 추가 2 또는 8 바이트는 길이에 사용되는 바이트 수를 알아야하기 때문에 약간의 구문 분석이 필요합니다 (실제 데이터가 시작되는 위치를 알아야 함). 데이터가 이미 있으므로 길이 자체는 일반적으로 필요하지 않습니다.
두 번째 바이트의 첫 번째 비트는 항상 1
데이터가 마스킹 (= 인코딩 됨)됨을 의미합니다. 클라이언트에서 서버로 보내는 메시지는 항상 마스킹됩니다. 을 수행하여 첫 번째 비트를 제거해야합니다 secondByte AND 0111 1111
. 결과 바이트가 두 번째 바이트에 맞지 않아 길이를 나타내지 않는 두 가지 경우가 있습니다.
0111 1110
또는 의 두 번째 바이트 126
는 다음 두 바이트가 길이로 사용됨을 의미합니다.0111 1111
또는 의 두 번째 바이트 127
는 다음 8 바이트가 길이로 사용됨을 의미합니다.4 개의 마스크 바이트는 전송 된 실제 데이터를 디코딩하는 데 사용됩니다. 디코딩 알고리즘은 다음과 같습니다.
decodedByte = encodedByte XOR masks[encodedByteIndex MOD 4]
여기서 encodedByte
데이터의 원본 바이트이고, encodedByteIndex
첫 번째 바이트로부터의 바이트 카운트의 지수 (오프셋)는 실제 데이터의 인덱스를 가지고 0
. masks
4 개의 마스크 바이트를 포함하는 배열입니다.
이로 인해 디코딩을 위해 다음과 같은 의사 코드가 생성됩니다.
secondByte = bytes[1]
length = secondByte AND 127 // may not be the actual length in the two special cases
indexFirstMask = 2 // if not a special case
if length == 126 // if a special case, change indexFirstMask
indexFirstMask = 4
else if length == 127 // ditto
indexFirstMask = 10
masks = bytes.slice(indexFirstMask, 4) // four bytes starting from indexFirstMask
indexFirstDataByte = indexFirstMask + 4 // four bytes further
decoded = new array
decoded.length = bytes.length - indexFirstDataByte // length of real data
for i = indexFirstDataByte, j = 0; i < bytes.length; i++, j++
decoded[j] = bytes[i] XOR masks[j MOD 4]
// now use "decoded" to interpret the received data
0001
"Opcode : 4 bits"사양의 해당 부분 헤더에 명시되어 있습니다. 첫 번째 바이트는 FIN, RSV1-3 및 opcode로 구성됩니다. FIN은 1
RSV1-3 모든 세이며, 0
상기 op 코드는 0001
에 가산하는 1000 0001
제 바이트. 또한 다른 부분에서 바이트가 분할되는 방식을 보여주는 사양의 삽화를 참조하십시오.
Java 구현 (필요한 경우)
읽기 : 클라이언트에서 서버로
int len = 0;
byte[] b = new byte[buffLenth];
//rawIn is a Socket.getInputStream();
while(true){
len = rawIn.read(b);
if(len!=-1){
byte rLength = 0;
int rMaskIndex = 2;
int rDataStart = 0;
//b[0] is always text in my case so no need to check;
byte data = b[1];
byte op = (byte) 127;
rLength = (byte) (data & op);
if(rLength==(byte)126) rMaskIndex=4;
if(rLength==(byte)127) rMaskIndex=10;
byte[] masks = new byte[4];
int j=0;
int i=0;
for(i=rMaskIndex;i<(rMaskIndex+4);i++){
masks[j] = b[i];
j++;
}
rDataStart = rMaskIndex + 4;
int messLen = len - rDataStart;
byte[] message = new byte[messLen];
for(i=rDataStart, j=0; i<len; i++, j++){
message[j] = (byte) (b[i] ^ masks[j % 4]);
}
parseMessage(new String(message));
//parseMessage(new String(b));
b = new byte[buffLenth];
}
}
쓰기 : 서버에서 클라이언트로
public void brodcast(String mess) throws IOException{
byte[] rawData = mess.getBytes();
int frameCount = 0;
byte[] frame = new byte[10];
frame[0] = (byte) 129;
if(rawData.length <= 125){
frame[1] = (byte) rawData.length;
frameCount = 2;
}else if(rawData.length >= 126 && rawData.length <= 65535){
frame[1] = (byte) 126;
int len = rawData.length;
frame[2] = (byte)((len >> 8 ) & (byte)255);
frame[3] = (byte)(len & (byte)255);
frameCount = 4;
}else{
frame[1] = (byte) 127;
int len = rawData.length;
frame[2] = (byte)((len >> 56 ) & (byte)255);
frame[3] = (byte)((len >> 48 ) & (byte)255);
frame[4] = (byte)((len >> 40 ) & (byte)255);
frame[5] = (byte)((len >> 32 ) & (byte)255);
frame[6] = (byte)((len >> 24 ) & (byte)255);
frame[7] = (byte)((len >> 16 ) & (byte)255);
frame[8] = (byte)((len >> 8 ) & (byte)255);
frame[9] = (byte)(len & (byte)255);
frameCount = 10;
}
int bLength = frameCount + rawData.length;
byte[] reply = new byte[bLength];
int bLim = 0;
for(int i=0; i<frameCount;i++){
reply[bLim] = frame[i];
bLim++;
}
for(int i=0; i<rawData.length;i++){
reply[bLim] = rawData[i];
bLim++;
}
out.write(reply);
out.flush();
}
자바 스크립트 구현 :
function encodeWebSocket(bytesRaw){
var bytesFormatted = new Array();
bytesFormatted[0] = 129;
if (bytesRaw.length <= 125) {
bytesFormatted[1] = bytesRaw.length;
} else if (bytesRaw.length >= 126 && bytesRaw.length <= 65535) {
bytesFormatted[1] = 126;
bytesFormatted[2] = ( bytesRaw.length >> 8 ) & 255;
bytesFormatted[3] = ( bytesRaw.length ) & 255;
} else {
bytesFormatted[1] = 127;
bytesFormatted[2] = ( bytesRaw.length >> 56 ) & 255;
bytesFormatted[3] = ( bytesRaw.length >> 48 ) & 255;
bytesFormatted[4] = ( bytesRaw.length >> 40 ) & 255;
bytesFormatted[5] = ( bytesRaw.length >> 32 ) & 255;
bytesFormatted[6] = ( bytesRaw.length >> 24 ) & 255;
bytesFormatted[7] = ( bytesRaw.length >> 16 ) & 255;
bytesFormatted[8] = ( bytesRaw.length >> 8 ) & 255;
bytesFormatted[9] = ( bytesRaw.length ) & 255;
}
for (var i = 0; i < bytesRaw.length; i++){
bytesFormatted.push(bytesRaw.charCodeAt(i));
}
return bytesFormatted;
}
function decodeWebSocket (data){
var datalength = data[1] & 127;
var indexFirstMask = 2;
if (datalength == 126) {
indexFirstMask = 4;
} else if (datalength == 127) {
indexFirstMask = 10;
}
var masks = data.slice(indexFirstMask,indexFirstMask + 4);
var i = indexFirstMask + 4;
var index = 0;
var output = "";
while (i < data.length) {
output += String.fromCharCode(data[i++] ^ masks[index++ % 4]);
}
return output;
}
2^31 - 1
.
C # 구현
브라우저-> 서버
private String DecodeMessage(Byte[] bytes)
{
String incomingData = String.Empty;
Byte secondByte = bytes[1];
Int32 dataLength = secondByte & 127;
Int32 indexFirstMask = 2;
if (dataLength == 126)
indexFirstMask = 4;
else if (dataLength == 127)
indexFirstMask = 10;
IEnumerable<Byte> keys = bytes.Skip(indexFirstMask).Take(4);
Int32 indexFirstDataByte = indexFirstMask + 4;
Byte[] decoded = new Byte[bytes.Length - indexFirstDataByte];
for (Int32 i = indexFirstDataByte, j = 0; i < bytes.Length; i++, j++)
{
decoded[j] = (Byte)(bytes[i] ^ keys.ElementAt(j % 4));
}
return incomingData = Encoding.UTF8.GetString(decoded, 0, decoded.Length);
}
서버-> 브라우저
private static Byte[] EncodeMessageToSend(String message)
{
Byte[] response;
Byte[] bytesRaw = Encoding.UTF8.GetBytes(message);
Byte[] frame = new Byte[10];
Int32 indexStartRawData = -1;
Int32 length = bytesRaw.Length;
frame[0] = (Byte)129;
if (length <= 125)
{
frame[1] = (Byte)length;
indexStartRawData = 2;
}
else if (length >= 126 && length <= 65535)
{
frame[1] = (Byte)126;
frame[2] = (Byte)((length >> 8) & 255);
frame[3] = (Byte)(length & 255);
indexStartRawData = 4;
}
else
{
frame[1] = (Byte)127;
frame[2] = (Byte)((length >> 56) & 255);
frame[3] = (Byte)((length >> 48) & 255);
frame[4] = (Byte)((length >> 40) & 255);
frame[5] = (Byte)((length >> 32) & 255);
frame[6] = (Byte)((length >> 24) & 255);
frame[7] = (Byte)((length >> 16) & 255);
frame[8] = (Byte)((length >> 8) & 255);
frame[9] = (Byte)(length & 255);
indexStartRawData = 10;
}
response = new Byte[indexStartRawData + length];
Int32 i, reponseIdx = 0;
//Add the frame bytes to the reponse
for (i = 0; i < indexStartRawData; i++)
{
response[reponseIdx] = frame[i];
reponseIdx++;
}
//Add the data bytes to the response
for (i = 0; i < length; i++)
{
response[reponseIdx] = bytesRaw[i];
reponseIdx++;
}
return response;
}
test�c=ܝX[
"test"가 내 메시지 인 여기와 같이 정의되지 않은 부록과 함께 내 특정 메시지를 반환 합니다. 다른 부분은 무엇입니까?
pimvdb의 답변은 파이썬으로 구현되었습니다.
def DecodedCharArrayFromByteStreamIn(stringStreamIn):
#turn string values into opererable numeric byte values
byteArray = [ord(character) for character in stringStreamIn]
datalength = byteArray[1] & 127
indexFirstMask = 2
if datalength == 126:
indexFirstMask = 4
elif datalength == 127:
indexFirstMask = 10
masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]]
indexFirstDataByte = indexFirstMask + 4
decodedChars = []
i = indexFirstDataByte
j = 0
while i < len(byteArray):
decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) )
i += 1
j += 1
return decodedChars
사용 예 :
fromclient = '\x81\x8c\xff\xb8\xbd\xbd\xb7\xdd\xd1\xd1\x90\x98\xea\xd2\x8d\xd4\xd9\x9c'
# this looks like "?ŒOÇ¿¢gÓ ç\Ð=«ož" in unicode, received by server
print DecodedCharArrayFromByteStreamIn(fromclient)
# ['H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '!']
PHP 프레임 인코딩 기능 외에도 다음은 디코딩 기능입니다.
function Decode($M){
$M = array_map("ord", str_split($M));
$L = $M[1] AND 127;
if ($L == 126)
$iFM = 4;
else if ($L == 127)
$iFM = 10;
else
$iFM = 2;
$Masks = array_slice($M, $iFM, 4);
$Out = "";
for ($i = $iFM + 4, $j = 0; $i < count($M); $i++, $j++ ) {
$Out .= chr($M[$i] ^ $Masks[$j % 4]);
}
return $Out;
}
여기에 사용하기 쉬운 WebSocket PHP 클래스에서이 기능과 기타 기능을 구현했습니다 .
PHP 구현 :
function encode($message)
{
$length = strlen($message);
$bytesHeader = [];
$bytesHeader[0] = 129; // 0x1 text frame (FIN + opcode)
if ($length <= 125) {
$bytesHeader[1] = $length;
} else if ($length >= 126 && $length <= 65535) {
$bytesHeader[1] = 126;
$bytesHeader[2] = ( $length >> 8 ) & 255;
$bytesHeader[3] = ( $length ) & 255;
} else {
$bytesHeader[1] = 127;
$bytesHeader[2] = ( $length >> 56 ) & 255;
$bytesHeader[3] = ( $length >> 48 ) & 255;
$bytesHeader[4] = ( $length >> 40 ) & 255;
$bytesHeader[5] = ( $length >> 32 ) & 255;
$bytesHeader[6] = ( $length >> 24 ) & 255;
$bytesHeader[7] = ( $length >> 16 ) & 255;
$bytesHeader[8] = ( $length >> 8 ) & 255;
$bytesHeader[9] = ( $length ) & 255;
}
$str = implode(array_map("chr", $bytesHeader)) . $message;
return $str;
}
답변 해 주셔서 감사합니다 . 관심이 있으시면 Sending 기능을 포함하도록 hfern의 (위) Python 버전에 추가하고 싶습니다 .
def DecodedWebsockRecieve(stringStreamIn):
byteArray = stringStreamIn
datalength = byteArray[1] & 127
indexFirstMask = 2
if datalength == 126:
indexFirstMask = 4
elif datalength == 127:
indexFirstMask = 10
masks = [m for m in byteArray[indexFirstMask : indexFirstMask+4]]
indexFirstDataByte = indexFirstMask + 4
decodedChars = []
i = indexFirstDataByte
j = 0
while i < len(byteArray):
decodedChars.append( chr(byteArray[i] ^ masks[j % 4]) )
i += 1
j += 1
return ''.join(decodedChars)
def EncodeWebSockSend(socket,data):
bytesFormatted = []
bytesFormatted.append(129)
bytesRaw = data.encode()
bytesLength = len(bytesRaw)
if bytesLength <= 125 :
bytesFormatted.append(bytesLength)
elif bytesLength >= 126 and bytesLength <= 65535 :
bytesFormatted.append(126)
bytesFormatted.append( ( bytesLength >> 8 ) & 255 )
bytesFormatted.append( bytesLength & 255 )
else :
bytesFormatted.append( 127 )
bytesFormatted.append( ( bytesLength >> 56 ) & 255 )
bytesFormatted.append( ( bytesLength >> 48 ) & 255 )
bytesFormatted.append( ( bytesLength >> 40 ) & 255 )
bytesFormatted.append( ( bytesLength >> 32 ) & 255 )
bytesFormatted.append( ( bytesLength >> 24 ) & 255 )
bytesFormatted.append( ( bytesLength >> 16 ) & 255 )
bytesFormatted.append( ( bytesLength >> 8 ) & 255 )
bytesFormatted.append( bytesLength & 255 )
bytesFormatted = bytes(bytesFormatted)
bytesFormatted = bytesFormatted + bytesRaw
socket.send(bytesFormatted)
읽기를위한 사용법 :
bufSize = 1024
read = DecodedWebsockRecieve(socket.recv(bufSize))
쓰기를위한 사용법 :
EncodeWebSockSend(sock,"hellooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo")
Go에서 구현
인코딩 부분 (서버-> 브라우저)
func encode (message string) (result []byte) {
rawBytes := []byte(message)
var idxData int
length := byte(len(rawBytes))
if len(rawBytes) <= 125 { //one byte to store data length
result = make([]byte, len(rawBytes) + 2)
result[1] = length
idxData = 2
} else if len(rawBytes) >= 126 && len(rawBytes) <= 65535 { //two bytes to store data length
result = make([]byte, len(rawBytes) + 4)
result[1] = 126 //extra storage needed
result[2] = ( length >> 8 ) & 255
result[3] = ( length ) & 255
idxData = 4
} else {
result = make([]byte, len(rawBytes) + 10)
result[1] = 127
result[2] = ( length >> 56 ) & 255
result[3] = ( length >> 48 ) & 255
result[4] = ( length >> 40 ) & 255
result[5] = ( length >> 32 ) & 255
result[6] = ( length >> 24 ) & 255
result[7] = ( length >> 16 ) & 255
result[8] = ( length >> 8 ) & 255
result[9] = ( length ) & 255
idxData = 10
}
result[0] = 129 //only text is supported
// put raw data at the correct index
for i, b := range rawBytes {
result[idxData + i] = b
}
return
}
디코딩 부분 (브라우저-> 서버)
func decode (rawBytes []byte) string {
var idxMask int
if rawBytes[1] == 126 {
idxMask = 4
} else if rawBytes[1] == 127 {
idxMask = 10
} else {
idxMask = 2
}
masks := rawBytes[idxMask:idxMask + 4]
data := rawBytes[idxMask + 4:len(rawBytes)]
decoded := make([]byte, len(rawBytes) - idxMask + 4)
for i, b := range data {
decoded[i] = b ^ masks[i % 4]
}
return string(decoded)
}
Clojure, 디코드 기능은 프레임이 맵으로 전송된다고 가정합니다. {:data byte-array-buffer :size int-size-of-buffer}
스트림의 청크 크기에 따라 실제 크기가 바이트 배열과 동일한 크기가 아닐 수 있으므로 .
여기에 게시 된 코드 : https://gist.github.com/viperscape/8918565
(defn ws-decode [frame]
"decodes websocket frame"
(let [data (:data frame)
dlen (bit-and (second data) 127)
mstart (if (== dlen 127) 10 (if (== dlen 126) 4 2))
mask (drop 2 (take (+ mstart 4) data))
msg (make-array Byte/TYPE (- (:size frame) (+ mstart 4)))]
(loop [i (+ mstart 4), j 0]
(aset-byte msg j (byte (bit-xor (nth data i) (nth mask (mod j 4)))))
(if (< i (dec(:size frame))) (recur (inc i) (inc j))))
msg))
(defn ws-encode [data]
"takes in bytes, return websocket frame"
(let [len (count data)
blen (if (> len 65535) 10 (if (> len 125) 4 2))
buf (make-array Byte/TYPE (+ len blen))
_ (aset-byte buf 0 -127) ;;(bit-or (unchecked-byte 0x80)
(unchecked-byte 0x1)
_ (if (= 2 blen)
(aset-byte buf 1 len) ;;mask 0, len
(do
(dorun(map #(aset-byte buf %1
(unchecked-byte (bit-and (bit-shift-right len (*(- %2 2) 8))
255)))
(range 2 blen) (into ()(range 2 blen))))
(aset-byte buf 1 (if (> blen 4) 127 126))))
_ (System/arraycopy data 0 buf blen len)]
buf))
1000 0001
텍스트 프레임에 (129)? 사양에 따르면%x1 denotes a text frame
. 따라서0000 0001
(0x01
) 또는?