Я создаю простой сервер Node.js WebSocket, но после первоначального рукопожатия у меня возникла проблема.
Вначале я использовал только Chrome и командную строку для мониторинга между веб-сокетом HTML5 и сервером Node.js. Потребовалось немного времени, чтобы реализовать протокол, но я только что закончил очень простую версию декодирования сообщений на стороне сервера. Однако у меня были трудные времена, потому что всякий раз, когда я вызывал ws.send('some kind of message'), веб-сокет закрывался на стороне клиента. Глядя на вкладку сети в DevTools, похоже, что сообщение будет отправлено от клиента и получит немедленный ответ об ошибке (Opcode -1), и эта ошибка будет зарегистрирована в консоли:
WebSocket connection to 'ws://localhost:4000/' failed: A server must not mask any frames that it sends to the client.
Я изучил, что все это значит, и не могу понять, почему мой код выдает это. Я попытался восстановить его, а также отправить тестовое сообщение после подтверждения, которое сработало. Единственное, что я не пробовал, так это использование другого браузера, поэтому я попробовал его сегодня. И это сработало, как и ожидалось.
Ниже приведен весь мой соответствующий код.
Библиотеки, константы и прослушивания:
const hostname = 'localhost';
const webport = 8080;
const socketport = 4000;
const http = require('http');
const net = require('net');
const mysql = require('mysql');
const rlm = require('readline');
const crypt = require('crypto');
...
server.listen(webport,hostname);
socketServer.listen(socketport,hostname);
HTTP-сервер:
const server = http.createServer(
function(req,res) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write("
<html>
<head>
<title>Test Title</title>
</head>
<body>
<h1>Here's the thing</h1>
<p>im baby</p>
</body>
<script>
const ws = new WebSocket('ws://"+hostname+":"+socketport+"');
ws.addEventListener('message',function(data){
console.info(data.data)
});
</script>
</html>
"); // Reformatted for better reading
res.end();
});
Сетевой сервер:
var sockets = new Map();
var socketInfo = {};
const socketDelimiters = {
'Accept-Encoding':',',
'Accept-Language':';',
'Sec-WebSocket-Extensions':'; '
}
const socketServer = net.Server(function(s) {
s.on('data',function(e) {
/*
* If the socket is not registered, read first message as
* the beginning to a handshake
*/
if (sockets.get(s)==null) {
var str = ""+e;
var tempobj = str.split("\r\n");
var newObj = {};
for(var i in tempobj) {
if (tempobj[i].length>0) {
var tempProperty = tempobj[i].split(': ');
if (tempProperty.length>1) {
if (socketDelimiters[tempProperty[0]]!=null){
tempProperty[1] = tempProperty[1].split(
socketDelimiters[tempProperty[0]]);
}
newObj[tempProperty[0]] = tempProperty[1];
} else {
newObj.header = tempProperty;
}
}
}
var protocolReturn = "
HTTP/1.1 101 Switching Protocols\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-Websocket-Accept: "+createAcceptKey(newObj['Sec-WebSocket-Key'])
+"\r\n\r\n"; //Reformatted for better reading
s.write(protocolReturn);
s.pipe(s);
sockets.set(s,newObj['Sec-WebSocket-Key']);
socketInfo[newObj['Sec-WebSocket-Key']] = {
socket:s,
isReading:false,
message:null,
mask:null,
handshake: newObj
};
s.write(Buffer.from([0x81,0x04,0x74,0x65,0x73,0x74])); // 'test'
s.pipe(s);
} else {
/*
* If the socket is found and registered, decode the incoming message
*/
var firstBytes = e.readUInt16BE(0);
console.info(firstBytes);
var length=((firstBytes & 0x007F)/0x0001);
var FIN = ((firstBytes & 0x8000))!=0;
var opcode = (firstBytes & 0x0F00)/0x0100;
var mask = ((firstBytes & 0x0080)!=0);
if (opcode!=8) {
console.info("end: "+FIN);
console.info("mask: "+mask);
console.info("op code: "+opcode);
console.info("length: "+length);
var mask = [];
for(var i=0; i<4; i++) {
var b = e.readUInt8(2+i);
mask.push(b);
}
var val=[];
for(var i=0; i<length; i++) {
var b = e.readUInt8(6+i) ^ mask[i%4];
val.push(b);
}
var newVal = new Buffer.from(val);
console.info(newVal.toString('utf8'));
}
}
})
// Handles error
s.on('error',function(err) {
console.info(err);
})
// Takes socket out of the socket list on close
s.on('close',function(hasError) {
if (hasError) {console.info("Please see error")}
delete socketInfo[sockets.get(s)];
sockets.delete(s);
});
});
// Generates accept key from given key
function createAcceptKey(key) {
var inKeyHash = crypt.createHash('sha1');
inKeyHash.update(key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
return (inKeyHash.digest('base64'));
}
Что все это должно делать («<» означает «сервер-клиент», «>» означает «клиент-сервер»)
> [handshake initiation]
< [handshake confirmation]
< test
> [anything the client sends through the console]
/*
*All I do for the client to server bit at the end is go into the console,
* and plug in something like this
*/
ws.send('blah blah blah')
Это отлично работает в Firefox, но, как объяснялось выше, в Chrome выдает ошибку, утверждая, что сервер отправил замаскированный кадр в тот же момент, когда клиент отправляет сообщение на сервер.
Есть ли причина, по которой хром читает замаскированный кадр, а фаерфокс нет?
Обновлено:
Теперь я попытался использовать это в другом браузере (точнее, в браузере OBS), и он выдает ту же ошибку на стороне сервера, что и подключение к Chrome (я добавил прослушиватель событий для отправки сообщения в сокет, открытый на на стороне клиента). Кто-нибудь знает, почему это работает только в Firefox?
@JoshLee Только что сделал это, не знал, что смогу это сделать. Спасибо!





Решил это два дня назад, не осознавал, что могу опубликовать свой собственный ответ (все еще новичок в размещении здесь, извините!)
Многое из того, что я понял о сокетах Node.js, пришло из сетевой документации. В этом есть пример взаимодействия сервера и клиента. Команда pipe() используется после записи на стороне сервера, поэтому я предположил, что она необходима при записи клиенту сокета.
Это не требуется и фактически не должно использоваться. Примером является эхо-сервер, поэтому каждое сообщение, которое клиент отправляет на сервер, будет ретранслироваться обратно клиенту. Этот пост помог мне в этом, но я немного злюсь, потому что я пытался следовать этому совету раньше, и он перестал работать, когда я удалил команды канала. Если определение сумасшествия звучит так: «Попробовать что-то еще раз и ожидать других результатов», то выбросьте меня в психушку.
ТЛ, ДР;
Запись в сокет оказалась проще, чем я думал:
// Expected:
socket.write('blah blah blah');
socket.pipe(socket);
// Reality
socket.write('blah blah blah');
Вы должны опубликовать свой ответ как ответ.