前言

本文介绍使用openssl或者基于qt去实现websocket服务器,简单介绍websocket通信格式,以及附上主要的代码。

安装openssl

安装前准备:
安装gcc:

yum -y install gcc   #安装
yum update gcc       #升级
gcc -v               #查看版本号

安装perl:

wget https://www.cpan.org/src/5.0/perl-5.28.0.tar.gz
tar -zxvf perl-5.28.0.tar.gz    
cd perl-5.28.0
./Configure -des -Dprefix=$HOME/localperl
make
make test
make install
perl -v       #查看版本号

安装openssl方式1:

OpenSSL最新版本下载地址:http://www.openssl.org/source/
在这里插入图片描述

放置到对应目录下——解压——进入目录
然后执行如下命令:

./configure

make

sudo make install

头文件位置在:

/usr/local/include/openssl

安装openssl方式2:
通过apt安装

sudo apt-get install openssl
sudo apt-get install libssl-dev

查看安装openssl的版本:

openssl version

websocket握手流程

首先,服务器接收来自客户端的协议握手(刚开始建立的是基础TCP连接),收到的报文大致如下:

GET / HTTP/1.1
Connection:Upgrade
Host:127.0.0.1:8088
Origin:null
Sec-WebSocket-Extensions:x-webkit-deflate-frame
Sec-WebSocket-Key:puVOuWb7rel6z2AVZBKnfw==
Sec-WebSocket-Version:13
Upgrade:websocket

服务器获取其中的 Sec-WebSocket-Key 进行解析,解析方法大致如下:

  • 首先加上字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"(固定的)。
  • 对其进行sha1解码跟base64编码,得到一个“密码”。
  • 把这个密码再次打包一下,然后发给客户端。
  • 如果服务器发给客户端的报文密码一致,则完成了协议握手。

服务端最后打包要发给客户端的报文,示例如下:

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Server:beetle websocket server
Upgrade:WebSocket
Date:Mon, 26 Nov 2012 23:42:44 GMT
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:content-type
Sec-WebSocket-Accept:FCKgUr8c7OsDsLFeJTWrJw6WO8Q=

websocket数据格式简单介绍

客户端、服务器连接之后是基于数据帧传输。

  • 发送端: 将消息切割成多个帧,并发送给服务端。
  • 接收端: 接收消息帧,并将关联的帧重新组装成完整的消息。

websocket协议格式如下

在这里插入图片描述

// 第一个字节

Fin:表示消息是否结束。(1表示为消息尾部, 0表示后续数据包)。

RSV1~3:用于扩展定义(如果没有扩展约定,则必须为0)。

opcode:(第4~7位)数据类型。
如果接收到未知的opcode,接收端必须关闭连接

含义
0x0附加数据帧
0x1文本数据帧
0x2二进制数据帧
0x3-7暂无定义,为以后的非控制帧保留
0x8连接关闭
0x9ping (心跳)
0xApong (心跳)
0xB-F暂无定义,为以后的控制帧保留

客户端和服务端虽然长时间没有数据往来,但仍需保持连接,需要心跳来实现。

  • 发送方-> 接收方 :ping
  • 接收方-> 发送方 :pong
// 第二个字节

MASK:表示数据PayloadData是否经过掩码处理。
客户端—>服务器:数据需要通过掩码处理(接收到的数据不能马上使用)。
服务端—>客户端:数据不需要使用掩码加密。

数据长度:Payload len + Extended payload length(7+16、7+64)
网络字节序,需要转换

Payload len值数据长度
0-125真实长度
126后面2个字节 (Extended payload length) 形成的16位无符号整型数的值
127后面8个字节 (Extended payload length) 形成的64位无符号整型数的值

使用openssl实现服务器

MyWebsocket.h

#ifndef MYWEBSOCKET_H
#define MYWEBSOCKET_H

#include <iostream>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <unistd.h>
#include <string.h>
#include <queue>
#include <mutex>
#include <thread>
#include <atomic>

