websocket协议简介

WebSocket是一种在单个TCP连接上进行全双工通信的协议,对比HTTP,它实现了服务器主动向客户端推送信息的能力,解决了HTTP无状态、单向通信的问题。WebSocket协议在建立时通过HTTP握手,成功后则通过TCP通道进行双向数据传输。在实际应用中,WebSocket常用于实时通信需求,如在线聊天、股票更新等场景。
摘要由CSDN通过智能技术生成

概念介绍

  • 单工通信:数据传输只允许在一个方向上传输,只能一方发送数据,另一方接收数据并发送。
  • 半双工:数据传输允许两个方向上的传输,但在同一时间内,只可以有一方发送或接收数据。
  • 全双工:同时可进行双向数据传输。

websocket介绍

  • WebSocket协议在2008年诞生,2011年成为国际标准。
  • WebSocket是一种在单个TCP连接上进行全双工通信的协议,位于 OSI 模型的应用层。
  • WebSocket的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。

出现背景

  • HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
  • 这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。很多网站为了实现推送技术,所用的技术都是轮询。即在特定得时间间隔,由浏览器对服务器发出http请求。

websocket与HTTP比较

http不同版本简介

相同点

  • 都是一样基于TCP的,都是可靠性传输协议。
  • 都是应用层协议。

不同点

  • websocket 是持久连接,http 是短连接;
  • websocket 的协议是以 ws/wss 开头,http 对应的是 http/https;
  • websocket 是有状态的,http 是无状态的;
  • websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据;
  • websocket 连接建立之后,不需要再发送request请求,数据直接从TCP通道传输。

联系

  • WebSocket在建立握手时,数据是通过HTTP传输的。

在这里插入图片描述

websocket握手过程

  • 1、浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
  • 2、TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
  • 3、服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
  • 4、当收到了连接成功的消息后,通过TCP通道进行传输通信。

HTTP协议头

http协议头详解

请求

  • Accept: text/html,application/xhtml+xml,application/xml
  • Accept-Encoding: gzip, deflate, br
  • Accept-Language: zh-CN,zh;q=0.9
  • Connection: keep-alive
  • Host: www.baidu.com
  • User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.88 Safari/537.36

响应

  • Connection: keep-alive
  • Content-Encoding: gzip
  • Content-Type: text/html;charset=utf-8
  • Date: Sat, 16 Apr 2022 10:43:46 GMT
  • Server: BWS/1.1

websocket协议头

请求

  • *Accept-Encoding: gzip, deflate
  • *Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
  • Connection: Upgrade
  • Host: 192.168.1.2:8080
  • Sec-WebSocket-Key: 821VqJT7EjnceB8m7mbwWA==
  • Sec-WebSocket-Version: 13
  • Upgrade: websocket
  • User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36 Edg/100.0.1185.39

响应

  • Connection: Upgrade
  • Date: Sat, 16 Apr 2022 10:49:05 GMT
  • Sec-WebSocket-Accept: paFykwJusIMnfpohWxA5HVpjD1Q=
  • Server: Server
  • Upgrade: websocket

websocket头详解

  • 请求头详解
  • Upgrade: 向服务器指定协议类型,告诉web服务器当前使用的是websocket协议
  • Sec-WebSocket-Key:是一个 Base64 encode 的值,这个是浏览器随机生成的
  • Sec-WebSocket-Version:websocket协议版本
  • 响应头详解(web服务返回状态码101表示协议切换成功)
  • Sec-WebSocket-Accept: 是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。用来证明客户端和服务器之间能进行通信了。

代码展示

服务端

服务端程序是通过QT实现

  • websocketservice.h
#ifndef WEBSOCKETSERVER_H
#define WEBSOCKETSERVER_H

#include <QWidget>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QHostInfo>
#include <QNetworkInterface>

namespace Ui {
class WebSocketServer;
}

class WebSocketServer : public QWidget
{
    Q_OBJECT

public:
    explicit WebSocketServer(QWidget *parent = 0);
    ~WebSocketServer();

private slots:
    void on_pushButton_startListen_clicked();
    void onNewConnection();
    void onTextMessageReceived(QString msg);
    void onDisconnected();


    void on_pushButton_send_clicked();

private:
    Ui::WebSocketServer *ui;
    QWebSocketServer *server;
    QWebSocket *socket;
    QList<QWebSocket*> clientList;

    QString mAddr;
    int mPort;
};

#endif // WEBSOCKETSERVER_H
  • websocketservice.cpp
#include "websocketserver.h"
#include "ui_websocketserver.h"

