# WebSocket

* [WebSocket](/directory-3/websocket.md#websocket)
  * [TCP的三次握手与四次分手](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#tcp的三次握手与四次分手)
    * [三次握手](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#三次握手)
    * [四次分手：](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#四次分手)
  * [WebSocket简介](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#websocket简介)
  * [实例](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#实例)
  * [WebSocket原生事件](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#websocket原生事件)
  * [WebSocket原生方法](https://note.niefee.com/directory-3/pages/-LjTb6fUG2FjcTu6-1ot#websocket原生方法)
  * [readyState](/directory-3/websocket.md#readystate)

## WebSocket

### TCP的三次握手与四次分手

#### 三次握手

TCP是主机对主机层的传输控制协议，提供可靠的连接服务，采用三次握手确认建立一个连接:

位码即tcp标志位,有6种标示:

1. SYN(synchronous建立联机)
2. ACK(acknowledgement 确认)
3. PSH(push传送)
4. FIN(finish结束)
5. RST(reset重置)
6. URG(urgent紧急)
7. Sequence number(顺序号码)
8. Acknowledge number(确认号码)

握手过程：

第一次握手：建立连接。客户端发送连接请求报文段，将`SYN`位置为1，`Sequence Number`为x；然后，客户端进入`SYN_SEND`状态，等待服务器的确认； 第二次握手：服务器收到`SYN`报文段。服务器收到客户端的`SYN`报文段，需要对这个`SYN`报文段进行确认，设置`Acknowledgment Number`为`x+1(Sequence Number+1)`；同时，自己还要发送`SYN`请求信息，将`SYN`位置为1，`Sequence Number`为y；服务器端将上述所有信息放到一个报文段（即`SYN+ACK`报文段）中，一并发送给客户端，此时服务器进入`SYN_RECV`状态； 第三次握手：客户端收到服务器的`SYN+ACK`报文段。然后将`Acknowledgment Number`设置为y+1，向服务器发送`ACK`报文段，这个报文段发送完毕以后，客户端和服务器端都进入`ESTABLISHED`状态，完成`TCP`三次握手；

![TCP/IP](/files/-LjTb9P6suFKZyxNjPlT)

#### 四次分手：

由于TCP连接是全双工的，因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个`FIN`来终止这个方向的连接。收到一个 `FIN`只意味着这一方向上没有数据流动，一个`TCP`连接在收到一个`FIN`后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。

操作：

1. 客户端A发送一个`FIN`，用来关闭客户A到服务器B的数据传送。
2. 服务器B收到这个`FIN`，它发回一个`ACK`，确认序号为收到的序号加1。和`SYN`一样，一个`FIN`将占用一个序号。
3. 服务器B关闭与客户端A的连接，发送一个`FIN`给客户端A。
4. 客户端A发回`ACK`报文确认，并将确认序号设置为收到序号加1。

![TCP/IP](/files/-LjTb9P8OyB6FPQ5wlZI)

> 参考： 1. <http://www.cnblogs.com/Jessy/p/3535612.html> 2. <https://www.zhihu.com/question/24853633>

### WebSocket简介

WebSocket协议是基于`TCP`的一种新的网络协议。它实现了浏览器与服务器全双工(`full-duplex`)通信——可以通俗的解释为服务器主动发送信息给客户端。 只需要经过一次`HTTP`请求，就可以做到源源不断的信息传送了（在程序设计中，这种设计叫做回调，即：你有信息了再来通知我）。

客户端请求：

```
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
```

`Upgrade: websocket`、`Connection: Upgrade`表示希望将http协议升级到Websocket协议。 `Sec-WebSocket-Key`是一个经过base64编码的随机字节。 `Sec-WebSocket-Protocol`表示客户端支持的协议列表。 `Sec-WebSocket-Version`:是告诉服务器所使用的Websocket Draft（协议版本）。 `Origin`字段是可选的，表示在浏览器中发起此Websocket连接所在的页面。

服务端响应：

```
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
```

`Sec-WebSocket-Accept`:是把`Sec-WebSocket-Key`加上一个特殊字符串“`258EAFA5-E914-47DA-95CA-C5AB0DC85B11`”，然后计算**SHA-1**摘要，之后进行**BASE-64**编码，将结果做为`Sec-WebSocket-Accept`头的值，返回给客户端。

> 参考： 1.[知乎](https://www.zhihu.com/question/20215561) 2.[维基百科](https://zh.wikipedia.org/wiki/WebSocket)

### 实例

服务器端：

```javascript
const WebSocket = require('ws');

const WebSocketServer = WebSocket.Server;

const wss = new WebSocketServer({
    port: 3000
});

//启动服务链接
wss.on('connection', function (ws) {
    console.log(`[SERVER] connection()`);
    //监听有否有请求信息
    ws.on('message', function (message) {
        console.log(`[SERVER] Received: ${message}`);
        setTimeout(() => {
            //定时给客户端发送信息
            ws.send(`What's your name?`, (err) => {
                if (err) {
                    console.log(`[SERVER] error: ${err}`);
                }
            });
        }, 1000);
    })
});

console.log('ws server started at port 3000...');
```

客户端：

```javascript
const WebSocket = require('ws');
let count = 0;
let ws = new WebSocket('ws://localhost:3000/ws/chat');
//打开链接，给服务器发送一条信息
ws.on('open', function () {
    console.log(`[CLIENT] open()`);
    ws.send('Hello!');
});
//监听服务器是否有信息发送过来
ws.on('message', function (message) {
    console.log(`[CLIENT] Received: ${message}`);
    count++;
    if (count > 3) {
        ws.send('Goodbye!');
        ws.close();
    } else {
        setTimeout(() => {
            ws.send(`Hello, I'm Mr No.${count}!`);
        }, 1000);
    }
});
```

运行后：

```
ws server started at port 3000...
[SERVER] connection()
[CLIENT] open()
[SERVER] Received: Hello!
[CLIENT] Received: What's your name?
[SERVER] Received: Hello, I'm Mr No.1!
[CLIENT] Received: What's your name?
[SERVER] Received: Hello, I'm Mr No.2!
[CLIENT] Received: What's your name?
[SERVER] Received: Hello, I'm Mr No.3!
[CLIENT] Received: What's your name?
[SERVER] Received: Goodbye!
[SERVER] error: Error: not opened

end'
```

> 参考：[websocket](http://www.liaoxuefeng.com/wiki/001434446689867b27157e896e74d51a89c25cc8b43bdb3000/0014727922914053479c966220f47da91991fa9c27ac3ea000)

### WebSocket原生事件

| 事件      | 事件处理程序           | 描述            |
| ------- | ---------------- | ------------- |
| open    | Socket.onopen    | 连接建立时触发       |
| message | Socket.onmessage | 客户端接收服务端数据时触发 |
| error   | Socket.onerror   | 通信发生错误时触发     |
| close   | Socket.onclose   | 连接关闭时触发       |

```javascript
var ws = new WebSocket('ws://localhost:3000/ws/chat');
ws.onopen=function () {
    ws.send('Hello!');
};
```

### WebSocket原生方法

| 方法             | 描述       |
| -------------- | -------- |
| Socket.send()  | 使用连接发送数据 |
| Socket.close() | 关闭连接     |

### readyState

| 特性常量                 | 取值 | 状态             |
| -------------------- | -- | -------------- |
| WebSocket.CONNECTING | 0  | 连接正在进行中，但还未建立  |
| WebSocket.OPEN       | 1  | 连接已建立，消息可以开始传递 |
| WebSocket.CLOSING    | 2  | 连接正在进行关闭       |
| WebSocket.CLOSED     | 3  | 连接已关闭          |

#### 连接池

每一个连接都放到一个数组中，组成连接池。

```javascript
// 连接池
var clients = [];

wss.on('connection', function(ws) {
    // 将该连接加入连接池
    clients.push(ws);
    ws.on('message', function(message) {
        // 广播消息
        clients.forEach(function(ws1){
            if(ws1 !== ws) {
                ws1.send(message);
            }
       })
    });
});
```

为了区分每个连接，可以添加`UUID` ，通用唯一识别码（Universally Unique Identifier）

> <http://www.jianshu.com/p/ea0a9a6311cf>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://note.niefee.com/directory-3/websocket.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