namespace MyWebsocket
{

#define DECODE_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
#define WEBSOCKET_KEY "Sec-WebSocket-Key"
#define PORT_MAX      65535
#define PORT_MIN      1024
#define BUFF_SIZE     1024
#define DATA_SIZE     1024*1024  // 1mb
#define SHA1_LEN      20
#define DATA_LEVEL1   126  // data: 7+16 bit
#define DATA_LEVEL2   127  // data: 7+64 bit
#define READFD_NUM    20

typedef unsigned char uchar;
typedef unsigned int  uint;
typedef unsigned short ushort;
typedef unsigned long long ull;

// 数据类型
enum OpCode
{
    CODE_ADD   = 0x0,  // 附加帧
    CODE_TEXT  = 0x1,  // 文本帧
    CODE_BIN   = 0x2,  // 二进制帧
    CODE_CLOSE = 0x8,  // 关闭
    CODE_PING  = 0x9,  // ping(心跳) 
    CODE_PONG  = 0xA,  // pong(心跳)
    CODE_UNKNOWN = 0xF // 未知
};

// 消息头
typedef struct frame_head
{
    bool  m_Fin;         // 结尾标志位       1bit
    bool  m_Mask;        // 掩码处理         1bit
    uchar m_Opcode;      // 数据类型         4bit
    uint  m_LoadLen;     // Payload len      7bit
    uint  m_Masking_Key; // 
    ull   m_DataLength;  // Ext payload len  16/64bit

    frame_head()
    {
        m_Fin = true;
        m_Mask = true;
        m_Opcode = 0xf;
        m_LoadLen = 0;
        m_DataLength = 0;
        m_Masking_Key = 0;
    }

    void clear()
    {
        m_Fin = true;
        m_Mask = true;
        m_Opcode = 0xf;
        m_LoadLen = 0;
        m_DataLength = 0;
        m_Masking_Key = 0;
    }
}tFrameHeader;

class Websocket_Server
{
private:
    int  m_Port;      // 端口号
    int  m_Backlog;   // 未完成连接队列和已完成连接队列的总和
    int  m_Sockfd;    // 套接字  
    int  m_Clientfd;
    bool m_bShake;    // 握手成功标志位
    std::atomic<int> m_ClientNum;     // 连接的客户端数量
    std::atomic<bool>   m_bStopTask;  // 停止
    std::atomic<char* > m_pSendBuff;

    std::queue<int> m_Data;
    std::mutex   m_DataMutex;
    std::thread* m_pTasker;

    
    int m_ArrWebClient[READFD_NUM];
private:
    bool SendFrameHead(int fd, tFrameHeader* ptFrameHeader);
    bool FrameHead_Send(int fd, bool fin, OpCode type, ull length);  
    void Run();

    // about the beginning of websocket
    int DataRecv(int fd);
    bool TcpCreate();
    bool Shake_Hand(int fd, char* pKey);
    bool Read_KeyBuff(char* pSrcStr, char* pResStr);
    void SendData();
    bool RecvFrameHead(tFrameHeader* ptFrameHeader);
    int  Base64_encode(char* pSrcStr, int len, char* pResStr);
  
public:
    Websocket_Server(int Port, int Backlog);
    ~Websocket_Server();

    // api for user
    int GetClientNum();      // 获取客户端连接的数量
    bool Start_Task();       // 开启线程
    bool SetData(int data);  // 写入数据
};

} // namespace MyWebsocket

#endif

MyWebsocket.cpp(主要代码)

#include "MyWebsocket.h"

using namespace std;

bool MyWebsocket::Websocket_Server::TcpCreate()
{
    std::cout<<"Port:"<<m_Port<<",backlog:"<<m_Backlog<<endl;
    // 参数检查
    if(m_Port < PORT_MIN || PORT_MAX < m_Port || m_Backlog < 1)
    {
        return false;
    }

    m_Sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == m_Sockfd)
    {
        perror("socket");
        return false;
    }

    int opt = 1; 
    setsockopt(m_Sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));  
    struct sockaddr_in server_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    server_sockaddr.sin_family = PF_INET;
    server_sockaddr.sin_port = m_Port;
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);

    // bind
    if(bind(m_Sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) < 0)
    {
        perror("bind");
        return false;
    }

    // listen
    if(listen(m_Sockfd, m_Backlog) < 0)
    {
        perror("listen");
        return false;
    }

    return true;
}