WebSocketServer::WebSocketServer(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::WebSocketServer)
{
    ui->setupUi(this);
    ui->pushButton_send->setEnabled(false);


    //获取本机IP和端口
    QString hostName = QHostInfo::localHostName();   //获取主机名
    QHostInfo hostInfo = QHostInfo::fromName(hostName); //获取主机信息
    QList<QHostAddress> addList = hostInfo.addresses(); //获取IP地址列表

    QString localIP;
    if(!addList.isEmpty())
    {
        for(int i = 0; i < addList.count();i++)
        {
            QHostAddress aHost = addList.at(i);
            if(QAbstractSocket::IPv4Protocol == aHost.protocol())
            {
                localIP = aHost.toString();
                break;
            }
        }
    }

    ui->lineEdit_url->setText(localIP);
    ui->lineEdit_port->setText("8080");


    //构造:QWebSocketServer(const QString& serverName,QWebSocketServer::SslMode secureMode,QObject *parent=nullptr)
    //使用给定的serverName构造一个新的QWebSocketServer。
    //该服务器名称将在HTTP握手阶段被用来识别服务器。它可以为空,此时不会将服务器名称发送给客户端。
    //SslMode指示服务器是通过wss(SecureMode)还是ws(NonSecureMode)运行
    //QWebSocketServer::SecureMode服务器以安全模式运行(通过wss)
    //QWebSocketServer::NonSecureMode服务器以非安全模式运行(通过ws)

    server=new QWebSocketServer("Server",QWebSocketServer::NonSecureMode,this);

    //有新的连接
    connect(server,&QWebSocketServer::newConnection,this,&WebSocketServer::onNewConnection);
}

WebSocketServer::~WebSocketServer()
{
    delete ui;
}

void WebSocketServer::on_pushButton_startListen_clicked()
{
    QHostAddress address = QHostAddress(ui->lineEdit_url->text());
    if(server->listen(address, ui->lineEdit_port->text().toInt())){
        ui->pushButton_startListen->setEnabled(false);
        ui->pushButton_startListen->setText("disListen");
    }
}

void WebSocketServer::onNewConnection()
{
    socket=server->nextPendingConnection();
    mAddr = socket->peerAddress().toString();
    mPort = socket->peerPort();

    ui->plainTextEdit_clientStatus->appendPlainText("[" + mAddr + ":" + QString::number(mPort) + "]" + " connected...");

    ui->pushButton_send->setEnabled(true);

    connect(socket,&QWebSocket::textMessageReceived, this, &WebSocketServer::onTextMessageReceived);

    ui->plainTextEdit_sendMsg->clear();
    ui->plainTextEdit_sendMsg->appendPlainText("welcome to connect Server!");

    //断开连接时
    connect(socket,&QWebSocket::disconnected, this, &WebSocketServer::onDisconnected);
}

void WebSocketServer::onTextMessageReceived(QString msg)
{
    ui->plainTextEdit_recvMsg->appendPlainText(msg);
}

void WebSocketServer::onDisconnected()
{
    ui->plainTextEdit_clientStatus->appendPlainText("[" + mAddr + ":" + QString::number(mPort) + "]" + " disConnected!");
}

void WebSocketServer::on_pushButton_send_clicked()
{
    socket->sendTextMessage(ui->plainTextEdit_sendMsg->toPlainText());
}

客户端

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Qt WebSocket Demo</title>
</head>

<body>
    <input type="text" id="edit_url" value="ws://192.168.1.2:8080" />
    <input type="button" id="btn_open" value="open" onclick="doOpen()" />
    <br />
    <p>Recv:</p>
    <br />
    <textarea id="edit_recv" cols="50" rows="10"></textarea>
    <br />
    <p>Send:</p>
    <br />
    <textarea id="edit_send" cols="50" rows="10">Hello, I am websocket Client!</textarea>
    <br />
    <input type="button" value="Send" onclick="doSend()" />
    <script>
        var edit_url = document.getElementById("edit_url");
        var btn_open = document.getElementById("btn_open");
        var edit_recv = document.getElementById("edit_recv");
        var edit_send = document.getElementById("edit_send");

        var client = null;

        function doOpen() {
            console.log("open")
            if (!("WebSocket" in window)) {
                //不支持WebSocket
				console.log("no websocket")
                return;
            }
            if (client === null) {
                client = new WebSocket(edit_url.value);

                client.onopen = function () {
                    btn_open.value = "Close";
                }
                //收到数据后追加到尾巴上
                client.onmessage = function (event) {
                    edit_recv.value += String(event.data);
                }
                client.onclose = function () {
                    client = null;
                    btn_open.value = "Open";
                }
            } else {
                client.close();
                client = null;
            }
        }

        function doSend() {
            console.log("send")
            if (client === null)
                return;
            client.send(edit_send.value);
        }
    </script>
</body>

</html>

演示

  • 可以看到只需发一次request请求,后续就不需要再发request请求,可以直接进行数据的传输,并且服务端也能主动发送消息给客户端
    在这里插入图片描述
    在这里插入图片描述

抓包分析

  • 我下载了一个简易的网络库mongoose,可以把这个传到Linux服务器上,也可以实现websocket服务。
  • mongoose-master/examples/websocket-server 这个目录下就是websocket服务程序,修改main.c中的ip和端口,直接make就可以启动。同时该目录下有test.html的测试文件,我们可以拷贝到本地进行测试。
  • 客户端给服务端发送一个hello word,同时服务端也回复了一个hello word
    在这里插入图片描述
  • 可以看到,客户端发送请求时,先通过TCP建立连接,然后发送了一个HTTP的GET请求,服务端通过HTTP返回一个101,101表示的就是协议转换,告诉客户端下面要转换协议了。再往下就是通过websocket协议进行的一次数据交互。
  • 客户端GET请求详细信息。客户端通过Upgrade字段告诉服务端要使用websocket协议。
    在这里插入图片描述
  • 服务端响应详细信息。服务端返回101,表示进行协议的切换。
    在这里插入图片描述
  • 协议协商完成后,后面就是通过websocket协议进行数据传输
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大草原的小灰灰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值