前言
本文介绍使用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 | 连接关闭 | 0x9 | ping (心跳) | 0xA | pong (心跳) | 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
#define SHA1_LEN 20
#define DATA_LEVEL1 126
#define DATA_LEVEL2 127
#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,
CODE_PONG = 0xA,
CODE_UNKNOWN = 0xF
};
typedef struct frame_head
{
bool m_Fin;
bool m_Mask;
uchar m_Opcode;
uint m_LoadLen;
uint m_Masking_Key;
ull m_DataLength;
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();
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();
int GetClientNum();
bool Start_Task();
bool SetData(int data);
};
}
#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);
if(bind(m_Sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) < 0)
{
perror("bind");
return false;
}
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;
}
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
{
return false;
}
}
memcpy(pResStr, pSrcStr + Key_Begin, Key_End - Key_Begin + 1);
return true;
}
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)
{
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)
{
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;
}
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));
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
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.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()
{
}
void MyQWebSockets::run()
{
while (true)
{
emit WriteMsg();
QThread::msleep(10);
}
}
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;
}
}
}
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();
});
}
}
void MyQWebSockets::RecvTextMsg(const QString &message)
{
QWebSocket* ClientSocket = qobject_cast<QWebSocket *>(sender());
qDebug()<<ClientSocket->origin()<<", TextMsg:"<<message;
}
void MyQWebSockets::RecvBinMsg(const QByteArray &message)
{
QWebSocket* ClientSocket = qobject_cast<QWebSocket *>(sender());
qDebug()<<ClientSocket->origin()<<", BinMsg:"<<message;
}
void MyQWebSockets::onError(QAbstractSocket::SocketError error)
{
QWebSocket * ClientSocket = qobject_cast<QWebSocket *>(sender());
qDebug()<<ClientSocket->origin()<<", error:"<<ClientSocket->errorString();
}
void MyQWebSockets::SendData()
{
if(0 == m_ArrWebSock.size())
{
return;
}
QMutexLocker locker(&m_DataMutex);
}
|