int MyWebsocket::Websocket_Server::Base64_encode(char* pSrcStr, int len, char* pResStr)
{
    if (NULL == pSrcStr || NULL == pResStr)
    {
        return -1;
    }

    BIO *b64, *bio;
    BUF_MEM *bptr = NULL;
    size_t size = 0;
 
    b64 = BIO_new(BIO_f_base64());
    bio = BIO_new(BIO_s_mem());
    bio = BIO_push(b64, bio);
 
    BIO_write(bio, pSrcStr, len);
    BIO_flush(bio);
 
    BIO_get_mem_ptr(bio, &bptr);
    memcpy(pResStr, bptr->data, bptr->length);
    pResStr[bptr->length] = '\0';
    size = bptr->length;
 
    BIO_free_all(bio);
    return size;
}

/*=============================================================== 
 * 函  数: Read_KeyBuff
 * 功  能: 读取key值
 * 参  数: pSrcStr         源字符串
 *          pResStr        结果字符串
 * 返回值: true            成功
 *         false           失败
 *=============================================================*/
bool MyWebsocket::Websocket_Server::Read_KeyBuff(char* pSrcStr, char* pResStr)
{
    if(NULL == pSrcStr)
    {
        return false;
    }

    int Key_End = -1;
    int Key_Begin = -1;
    int length = strlen(pSrcStr);       // 字符总长
    int step = strlen(WEBSOCKET_KEY);   // 关键字长度
    for(int i = 0; i < length - step; i ++)
    {
        if((':' == *(pSrcStr + step + i))&&(Key_Begin < 0))
        {
            Key_Begin = step + i + 1;
        }

        if(step + i + 2 <= length)
        {
            if(('\r' == *(pSrcStr + step + i + 1)) && ('\n' == *(pSrcStr + step + i + 2)))
            {
                Key_End = step + i;
                if(-1 == Key_Begin)
                {
                    return false;
                }
               break;
            }
        }
        else
        {
            // error data
            return false;
        }
    }
    memcpy(pResStr, pSrcStr + Key_Begin, Key_End - Key_Begin + 1);

    return true;
}

/*=============================================================== 
 * 函  数: SendFrameHead
 * 功  能: 根据自定义结构体,发送数据头
 * 参  数: ptFrameHeader     自定义结构体
 * 返回值: true              成功
 *         false             失败
 *=============================================================*/
bool MyWebsocket::Websocket_Server::SendFrameHead(int fd, tFrameHeader* ptFrameHeader)
{
    bool Res = false;
    if(NULL == ptFrameHeader || fd < 0)
    {
        return Res;
    }
    
    uchar* pHead = NULL;
    uchar byte1 = ptFrameHeader->m_Opcode;
    uchar byte2 = ptFrameHeader->m_LoadLen;
    int Length = 0;

    if(true == ptFrameHeader->m_Fin)
    {
        byte1 += 0x80;
    }

    if(true == ptFrameHeader->m_Mask)
    {
        byte2 += 0x80;
    }

    if(ptFrameHeader->m_LoadLen == DATA_LEVEL1)
    {
        // 7 + 16 bit
        pHead = new uchar(4);
        pHead[0] = byte1;
        pHead[1] = byte2;
        pHead[2] = (ptFrameHeader->m_DataLength>>8)&0xff;
        pHead[3] = (ptFrameHeader->m_DataLength& 0xff);
        Length = 4;
    }
    else if (ptFrameHeader->m_LoadLen == DATA_LEVEL2)
    {
        // 7 + 64 bit
        pHead = new uchar(10);
        pHead[0] = byte1;
        pHead[1] = byte2;
        ull temp = ptFrameHeader->m_DataLength;
        for(int i = 0; i < 8; i ++)
        {
            pHead[2 + i] = ( ptFrameHeader->m_DataLength>>(8 * (7 - i) ) )&0xff;
        }
        Length = 12;
    }
    else if((ptFrameHeader->m_LoadLen > 0)&&(ptFrameHeader->m_LoadLen < DATA_LEVEL1))
    {
        pHead = new uchar(2);
        pHead[0] = byte1;
        pHead[1] = byte2;
        Length = 2;
    }
    else
    {
        std::cout<<"error in data"<<endl;
        return false;
    }

    if(send(fd, pHead, Length, 0) < 1)
    {
        perror("send");
        Res = false;
    }

    if(NULL != pHead)
    {
        delete [] pHead;
    }
    
    return true;
}


