连接世界的纽带:掌握Linux网络设计中的WebSocket服务器

简介: 本文探索了在Linux环境下实现WebSocket服务器的网络设计,将WebSocket服务器作为连接世界的纽带,为读者介绍了如何掌握Linux网络设计中的关键技术。文章从实现WebSocket协议到优化服务器性能和稳定性等方面进行了深入讲解。通过学习本文,读者将能够全面了解WebSocket服务器的原理和工作机制,并获得构建高效、可靠的Linux WebSocket服务器的实用技巧和最佳实践。无论是初学者还是有经验的开发人员,都能从本文中获得宝贵的知识和启发,进一步提升在Linux网络设计中的能力。让我们一同打造连接世界的纽带,掌握Linux网络设计中WebSocket服务器的精髓。

websocket描述

websocket是在单个TCP连接上进行全双工通信的协议,允许Server主动向Client推送数据。
客户端和服务器只需要完成一次握手,就可以创建持久性的连接,进行双向数据传输。
websocket是独立的,作用在TCP上的协议。
为了向前兼容, WebSocket 协议使用 HTTP Upgrade 协议升级机制来进行 WebSocket 握手, 当握手完成之后, 客户端和服务端就可以依据WebSocket 协议的规范格式进行数据传输。

websocket相对HTTP协议的优点

1、支持双向通信,数据的实时性更新更强。
2、开销小;客户端和服务端进行数据通信时,websocket的header(数据头)较小。服务端到客户端的header只有2~10 Bytes,客户端到服务端的需要加上额外的4 Bytes的masking-key。而HTTP协议每次通信都需要携带完整的数据头。
3、扩展性。
4、二进制数据支持更好。

websocket的应用场景

从websocket的优点可以知道,主要应用场景有:
1、视频弹幕
2、媒体即时通讯
3、需要实时位置/数据的应用
5、金融行业的股票基金价格实时更新
等。

websocket握手

1、客户端:Upgrade(申请升级到websocket协议)

协议包含两个部分:握手和数据传输。WebSocket复用了HTTP的握手通道。
客户端通过HTTP请求与WebSocket服务端协商升级到websocket协议。协议升级完成后,后续的数据传输按照WebSocket的data frame进行。
WebSocket 握手采用 HTTP Upgrade 机制,使用标准的HTTP报文格式,只支持使用HTTP的GET方法,客户端发送如下所示的结构发起握手:

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

说明:

参数 含义
Upgrade: websocket 升级到websocket协议
Connection: Upgrade 升级协议
Sec-WebSocket-Key: (key value) 与服务端响应的sec-websocket-accept对应,提供安全防护
Sec-WebSocket-Version: 13 指示websocket的版本

2、服务器:响应协议升级

服务端如果支持 WebSocket 协议,则返回 101 的 HTTP 状态码。返回如下所示的结构:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13

参数说明:

参数 说明
Sec-WebSocket-Accept 必须有,与客户端的Sec-WebSocket-Key对应
Sec-WebSocket-Version 必须有, 返回服务端和客户端都支持的 WebSocket 协议版本。如果服务端不支持客户端的协议版本则立即终止握手, 并返回 HTTP 426 状态码,同时设置 Sec-WebSocket-Version 说明服务端支持的 WebSocket 协议版本列表
Sec-WebSocket-Protocol 可选, 是否支持 WebSocket 子协议
Sec-WebSocket-Extensions 可选, 是否支持拓展列表

注意:每个HTTP的header都以\r\n结尾,并且最后一行要加上一个额外的\r\n。这是由于http协议制定的时候,就是用分隔符进行分包。

3、Sec-WebSocket-Accept值的计算

客户端发起握手时通过 Sec-WebSocket-Key 传递了一个安全防护字符串,服务端将该值与 WebSocket 魔数 "258EAFA5-E914-47DA- 95CA-C5AB0DC85B11" 进行字符串拼接,将得到的字符串做 SHA-1 哈希, 将得到的哈希值再做 base64 编码,最后得到的值就是Sec-WebSocket-Accept值。
计算公式为:
(1)将Sec-WebSocket-Key的值与258EAFA5-E914-47DA-95CA-C5AB0DC85B11魔数进行字符串拼接;
(2)使用SHA1对拼接的字符串做哈希,得到一个哈希值;
(3)将哈希值做base64编码得到Sec-WebSocket-Accept值。
伪代码:


//......

// 字符串拼接
char *str=Sec-WebSocket-Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

// 计算sha1哈希
char sec_data[64];
SHA1(str,strlen(str),sec_data);

// 编码成base64
char sec_accept[64];
base64_encode(sec_data,strlen(sec_data),sec_accept);
//......

base64_encode函数实现:

#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

int base64_encode(char *in_str,int in_len,char *out_str)
{
   
   
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    size_t size = 0;

    if (in_str == NULL || out_str == NULL)
        return -1;

    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);

    BIO_write(bio, in_str, in_len);
    BIO_flush(bio);

    BIO_get_mem_ptr(bio, &bptr);

    memcpy(out_str, bptr->data, bptr->length);
    out_str[bptr->length - 1] = '\0';
    size = bptr->length;
    BIO_free_all(bio);

    return size;
}

WebSocket 数据帧 (data frame)

WebSocket 协议以 frame 为最小单位传输数据,当一条message(消息)过长时,发送方可以将message(消息)拆分成多个 frame 发送,接收方收到以后再重新拼接、解码还原出一条完整的message(消息)。
WebSocket 协议的data frame 的结构如下所示(从左到右,单位是比特):

  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 1 bit 指示当前的 frame是否是消息的最后一个切片。1 表示这是消息(message)的最后一个分片(fragment);0 表示不是是消息(message)的最后一个分片(fragment)
RSV1~3 1 bit 一般情况下全为0。使用WebSocket扩展时,这三个标志位可以非0,由扩展进行定义。注意,如果这三个数是非零的值,并且并没有使用WebSocket扩展,接收方应该立刻终止websocket的连接。
opcode 4 bit 操作代码,指示data frame 的类型,决定了数据载荷(data payload)的解析方式。如果操作代码是非法的,那么接收端应该断开连接
mask 1 bit 指示是否要对数据载荷(data payload)进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。即所有客户端发送到服务端的数据帧,Mask必须为1,如果服务端接收到的数据没有进行掩码操作,服务端应该断开连接。
Payload len 7 bit 指示数据载荷的长度,单位是字节。该字段的长度有三种可能:7 bit ,7 + 16 bit ,7 + 64 bit。当 数据载荷(Payload )的实际长度 <126 时, 则 此字段的长度为 7bit, 直接代表了数据载荷的实际长度;当 此字段为 126 时, 则其后跟随的 16 bit将被解释为 16-bit 的无符号整数, 该整数的值指示数据载荷的实际长度; 当 此字段为 127 时, 其后的 64 bit将被解释为 64-bit 的无符号整数, 该整数的值指示数据载荷的实际长度。注意,如果payload length占用了多个字节的话,payload length的二进制表达采用网络序(需要解决大小端问题)
Masking-key 32 bit 可选字段,如果 Mask 为 1 ,Masking-key 字段存在,长度为 32 bit(4字节),所有由客户端发往服务端的data frame 都必须使用掩码覆盖; 如果Mask为0,则没有Masking-key。注意,载荷数据的长度,不包括masking-key的长度
Payload 0~64bit 数据载荷,长度不固定,是 fram的数据部分。 如果使用了 WebSocket 扩展,扩展数据 (Extension data) 也将存放在这里, 扩展数据 + 应用数据, Payload Len 字段指示的值等于它们的长度和

opcode可选操作代码:

操作码 含义
0x0 特殊,表示一个延续帧。本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片
0x1 表示这是一个文本帧
0x2 表示这是一个二进制帧
0x3-0x7 保留
0x8 连接断开
0x9 ping操作
0xA pong操作
0xB-0xF 保留

掩码算法unmask

Masking-key是由客户端发送过来的32位的随机数。Masking-key不影响数据载荷的长度。掩码、反掩码操作都采用如下算法:
以字节为步长遍历 Payload, 对于 Payload 的第 i 个字节, 首先做 i 对 4 取模得到 j, 则掩码覆盖后的 Payload 的第 i 个字节的值为原先 Payload 第 i 个字节与 Masking-Key 的第 j 个字节做按位异或操作。
伪代码如下:

如果
original-octet-i:为原始数据的第i字节。
transformed-octet-i:为转换后的数据的第i字节。
j=i mod 4。
masking-key-octet-j:为mask key第j字节。
则算法描述为: 
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j

掩码/反掩码的代码实现:

void umask(char *payload, int len, char *mask)
{
   
   
    int i = 0;
    for (i = 0; i < len; i++)
    {
   
   
        payload[i] ^= mask[i % 4];
    }
}

websocket数据传输

WebSocket的客户端、服务端握手成功后,就可以进行数据传输,以data frame进行传递。opcode区数据载荷的类型,0x0~0x2。

消息(message)分片

当要发送的一条消息(message)很长或者消息(message)长度不能预测时, 消息可以切分成多个frame发出;接收方收到一个frame时,根据FIN的值来判断是否是最后一个frame。

消息分片可以实现发送端在一条 TCP 链路上进行多份数据并发的发往接收端。

消息分片主要利用 frame Header 的 FIN 和 Opcode 字段来实现。
消息分片例子(以文本消息为例,分N片):

第一片:FIN=0,opcode=1;
第二片:FIN=0,opcode=0......
第N-1片:FIN=0,opcode=0;
第N片:FIN=1,opcode=1

接收端按序拼接分片得到完整的消息(message)。

心跳包--保持连接

有些场景,客户端、服务端虽然长时间没有数据交互,但仍需要保持连接。这个时候,可以采用心跳来实现。
逻辑:
发送方 --> 接收方:ping,探测,实现 WebSocket 的 Keep-Alive,可以有Payload。
接收方 --> 发送方:pong,Ping 的响应,Payload 的内容需要和 Ping frame 相同
ping、pong的操作对应opcode分别是0x9、0xA。

Sec-WebSocket-Key/Sec-WebSocket-Accept的作用

Sec-WebSocket-Key主要目的不是确保数据的安全性,最主要作用是提供基本的安全防护,减少恶意连接。连接是否安全、数据是否安全、客户端/服务端是否合法,并没有实际性的保证。

数据掩码(Masking-key)的作用

WebSocket协议中,数据掩码的作用是增强websocket协议的安全性,并不是为了保护数据本身。

数据掩码并不是为了防止数据泄密,而是为了防止代理缓存污染攻击(proxy cache poisoning attacks) 问题。

websocket服务器实现

处理流程:
1、接收到client发送的请求升级协议包
2、解析请求包,获取Sec-WebSocket-Key字符串,转换到数据解析状态
3、解析升级协议包,获取相关信息,转换到数据交互状态
4、打包websocket协议头,发送frame。

代码简单示例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>

#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

#include <assert.h>

#define GUID                "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define BUFFER_LENGTH        1024

enum {
   
   
    WS_HANDSHARK = 0,
    WS_TRANMISSION=1,
    WS_END=2,
    WS_COUNT
};

struct ws_ophdr
{
   
   
    unsigned char opencode : 4,
        rsv3 : 1,
        rsv2 : 1,
        rsv1 : 1,
        fin : 1;
    unsigned char pl_len : 7,
        mask : 1;

};

struct ntyevent {
   
   
    int fd;
    int events;
    void *arg;
    int (*callback)(int fd, int events, void *arg);

    int status;
    char buffer[BUFFER_LENGTH];
    int length;
    //long last_active;

    char wbuffer[BUFFER_LENGTH]; //response
    int wlength;

    char sec_accept[ACCEPT_KEY_LENGTH];

