概念介绍
- 单工通信:数据传输只允许在一个方向上传输,只能一方发送数据,另一方接收数据并发送。
- 半双工:数据传输允许两个方向上的传输,但在同一时间内,只可以有一方发送或接收数据。
- 全双工:同时可进行双向数据传输。
websocket介绍
- WebSocket是一种在单个TCP连接上进行全双工通信的协议,位于 OSI 模型的应用层。WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。
出现背景
- HTTP 协议是一种无状态的、无连接的、单向的应用层协议。它采用了请求/响应模型。通信请求只能由客户端发起,服务端对请求做出应答处理。
- 这种通信模型有一个弊端:HTTP 协议无法实现服务器主动向客户端发起消息。很多网站为了实现推送技术,所用的技术都是轮询。即在特定得时间间隔,由浏览器对服务器发出http请求。
websocket与HTTP
- websocket虽然是独立于HTTP的一种协议,但是websocket必须依赖 HTTP 协议进行一次握手(在握手阶段是一样的),握手成功后,数据就直接从 TCP通道传输,与 HTTP 无关了。
不同点
- websocket 是持久连接,http 是短连接;
- websocket 的协议是以 ws/wss 开头,http 对应的是 http/https;
- websocket 是有状态的,http 是无状态的;
- websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据;
- websocket 连接建立之后,不需要再发送request请求,数据直接从TCP通道传输。
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协议版本
- Sec-WebSocket-Accept: 是经过服务器确认,并且加密过后的 Sec-WebSocket-Key。用来证明客户端和服务器之间能进行通信了。
代码展示
服务端
服务端程序是通过QT实现
#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
#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>
|