/*=============================================================== 
 * 函  数: Shake_Hand
 * 功  能: websocket握手
 * 参  数: pKey             指向"Sec-WebSocket-Key"开头的buf
 * 返回值: true             成功
 *         false            失败
 *=============================================================*/
bool MyWebsocket::Websocket_Server::Shake_Hand(int fd, char* pKey)
{
    if(NULL == pKey || fd < 0)
    {
        return false;
    }

    char SHA1_data[SHA1_LEN + 1];
    char Frame_Head[BUFF_SIZE];
    char Sec_Accept[32];
    memset(SHA1_data, 0, sizeof(SHA1_data));
    memset(Frame_Head, 0, sizeof(Frame_Head));

    // 获取 Sec-WebSocket-Key 的值
    char WSkey[64];
    memset(WSkey, 0, sizeof(WSkey));
    Read_KeyBuff(pKey, WSkey);

    strcat(WSkey, DECODE_STRING);
    SHA1((uchar*)&WSkey + 19, strlen(WSkey + 19), (uchar*)&SHA1_data);
    Base64_encode(SHA1_data, strlen(SHA1_data), Sec_Accept);

    sprintf(Frame_Head, "HTTP/1.1 101 Switching Protocols\r\n" \
                                "Upgrade: websocket\r\n" \
                                "Connection: Upgrade\r\n" \
                                "Sec-WebSocket-Accept:%s\r\n" \
                                "\r\n", Sec_Accept);

    std::cout<<"Response:"<<endl;
    std::cout<<Frame_Head<<endl;

    int Res = write(fd, Frame_Head, sizeof(Frame_Head));
    if(Res < 0)
    {
        perror("send");
        return false;
    }
    
    m_ClientNum ++;
    return true;
}


QT实现websocket服务器(推荐)

推荐使用qt实现websocket服务器功能,对比使用openssl,QWebSocket已经做好了封装,不需要再花时间去调试

Myqwebsockets.h

#ifndef MYQWEBSOCKETS_H
#define MYQWEBSOCKETS_H

#include <QWebSocketServer>
#include <QWebSocket>
#include <QThread>
#include <QList>
#include <QQueue>
#include <QMutexLocker>

class QWEBSOCKETS_QT_EXPORT MyQWebSockets : public QThread
{
    Q_OBJECT

#define DATA_SIZE     1024*1024  // 1mb


public:
    MyQWebSockets(int port, QString strName);
    ~MyQWebSockets();

    void SendData(/*自定义*/);

public slots:
    void onError(QAbstractSocket::SocketError error);
    void OnWriteMsg(/*自定义*/);

signals:
    void WriteMsg(/*自定义*/);

private:
    void NewConnection();
    void RecvTextMsg(const QString &message);
    void RecvBinMsg(const QByteArray &message);

protected:
    void run();

private:
    int m_Port;
    QString m_ServerName;
    QMutex m_DataMutex;
    QMutex m_SockMutex;
    QWebSocketServer* m_pWebServer;

    QList<QWebSocket*> m_ArrWebSock;
};

#endif // MYQWEBSOCKETS_H

Myqwebsockets.cpp

#include "Myqwebsockets.h"