    int wsstatus; //0, 1, 2, 3
    char mask_key[4];

};
/*
......
*/
// 按行读取数据
int readline(char *buffer,int idx,char *linebuffer)
{
   
   
    int len = strlen(buffer);
    for (; idx < len; ++idx)
    {
   
   
        if (buffer[idx] == '\r' && buffer[idx + 1] == '\n')
            return idx + 2;
        *(linebuffer++) = buffer[idx];
    }
    return -1;
}
// base64 encode
int base64_encode(char *in_str,int in_len,char *out_str)
{
   
   
    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    size_t size = 0;

    if (in_str == NULL || out_str == NULL)
        return -1;

    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);

    BIO_write(bio, in_str, in_len);
    BIO_flush(bio);

    BIO_get_mem_ptr(bio, &bptr);

    memcpy(out_str, bptr->data, bptr->length);
    out_str[bptr->length - 1] = '\0';
    size = bptr->length;
    BIO_free_all(bio);

    return size;
}
// 握手,获取Sec-WebSocket-Key的字符串,并转换为Sec-WebSocket-Accept所需字符串
int ws_open_shark(struct ntyevent *ev)
{
   
   
    int idx = 0;
    char sec_data[128] = {
   
    0 };
    char sec_accept[128] = {
   
    0 };
    do
    {
   
   
        char linebuff[BUFFER_LENGTH] = {
   
    0 };
        idx = readline(ev->buffer, idx, linebuff);
        if (strstr(linebuff, "Sec-WebSocket-Key"))
        {
   
   
            strcat(linebuff, GUID);
            int keylen = strlen("Sec-WebSocket-Key: ");
            SHA1(linebuff + keylen, strlen(linebuff + keylen), sec_data);
            base64_encode(sec_data, strlen(sec_data), sec_accept);
            printf("index %d, line : %s\n", idx, sec_accept);
            memcpy(ev->sec_accept, sec_accept, ACCEPT_KEY_LENGTH);
        }

    } while ((ev->buffer[idx] != '\r' || ev->buffer[idx + 1] != '\n')
        && idx != -1);
    return 0;
}
// 掩码、反掩码
void umask(char *payload, int len, char *mask)
{
   
   
    int i = 0;
    for (i = 0; i < len; i++)
    {
   
   
        payload[i] ^= mask[i % 4];
    }
}
// 解析payloade数据
int ws_tranmission(struct ntyevent *ev)
{
   
   
    struct ws_ophdr *ophdr = (struct ws_ophdr *)ev->buffer;
    char *payload = NULL;
    size_t datalen = 0;

    int mask_key_offset = 0;

    if (ophdr->pl_len < 126)
    {
   
   
        if (ophdr->mask)
        {
   
   
            payload = ev->buffer + 6;
            mask_key_offset = 2;
            umask(payload, ophdr->pl_len, ev->buffer + mask_key_offset);
        }
        else
            payload = ev->buffer + 2;

        datalen = ophdr->pl_len;

    }
    else if(ophdr->pl_len == 126)
    {
   
   
        printf("%x %x\n", (unsigned char)ev->buffer[2], (unsigned char)ev->buffer[3]);
        datalen = (((unsigned char)ev->buffer[2]) << 8) | ((unsigned char)ev->buffer[3]);
        if (ophdr->mask)
        {
   
   
            payload = ev->buffer + 8;
            mask_key_offset = 4;
            umask(payload, datalen, ev->buffer + mask_key_offset);
        }
        else
            payload = ev->buffer + 4;



    }
    else if (ophdr->pl_len == 127)
    {
   
   
        int i = 0;
        for (i = 2; i <10; i++)
        {
   
   
            datalen |= ((unsigned char)ev->buffer[i]);
            if (i + 1 < 10)
                datalen <<= 8;
        }

        if (ophdr->mask)
        {
   
   
            payload = ev->buffer + 14;
            mask_key_offset = 10;
            umask(payload, datalen, ev->buffer + mask_key_offset);
        }
        else
            payload = ev->buffer + 10;


    }
    else
        assert(0);
    printf("fin : %d\n", ophdr->fin);
    printf("rsv1: %d,rsv2: %d,rsv3: %d\n", ophdr->rsv1, ophdr->rsv2, ophdr->rsv3);
    printf("opcode: %d\n", ophdr->opencode);
    printf("mask : %d\n", ophdr->mask);
    printf("payload len : %d\n", ophdr->pl_len);
    if (mask_key_offset)
        printf("mask-key: %x %x %x %x\n",
            ev->buffer[mask_key_offset],
            ev->buffer[mask_key_offset + 1],
            ev->buffer[mask_key_offset + 2],
            ev->buffer[mask_key_offset + 3]);
    printf("data len: %lu\n", datalen);
    printf("payload data [len = %ld]: %s\n", strlen(payload), payload);


    strcpy(ev->wbuffer, payload);
    ev->wlength = datalen;
    memcpy(ev->mask_key, ev->buffer + mask_key_offset, 4);

    return datalen;
}
// 解析获取申请升级协议请求,转换状态
void ws_status(struct ntyevent *ev)
{
   
   
    char linebuff[BUFFER_LENGTH] = {
   
    0 };
    readline(ev->buffer, 0, linebuff);
    if (strstr(linebuff, "GET "))
        ev->wsstatus = WS_HANDSHARK;
    else
        ev->wsstatus = WS_TRANMISSION;
}
// 响应请求
int ws_request(struct ntyevent *ev)
{
   
   
    ev->wlength = ev->length;
    ws_status(ev);
    if (ev->wsstatus == WS_HANDSHARK)
    {
   
   
        ws_open_shark(ev);
    }
    else if (ev->wsstatus = WS_TRANMISSION)
    {
   
   
        ws_tranmission(ev);
    }
} 
// 处理大小端的函数
void ws_inverted_string(char *str,int len)
{
   
   
    int i = 0;
    char temp;
    for (i = 0; i < len / 2; ++i)
    {
   
   
        temp = *(str + i);
        *(str + i) = *(str + len - i - 1);
        *(str + len - i - 1) = temp;
    }
}
// 发送websocket的header
int ws_send_hdr(struct ntyevent *ev)
{
   
   
    struct ws_ophdr ophdr;

    char extend[16] = {
   
    0 };
    int extend_length = 0;
    int ret = 0;

    ophdr.fin = 1;
    ophdr.rsv1 = ophdr.rsv2 = ophdr.rsv3 = 0;
    ophdr.mask = 1;
    ophdr.opencode = 1;

    if (ev->wlength<126)
        ophdr.pl_len = ev->wlength;
    else if (ev->wlength < 0xFFFF)
    {
   
   
        ophdr.pl_len = 126;
        extend_length += 2;
        extend[2] = (ev->wlength >> 8) & 0xFF;
        extend[3] = ev->wlength & 0xFF;
        printf("plelode length: %x%x\n", extend[2], extend[3]);
    }
    else
    {
   
   
        ophdr.pl_len = 127;
        extend_length += 8;
        printf("plelode length: ");
        int i = 0;
        for (i = 0; i<8; i++)
        {
   
   
            extend[i+2] = (ev->wlength >> ((7-i)*8)) & 0xFF;
            printf("%x", extend[i+2]);
        }

        // 处理大小端问题
        //ws_inverted_string((char *)extend + 2, sizeof(unsigned long long));

        printf("\n");
    }

    extend_length += 2;// ophdr length

    if (ophdr.mask)
    {
   
   
        printf("mask key start index: %d\n", extend_length);
        extend[extend_length]   = ev->mask_key[0];
        extend[extend_length+1] = ev->mask_key[1];
        extend[extend_length+2] = ev->mask_key[2];
        extend[extend_length+3] = ev->mask_key[3];

        printf("mask-key: %x %x %x %x\n",
            extend[extend_length],
            extend[extend_length + 1],
            extend[extend_length + 2],
            extend[extend_length + 3]);

        umask(ev->wbuffer, ev->wlength, ev->mask_key);

        extend_length += 4;
        printf("mask key end index: %d\n", extend_length);
    }

    printf("fin: %d\nmask: %d\nopcode: %d\n", ophdr.fin, ophdr.mask, ophdr.opencode);
    printf("send hdr[%d],extend_length=%d\n\n", ophdr.pl_len, extend_length);


    char *tmp = (char*)&ophdr;
    extend[0] = tmp[0];
    extend[1] = tmp[1];

    struct ws_ophdr_mask *maskkey=(struct ws_ophdr_mask *)extend;
    printf("mask key: %s,%d,%s\n",ev->mask_key,
        maskkey->mask,
        maskkey->mask_key);

    int i = 0;
    printf("\n\nALL: ");
    for (i = 0; i < extend_length; i++)
    {
   
   
        printf("%x ", extend[i]);
    }
    printf("\n\n");

    send(ev->fd, &extend, extend_length, 0);

    return ret;
}
// 响应请求
int ws_response(struct ntyevent *ev)
{
   
   

    if (ev->wsstatus == WS_HANDSHARK)
    {
   
   
        ev->wlength = sprintf(ev->wbuffer, "HTTP/1.1 101 Switching Protocols\r\n"
            "Upgrade: websocket\r\n"
            "Connection: Upgrade\r\n"
            "Sec-WebSocket-Accept: %s\r\n\r\n", ev->sec_accept);
    }
    else if (ev->wsstatus = WS_TRANMISSION)
    {
   
   
        ws_send_hdr(ev);
    }


    return ev->wlength;
}
/*
......
*/
int main(int argc,char *argv[])
{
   
   
    int listenfd=socket(AF_INET,SOCK_STREAM,0);

    struct sockaddr_in server;
    memset(&server,0,sizeof(server));

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(8888);

    bind(listenfd,(struct sockaddr*)&server,sizeof(server));

    if(listen(listenfd,10)<0)
        return -1;

    while(1)
    {
   
   
        struct sockaddr_in client;
        socklen_t len=sizeof(client);
        int clientfd=accept(listenfd,(struct sockaddr *)&client,&len);

        struct ntyevent ev;
        memset(&ev,0,sizeof(ev));
        recv(clientfd,ev.buffer,BUFFER_LENGTH,0);

        // 解析请求
        ws_request(&ev);
        // 响应请求
        ws_response(&ev);

        send(clientfd,ev->wbuffer,ev->wlength,0);
    }
    return 0;
}

