If the server finishes these steps without aborting the WebSocket handshake, the server considers the WebSocket connection to be established and that the WebSocket connection is in the OPEN state.
/**
* Start a closing handshake.
*
* +----------+ +-----------+ +----------+
* - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
* | +----------+ +-----------+ +----------+ |
* +----------+ +-----------+ |
* CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
* +----------+ +-----------+ |
* | | | +---+ |
* +------------------------+-->|fin| - - - -
* | +---+ | +---+
* - - - - -|fin|<---------------------+
* +---+
*
* @param {Number} [code] Status code explaining why the connection is closing
* @param {String} [data] A string explaining why the connection is closing
* @public
*/
close(code, data) {
//close handshake要在state OPEN後才有意義
//CLOSED狀態:已關閉連線 不需要處理
if (this.readyState === WebSocket.CLOSED) return;
//CONNETING: 在open之前,使用abortHandshake處理
if (this.readyState === WebSocket.CONNECTING) {
const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
}
//CLOSING狀態: 這個狀態比較複雜,因為很多情況會跑到此狀態
//包括: req.on('error' event listener
// abortHandshake
//這裡比較需要注意的是: 下面開始就是走 close handshake 這邊需要檢查是否有機會讓未open handshake完成的連線走到下面的步驟,這邊看不太出來,還需要再檢查確認,後面再補充說明
//RFC要求,close handshake要收到close + 送出一次 close算完成
//下面會要判斷 已sent & recv close再socket.end()是因為
//receiverOnConclude把關閉socket連線的工作交給close() 可參考websocket.js L779
//以下處理先送close再收到close然後斷線的情形 CASE 1
if (this.readyState === WebSocket.CLOSING) {
if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
//如果已經是CLOSING狀態,代表呼叫已經send close,並且設定timeout timer,所以後面不用再處理了
return;
}
this._readyState = WebSocket.CLOSING;
this._sender.close(code, data, !this._isServer, (err) => {
//
// This error is handled by the `'error'` listener on the socket. We only
// want to know if the close frame has been sent here.
//
//socket error,不能確定close frame是否成功送出
if (err) return;
this._closeFrameSent = true;
//以下處理收到close並已送出close然後斷線的情形 CASE 2
if (this._closeFrameReceived) this._socket.end();
});
//
// Specify a timeout for the closing handshake to complete.
//
//這邊可以理解為送出close後,timeout後強制將socket destroy,清除資源
this._closeTimer = setTimeout(
this._socket.destroy.bind(this._socket),
closeTimeout //預設 30 seconds
);
}
在上方有個地方判斷closeFrameSent + closeFrameReceived就做socket.end,socket.end是half-close,也就是送出FIN,ws統一在close()裡判斷是否要關閉連線,close handshake無論是 recv close + send close -> 斷線 or send close + recv close ->斷線 這兩種斷線的case分別在close()處理了
receiver.on('conclude', receiverOnConclude); //收到close frame
receiver.on('error', receiverOnError); //parse frame error
socket.on('close', socketOnClose); //Emitted once the socket is fully closed. The argument hadError is a boolean which says if the socket was closed due to a transmission error.
socket.on('end', socketOnEnd); //Emitted when the other end of the socket sends a FIN packet, thus ending the readable side of the socket.
socket.on('error', socketOnError); //Emitted when an error occurs. The 'close' event will be called directly following this event.
以下分別註解對應的event listener
以下是收到close frame時
/**
* The listener of the `Receiver` `'conclude'` event.
*
* @param {Number} code The status code
* @param {String} reason The reason for closing
* @private
*/
function receiverOnConclude(code, reason) {
const websocket = this[kWebSocket];
//收到close frame,停止後續的data event接收
websocket._socket.removeListener('data', socketOnData);
websocket._socket.resume();//**這裡resume()是socket.pause()暫停'data'後想要繼續接收'data' event時需要的,這行可能不必要呼叫
//收到close frame設定相關資訊
websocket._closeFrameReceived = true;
websocket._closeMessage = reason;
websocket._closeCode = code;
//如果有帶close status code/reason,就回覆同樣的內容
if (code === 1005) websocket.close();
else websocket.close(code, reason);
}
以下是receiver parse error時
/**
* The listener of the `Receiver` `'error'` event.
*
* @param {(RangeError|Error)} err The emitted error
* @private
*/
function receiverOnError(err) {
const websocket = this[kWebSocket];
//停止繼續收data event
websocket._socket.removeListener('data', socketOnData);
websocket._readyState = WebSocket.CLOSING;
websocket._closeCode = err[kStatusCode]; //這邊的err[kStatusCode]是由receiver所設定的,根據parse error的結果設定不同的code
websocket.emit('error', err);
//直接socket destroy
websocket._socket.destroy();
}
以下是socket斷線時,這邊的錯誤處理比較多細節須注意: socket ‘close’ event: Emitted once the socket is fully closed. The argument hadError is a boolean which says if the socket was closed due to a transmission error
/**
* The listener of the `net.Socket` `'close'` event.
*
* @private
*/
function socketOnClose() {
const websocket = this[kWebSocket];
this.removeListener('close', socketOnClose);
this.removeListener('end', socketOnEnd);
//這邊需特別注意是否重複走入CLOSING狀態或是是否有機會與CLOSED衝突,主要的關鍵點在socket close event可能發生的時間點,待之後另文分析
websocket._readyState = WebSocket.CLOSING;
//
// The close frame might not have been received or the `'end'` event emitted,
// for example, if the socket was destroyed due to an error. Ensure that the
// `receiver` stream is closed after writing any remaining buffered data to
// it. If the readable side of the socket is in flowing mode then there is no
// buffered data as everything has been already written and `readable.read()`
// will return `null`. If instead, the socket is paused, any possible buffered
// data will be read as a single chunk and emitted synchronously in a single
// `'data'` event.
//
websocket._socket.read(); //這裡的目的主要是清read buffer給receiver的writable stream,是synchronous call to 'data' event
websocket._receiver.end(); //no more data
this.removeListener('data', socketOnData); //移除data event listener
this[kWebSocket] = undefined;
clearTimeout(websocket._closeTimer); //關掉timeout timer,因為socket收到close會正常關閉,不須再destroy
//什麼時候writableState finished or errorEmitted?
//errorEmitted: 當stream writer送出error event, 發生於receiver _write 返回error時,例如receiver parse frame error
//finished: 當stream writer送出finish event
if (
websocket._receiver._writableState.finished ||
websocket._receiver._writableState.errorEmitted
) {
websocket.emitClose(); //emit close!
} else {
//否則就等待 error or finish event,等到後就emit close
websocket._receiver.on('error', receiverOnFinish); //will emit close
websocket._receiver.on('finish', receiverOnFinish); //will emit close
}
}
/**
* The listener of the `Receiver` `'finish'` event.
*
* @private
*/
function receiverOnFinish() {
this[kWebSocket].emitClose();
}
/**
* The listener of the `net.Socket` `'end'` event.
*
* @private
*/
function socketOnEnd() {
const websocket = this[kWebSocket];
websocket._readyState = WebSocket.CLOSING;
websocket._receiver.end();
this.end();
}
下面是當發生socket error時的處理,需要注意的是程式為了只處理一次error event,移除了如果沒有listener listen ‘error’ event會導致process exit: If an EventEmitter does not have at least one listener registered for the 'error' event, and an 'error' event is emitted, the error is thrown, a stack trace is printed, and the Node.js process exits.
/**
* The listener of the `net.Socket` `'error'` event.
*
* @private
*/
function socketOnError() {
const websocket = this[kWebSocket];
this.removeListener('error', socketOnError);
//上面解除socketOnError要特別小心,因為nodejs的eventemitter預設的行為是如果沒有任何'error' listener,會直接process exit
this.on('error', NOOP);
//socket都error了,直接destroy
if (websocket) {
websocket._readyState = WebSocket.CLOSING;
this.destroy();
}
}
close error handling還有一種情況是當使用者主動呼叫send()或其他會送出data的method,但是readyState不是OPEN,這部分在sendAfterClose處理
/**
* Handle cases where the `ping()`, `pong()`, or `send()` methods are called
* when the `readyState` attribute is `CLOSING` or `CLOSED`.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {*} [data] The data to send
* @param {Function} [cb] Callback
* @private
*/
function sendAfterClose(websocket, data, cb) {
if (data) {
const length = toBuffer(data).length;
//
// The `_bufferedAmount` property is used only when the peer is a client and
// the opening handshake fails. Under these circumstances, in fact, the
// `setSocket()` method is not called, so the `_socket` and `_sender`
// properties are set to `null`.
//
//更新bufferAmount, ws內部有分handshake完前後,參考不同的變數,可參考下面 bufferedAmount()的實作
if (websocket._socket) websocket._sender._bufferedBytes += length;
else websocket._bufferedAmount += length;
}
if (cb) {
const err = new Error(
`WebSocket is not open: readyState ${websocket.readyState} ` +
`(${readyStates[websocket.readyState]})`
);
cb(err);
}
}
//這邊順便註記一下bufferedAmount,handshake完成之前(socket = null)是記在一個內部變數
/**
* @type {Number}
*/
get bufferedAmount() {
if (!this._socket) return this._bufferedAmount;
return this._socket._writableState.length + this._sender._bufferedBytes;
}
WebSocket還提供發送訊息的method如send ping pong,這邊介紹send
/**
* Send a data message.
*
* @param {*} data The message to send
* @param {Object} [options] Options object
* @param {Boolean} [options.compress] Specifies whether or not to compress
* `data`
* @param {Boolean} [options.binary] Specifies whether `data` is binary or
* text
* @param {Boolean} [options.fin=true] Specifies whether the fragment is the
* last one
* @param {Boolean} [options.mask] Specifies whether or not to mask `data`
* @param {Function} [cb] Callback which is executed when data is written out
* @public
*/
send(data, options, cb) {
if (this.readyState === WebSocket.CONNECTING) {
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
}
//options is optional
if (typeof options === 'function') {
cb = options;
options = {};
}
//這邊只有判斷data是number時,轉成string
if (typeof data === 'number') data = data.toString();
//如果狀態是CLOSING CLOSED
if (this.readyState !== WebSocket.OPEN) {
sendAfterClose(this, data, cb);
return;
}
const opts = {
binary: typeof data !== 'string',
mask: !this._isServer,
compress: true,
fin: true,
...options
};
if (!this._extensions[PerMessageDeflate.extensionName]) {
opts.compress = false;
}
//交給sender處理,這邊的細節再留到解析sender.js再說明
this._sender.send(data || EMPTY_BUFFER, opts, cb);
}
}
在Node.js中,按照習慣,會發出event的object繼承於EventEmitter,透過.on(eventName, eventHandler/Listener) 來接收event,並且eventName建議是以camel case的方式命名,只是Nodejs大部分的event不需要用到第二個字,所以常見的都只是小寫event name
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
console.log('an event occurred!');
//this === myEmitter
});
myEmitter.emit('event', 'a', 'b');
這裡節錄一段官方文件的範例,透過extends EventEmitter方式,使物件具有收發event的能力,emit透過 function argument,傳遞eventName之外的其他資訊。
open handshake基本上就是透過http request – response的方式在header內傳遞相關資訊,以下從RFC節錄
The handshake from the client looks as follows:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
The handshake from the server looks as follows:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
A user agent SHOULD NOT send a Content-Length header field when the request message does not contain a payload body and the method semantics do not anticipate such a body.
Sec-WebSocket-Protocol是subprotocol selector,client如果提供支援的subprotocol(可能多筆),而server就必須回應一個支援的subprotol,類似的設計在SSL handshake也可以看到,ClientHello會送出多組支援的cipher suite,server只會回應一組選擇的cipher suite
subprotocol指的是application-level protocols layered over the WebSocket Protocol,也就是在WebSocket protocol架構下的application protocol,可以想成WebSocket提供了基礎的通信收發,而應用端具體地定義message的內容與回應方式,相關資訊可以參考IANA的列表
The fragments of one message MUST NOT be interleaved between the fragments of another message unless an extension has been negotiated that can interpret the interleaving.
text frame: payload UTF8 text,在8.1. Handling Errors in UTF-8-Encoded Data提到,如果收到的text不是utf8要做Fail the WebSocket Connection (fail the websocket connection要做的事可參考7.1.7)
if there are two extensions “foo” and “bar” and if the header field |Sec-WebSocket-Extensions| sent by the server has the value “foo, bar“, then operations on the data will be made as bar(foo(data)), be those changes to the data itself (such as compression) or changes to the framing that may “stack”.
Closing The Connection 主要整理Section 7
在RFC裡,明確的描述一些操作的細節,特別是closing the connection這一個Section,因為如handshake或是傳輸中有一些不預期的內容時,必須作錯誤處理,最常見的方式就是關閉連線,在RFC中,client主動關閉連線只有兩種情形會發生,1.錯誤處理 2. application api主動呼叫close (參考p44 7.2.1)
送出close control frame,當送出close frame也收到close frame,就進行Close the WebSocket Connection
The WebSocket Closing Handshake is Started
當收到或是送出close frame,就進入The WebSocket Closing Handshake is Started的狀態,此時WebSocket connection是CLOSING狀態
The WebSocket Connection is Closed
當TCP連線關閉時,就是 The WebSocket Connection is Closed的狀態,WebSocket connection是CLOSED狀態,如果TCP連線關閉時,已經完成closing handshake,則是close cleanly,如果TCP連線關閉時沒有收到close frame,則傳給API層的close status code = 1006
Close the WebSocket Connection
當提到 Close the WebSocket Connection 是指close TCP connection,在TCP連線以建立下,對於server side是指 立即關掉,對於client side是指等待server close在關閉
Fail the WebSocket Connection
Fail the WebSocket Connection 大部分定義在open handshake操作,並且是client的行為(因為server如果再open handshake發生問題,可以透過http status code回應),而當text不是UTF8時,也會進行此操作,如果WebSocket connection還沒有established,就進行Close the WebSocket Connection,如果已經established,就送出close frame + Close the WebSocket Connection(不處理後續的frame,包括接收close frame)。這裡RFC描述得比較簡單,整理一下RFC內所有提到 Fail the WebSocket Connection 的case
p15. Open handshake時,client發現URI invalid (before WebSocket connection established)
不過RFC對於操作並沒有完全的描述清楚,例如7.2.2 提到的Abort the WebSocket Connection並沒有在該文件內出現,另外是還有一些case沒有明確定義是否歸類在fail the websocket connection, 例如 5.1 p27
server MUST NOT mask any frames that it sends to the client. A client MUST close a connection if it detects a masked frame. In this case, it MAY use the status code 1002 (protocol error) as defined in Section 7.4.1.
這邊只提到說要送close code 1002,但沒有提到要使用fail connection還是start close handshake。當然如果像收到不預期(mask錯誤)應該歸類在 Fail the WebSocket Connection
Predefined close status code 整理7.4.1,這邊只列出nodejs ws 有在使用的status code或是常見的code
1000 normal closure
1002 protocol error
1005 close frame without status code (for API)
1006 reserved value. It is designated for use in applications expecting a status code to indicate that the connection was closed abnormally, e.g., without sending or receiving a Close control frame. 這個在nodejs ws是WebSocket物件的預設值 (for API)
The Subprotocol In Use is defined to be the value of the |Sec-WebSocket-Protocol| header field in the server’s handshake or the null value if that header field was not present in the server’s handshake.
在4.2.2對於subprotocol server如果無法支援client的要求,就是只支援null,也就是回應不帶 Sec-WebSocket-Protocol header field
另外4.2.1提到一些server端對於open handshake request的要求
GET verb
header field Host
header field Upgrade = websocket (case-insensitive value)
header field Connection = Upgrade (case-insensitive value)
header field Sec-WebSocket-Protocol (optional, only one is selected)
header field Sec-WebSocket-Extensions (optional, can have multiple value or split in to multiple header field instances)
open handshake階段,在server side,送出handshake response後,此連線狀態為OPEN,server可以開始send/recv data,對於client side,收到server response並驗證成功後,連線狀態也是OPEN,至於client side要驗證的server response有哪些呢?在4.1的後半段有描述 The client MUST validate the server’s response as follows:
status code = 101
check header field Upgrade = websocket (value case-insensitive)
check header field Connection = Upgrade (value case-insensitive)
check header field Sec-WebSocket-Accept value
check header field (if exists) Sec-WebSocket-Extensions value is in the requested handshake
check header field (if exists) Sec-WebSocket-Protocol value is in the requested handshake
When multiple entries match for a user, they are applied in order. Where there are multiple matches, the last match is used (which is not necessarily the most specific match).
The Following External Vault mirrors (not monitored by the CentOS Infra team !) also provide direct downloads for all content, including isos and rsync access:
USA:
http://archive.kernel.org/centos-vault/
rsync://archive.kernel.org::centos-vault/
Europe:
http://mirror.nsc.liu.se/centos-store/
rsync://mirror.nsc.liu.se::centos-store/
This directory (and version of CentOS) is deprecated. Please see this FAQ concerning the CentOS release scheme: https://wiki.centos.org/FAQ/General Please keep in mind that 6.0, 6.1, 6.2, 6.3, 6.4 , 6.5, 6.6, 6.7, 6.8 , 6.9 and 6.10 no longer get any updates, nor any security fix’s. The whole CentOS 6 is *dead* and *shouldn’t* be used anywhere at *all*
Red Hat have pulled the plug on RHEL 6.x as of Nov 30th 2020 and as a result CentOS 6 is now a dead version. The online yum repos for CentOS 6 have been archived to vault.centos.org and there will be no more updates to it at all, ever.
#### MODULES ####
#for DOCKER add load imuxsock
module(load="imuxsock")
# The imjournal module bellow is now used as a message source instead of imuxsock.
#$ModLoad imuxsock # provides support for local system logging (e.g. via logger command)
#for DOCKER remove imjournal module
#$ModLoad imjournal # provides access to the systemd journal
#...
# Turn off message reception via local log socket;
# local messages are retrieved through imjournal now.
# for DOCKER
#$OmitLocalLogging on
# File to store the position in the journal
#for DOCKER
#$IMJournalStateFile imjournal.state