MyQWebSockets::MyQWebSockets(int port, QString strName)
{
    m_ClientNum = 0;
    m_Port = port;
    m_ServerName = strName;
    m_pWebServer = new QWebSocketServer(m_ServerName, QWebSocketServer::NonSecureMode, this);
    m_pWebServer->listen(QHostAddress::Any, m_Port);
    connect(m_pWebServer, &QWebSocketServer::newConnection, this, &MyQWebSockets::NewConnection);
    connect(this, &MyQWebSockets::WriteMsg, this, &MyQWebSockets::OnWriteMsg);
}

MyQWebSockets::~MyQWebSockets()
{
    // do something
}

/*=============================================================== 
 * 函  数: run
 * 功  能: 线程
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::run()
{
    while (true)
    {
    	// do something
    	//...
    	emit WriteMsg(/*自定义*/);  //通知数据发送
    	// ...
    	
        QThread::msleep(10);
    }
}

/*=============================================================== 
 * 函  数: OnWriteMsg
 * 功  能: 数据发送处理
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::OnWriteMsg(/*自定义*/)
{
    QMutexLocker locker(&m_SockMutex);
    for (size_t i = 0; i < m_ArrWebSock.size(); i++)
    {
    	// 判断状态
        if(m_ArrWebSock[i]->state() != QAbstractSocket::ConnectedState)
        {
            continue;
        }
        // send data
    }
}

/*=============================================================== 
 * 函  数: NewConnection
 * 功  能: 新客户端连接处理
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::NewConnection()
{
    // 有未处理的连接
    if(m_pWebServer->hasPendingConnections()) 
    {
        QWebSocket * ClientSocket = m_pWebServer->nextPendingConnection();
        m_SockMutex.lock();
        m_ArrWebSock<<ClientSocket;
        m_SockMutex.unlock();

        connect(ClientSocket, &QWebSocket::textMessageReceived, this, &MyQWebSockets::RecvTextMsg);
        connect(ClientSocket, &QWebSocket::binaryMessageReceived, this, &MyQWebSockets::RecvBinMsg);
        connect(ClientSocket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));

        // 断开处理
        connect(ClientSocket, &QWebSocket::disconnected, this, [ClientSocket, this]
        {
            qDebug()<<"DisConnect:"<<ClientSocket;
            m_SockMutex.lock();
            m_ArrWebSock.removeOne(ClientSocket);
            m_SockMutex.unlock();
            ClientSocket->deleteLater();
        });
    }
}

/*=============================================================== 
 * 函  数: RecvTextMsg
 * 功  能: 接收客户端文本消息并处理
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::RecvTextMsg(const QString &message)
{
    QWebSocket* ClientSocket = qobject_cast<QWebSocket *>(sender());
    qDebug()<<ClientSocket->origin()<<", TextMsg:"<<message;
}

/*=============================================================== 
 * 函  数: RecvBinMsg
 * 功  能: 接收客户端二进制消息并处理
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::RecvBinMsg(const QByteArray &message)
{
    QWebSocket* ClientSocket = qobject_cast<QWebSocket *>(sender());
    qDebug()<<ClientSocket->origin()<<", BinMsg:"<<message;
}

/*=============================================================== 
 * 函  数: onError
 * 功  能: web通信错误提示
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::onError(QAbstractSocket::SocketError error)
{
    QWebSocket * ClientSocket = qobject_cast<QWebSocket *>(sender());
    qDebug()<<ClientSocket->origin()<<", error:"<<ClientSocket->errorString();
}

/*=============================================================== 
 * 函  数: SendData
 * 功  能: 发送数据
 * 参  数: 
 * 返回值: 
 *=============================================================*/
void MyQWebSockets::SendData(/*自定义*/)
{
	// 无客户端时不处理
    if(0 == m_ArrWebSock.size())
    {
        return;
    }

    QMutexLocker locker(&m_DataMutex);
    // 加入数据队列
}
GitHub 加速计划 / li / linux-dash
6
1
下载
A beautiful web dashboard for Linux
最近提交(Master分支:3 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