总结

WebSocket 协议主要为了解决 HTTP/1.x 缺少双向通信机制的问题, 它使用 TCP 作为传输层协议, 使用 HTTP Upgrade 机制来握手,它与 HTTP 是相互独立的协议, 二者没有上下的分层关系。
image.png

目录
相关文章
|
7天前
|
机器学习/深度学习 缓存 监控
linux查看CPU、内存、网络、磁盘IO命令
`Linux`系统中,使用`top`命令查看CPU状态,要查看CPU详细信息,可利用`cat /proc/cpuinfo`相关命令。`free`命令用于查看内存使用情况。网络相关命令包括`ifconfig`(查看网卡状态)、`ifdown/ifup`(禁用/启用网卡)、`netstat`(列出网络连接,如`-tuln`组合)以及`nslookup`、`ping`、`telnet`、`traceroute`等。磁盘IO方面,`iostat`(如`-k -p ALL`)显示磁盘IO统计,`iotop`(如`-o -d 1`)则用于查看磁盘IO瓶颈。
|
17小时前
|
监控 负载均衡 网络协议
|
2天前
|
监控 Linux 网络安全
Linux服务器如何查询连接服务器的IP
【4月更文挑战第17天】Linux服务器如何查询连接服务器的IP
7 1
|
2天前
|
Linux
如何将一个linux服务器挂载到另外一个linux服务器上
如何将一个linux服务器挂载到另外一个linux服务器上
13 1
|
4天前
|
监控 安全 Linux
Linux系统之安装ServerBee服务器监控工具
【4月更文挑战第22天】Linux系统之安装ServerBee服务器监控工具
43 2
|
4天前
|
网络协议 Linux Shell
【linux网络(一)】初识网络, 理解四层网络模型
【linux网络(一)】初识网络, 理解四层网络模型
|
4天前
|
安全 Ubuntu Linux
Linux 网络操作命令Telnet
Linux 网络操作命令Telnet
20 0
Linux 网络操作命令Telnet
|
5天前
|
Ubuntu Linux
Linux(22) Linux设置网络优先级顺序
Linux(22) Linux设置网络优先级顺序
6 0
|
6天前
|
Ubuntu 网络协议 Linux
Linux(20) Ubuntu 20.04 网络接口自动切换路由配置
Linux(20) Ubuntu 20.04 网络接口自动切换路由配置
28 0
|
8天前
|
网络协议 安全 Linux
IDEA通过内网穿透实现固定公网地址远程SSH连接本地Linux服务器
IDEA通过内网穿透实现固定公网地址远程SSH连接本地Linux服务器

热门文章

最新文章