mirror of
https://gitee.com/acl-dev/acl.git
synced 2024-12-03 12:28:49 +08:00
376 lines
8.5 KiB
C++
376 lines
8.5 KiB
C++
/**
|
||
* Copyright (C) 2015-2018
|
||
* All rights reserved.
|
||
*
|
||
* AUTHOR(S)
|
||
* niukey@qq.com
|
||
* shuxin.zheng@qq.com
|
||
*
|
||
* VERSION
|
||
* Sun 18 Sep 2016 05:15:52 PM CST
|
||
*/
|
||
|
||
#pragma once
|
||
#include "../acl_cpp_define.hpp"
|
||
#include "../stdlib/noncopyable.hpp"
|
||
|
||
namespace acl {
|
||
|
||
class socket_stream;
|
||
class aio_socket_stream;
|
||
|
||
enum {
|
||
FRAME_CONTINUATION = 0x00,
|
||
FRAME_TEXT = 0x01,
|
||
FRAME_BINARY = 0x02,
|
||
FRAME_RSV3 = 0x03,
|
||
FRAME_RSV4 = 0x04,
|
||
FRAME_RSV5 = 0x05,
|
||
FRAME_RSV6 = 0x06,
|
||
FRAME_RSV7 = 0x07,
|
||
FRAME_CLOSE = 0x08,
|
||
FRAME_PING = 0x09,
|
||
FRAME_PONG = 0x0A,
|
||
FRAME_CTL_RSVB = 0x0B,
|
||
FRAME_CTL_RSVC = 0x0C,
|
||
FRAME_CTL_RSVD = 0x0D,
|
||
FRAME_CTL_RSVE = 0x0E,
|
||
FRAME_CTL_RSVF = 0x0F,
|
||
};
|
||
|
||
struct frame_header {
|
||
bool fin;
|
||
bool rsv1;
|
||
bool rsv2;
|
||
bool rsv3;
|
||
unsigned char opcode:4;
|
||
bool mask;
|
||
unsigned long long payload_len;
|
||
unsigned int masking_key;
|
||
|
||
frame_header() {
|
||
fin = false;
|
||
rsv1 = false;
|
||
rsv2 = false;
|
||
rsv3 = false;
|
||
opcode = FRAME_TEXT;
|
||
mask = false;
|
||
payload_len = 0;
|
||
masking_key = 0;
|
||
}
|
||
};
|
||
|
||
class string;
|
||
|
||
/**
|
||
* websocket 基础类
|
||
*/
|
||
class ACL_CPP_API websocket : public noncopyable {
|
||
public:
|
||
/**
|
||
* 构造方法
|
||
* @param client {socket_stream&}
|
||
*/
|
||
explicit websocket(socket_stream& client);
|
||
~websocket();
|
||
|
||
/**
|
||
* 当类对象被重复使用时,需要通过本方法将状态重重
|
||
*/
|
||
websocket& reset();
|
||
|
||
/**
|
||
* 获得本类对象所绑定的 socket_stream 对象
|
||
* @return {socket_stream&}
|
||
*/
|
||
socket_stream& get_stream() const {
|
||
return client_;
|
||
}
|
||
|
||
/**
|
||
* 设置是否结束的标志位
|
||
* @param yes {bool}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_fin(bool yes);
|
||
|
||
/**
|
||
* 设置保留标志位
|
||
* @param yes {bool}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_rsv1(bool yes);
|
||
|
||
/**
|
||
* 设置保留标志位
|
||
* @param yes {bool}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_rsv2(bool yes);
|
||
|
||
/**
|
||
* 设置保留标志位
|
||
* @param yes {bool}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_rsv3(bool yes);
|
||
|
||
/**
|
||
* 设置数据帧类型,参见上面定义:FRAME_XXX
|
||
* @param type {unsigned char}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_opcode(unsigned char type);
|
||
|
||
/**
|
||
* 设置本数据帧数据载体的总长度
|
||
* @param len {unsigned long long}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_payload_len(unsigned long long len);
|
||
|
||
/**
|
||
* 设置数据帧数据的掩码值,客户端模式下必须设置此项
|
||
* @param mask {unsigned int}
|
||
* @return {websocket&}
|
||
*/
|
||
websocket& set_frame_masking_key(unsigned int mask);
|
||
|
||
/**
|
||
* 发送数制帧中的数据体,可以循环调用本方法发送本帧的数据,发送数据
|
||
* 总长度(即多次调用本方法的数据长度之和)应与 set_frame_payload_len
|
||
* 方法设置的值相同
|
||
* @param data {const void*}
|
||
* @param len {size_t}
|
||
* @return {bool} 发送是否成功
|
||
*/
|
||
bool send_frame_data(const void* data, size_t len);
|
||
bool send_frame_data(void* data, size_t len);
|
||
bool send_frame_data(const char* str);
|
||
bool send_frame_data(char* str);
|
||
|
||
/**
|
||
* 发送 PONG 数据帧
|
||
* @param data {const void*} PONG 数据帧的数据载体,可以为 NULL
|
||
* @param len {size_t} data 数据长度,当 data 为 NULL 或 len 为 0 时,
|
||
* 表示没有数据载荷
|
||
* @return {bool} 发送是否成功
|
||
*/
|
||
bool send_frame_pong(const void* data, size_t len);
|
||
bool send_frame_pong(void* data, size_t len);
|
||
bool send_frame_pong(const char* str);
|
||
bool send_frame_pong(char* str);
|
||
|
||
/**
|
||
* 发送 PING 数据帧
|
||
* @param data {const void*} PING 数据帧的数据载体,可以为 NULL
|
||
* @param len {size_t} data 数据长度,当 data 为 NULL 或 len 为 0 时,
|
||
* 表示没有数据载荷
|
||
* @return {bool} 发送是否成功
|
||
*/
|
||
bool send_frame_ping(const void* data, size_t len);
|
||
bool send_frame_ping(void* data, size_t len);
|
||
bool send_frame_ping(const char* str);
|
||
bool send_frame_ping(char* str);
|
||
|
||
/**
|
||
* 调用非阻塞发送接口异步发送数据,当发送完数据后,应用层应该调用
|
||
* reset() 方法重置状态,在发送一个数据包前,应用层需要调用以上的
|
||
* set_frame_xxx 方法用来设置每一个数据包的帧头信息
|
||
* @param conn {aio_socket_stream&}
|
||
* @param data {void*} 要发送的数据,内部会被修改
|
||
* @param len {size_t} data 数据长度
|
||
* @return {bool} 是否出错
|
||
*/
|
||
bool send_frame_data(aio_socket_stream& conn, void* data, size_t len);
|
||
|
||
/**
|
||
* 异步发送一个 FRAME_TEXT 类型的数据帧
|
||
* @param conn {aio_socket_stream&}
|
||
* @param data {char*}
|
||
* @param len {size_t}
|
||
* @return {bool}
|
||
*/
|
||
bool send_frame_text(aio_socket_stream& conn, char* data, size_t len);
|
||
|
||
/**
|
||
* 异步发送一个 FRAME_BINARY 类型的数据帧
|
||
* @param conn {aio_socket_stream&}
|
||
* @param data {char*}
|
||
* @param len {size_t}
|
||
* @return {bool}
|
||
*/
|
||
bool send_frame_binary(aio_socket_stream& conn, void* data, size_t len);
|
||
|
||
/**
|
||
* 异步发送一个 FRAME_PING 类型的数据帧
|
||
* @param conn {aio_socket_stream&}
|
||
* @param data {char*}
|
||
* @param len {size_t}
|
||
* @return {bool}
|
||
*/
|
||
bool send_frame_ping(aio_socket_stream& conn, void* data, size_t len);
|
||
|
||
/**
|
||
* 异步发送一个 FRAME_PONG 类型的数据帧
|
||
* @param conn {aio_socket_stream&}
|
||
* @param data {char*}
|
||
* @param len {size_t}
|
||
* @return {bool}
|
||
*/
|
||
bool send_frame_pong(aio_socket_stream& conn, void* data, size_t len);
|
||
|
||
/**
|
||
* 读取数据帧帧头
|
||
* @return {bool}
|
||
*/
|
||
bool read_frame_head();
|
||
|
||
/**
|
||
* 读取数据帧数据体,需要循环调用本方法直至正常结束或出错
|
||
* @param buf {void*} 存放数据的缓冲区
|
||
* @param size {size_t} buf 数据缓冲区大小
|
||
* @return {int} 返回值 > 0 表示读到的数据长度需再次读,== 0 表示读结束,
|
||
* < 0 表示读出错
|
||
*/
|
||
int read_frame_data(void* buf, size_t size);
|
||
|
||
/**
|
||
* 用在非阻塞网络通信中,尝试读取 websocket 数据头,可以循环调用本方法
|
||
* 走到该方法返回 true 表示读到了完整的 websocket 头;如果返回 false,
|
||
* 则需通过 eof() 方法来判断网络连接是否已经断开,如 eof() 返回 true,
|
||
* 则应释放本对象
|
||
* @return {bool} 返回 true 表示读到了完整的 websocket 头,可以通过调用
|
||
* read_frame_data() 来读取数据体
|
||
*/
|
||
bool peek_frame_head();
|
||
|
||
/**
|
||
* 用在非阻塞网络通信中,尝试读取 websocket 数据体,可以循环调用本方法
|
||
* @param buf {char*} 存放读到的数据
|
||
* @param size {size_t} buf 的空间大小
|
||
* @return {int} 读到的数据长度,当返回值为:
|
||
* 0: 表示本帧的数据体读完毕
|
||
* -1: 表示读出错,需通过调用 eof() 判断连接是否已经关闭
|
||
* >0: 表示本次读到的数据长度
|
||
*/
|
||
int peek_frame_data(char* buf, size_t size);
|
||
int peek_frame_data(string& buf, size_t size);
|
||
|
||
/**
|
||
* 判断当前是否已读完 websocket 帧头数据
|
||
* @return {bool}
|
||
*/
|
||
bool is_head_finish() const;
|
||
|
||
/**
|
||
* 判断当前网络连接是否已经断开
|
||
* @return {bool}
|
||
*/
|
||
bool eof();
|
||
|
||
/**
|
||
* 获得读到的数据帧的帧头
|
||
* @return {const frame_header&}
|
||
*/
|
||
const frame_header& get_frame_header() const {
|
||
return header_;
|
||
}
|
||
|
||
/**
|
||
* 判断本帧是否为结束帧
|
||
* @return {bool}
|
||
*/
|
||
bool frame_is_fin() const {
|
||
return header_.fin;
|
||
}
|
||
|
||
/**
|
||
* 判断本帧是否设置了保留标志位
|
||
* @return {bool}
|
||
*/
|
||
bool frame_is_rsv1() const {
|
||
return header_.rsv1;
|
||
}
|
||
|
||
/**
|
||
* 判断本帧是否设置了保留标志位
|
||
* @return {bool}
|
||
*/
|
||
bool frame_is_rsv2() const {
|
||
return header_.rsv2;
|
||
}
|
||
|
||
/**
|
||
* 判断本帧是否设置了保留标志位
|
||
* @return {bool}
|
||
*/
|
||
bool frame_is_rsv3() const {
|
||
return header_.rsv3;
|
||
}
|
||
|
||
/**
|
||
* 获得本数据帧的状态码,参见上面:FRAME_XXX
|
||
* @return {unsigned char}
|
||
*/
|
||
unsigned char get_frame_opcode() const {
|
||
return header_.opcode;
|
||
}
|
||
|
||
/**
|
||
* 获得本数据帧是否设置了掩码
|
||
* @return {bool}
|
||
*/
|
||
bool frame_has_mask() const {
|
||
return header_.mask;
|
||
}
|
||
|
||
/**
|
||
* 获得本数据帧的数据体长度
|
||
* @return {unsigned long long}
|
||
*/
|
||
unsigned long long get_frame_payload_len() const {
|
||
return header_.payload_len;
|
||
}
|
||
|
||
/**
|
||
* 获得本数据帧的掩码值
|
||
* @return {unsigned int}
|
||
*/
|
||
unsigned int get_frame_masking_key() const {
|
||
return header_.masking_key;
|
||
}
|
||
|
||
/**
|
||
* 获得本数据帧已读到的数据长度
|
||
* @return {unsigned long long}
|
||
*/
|
||
unsigned long long get_frame_payload_nread() const {
|
||
return payload_nread_;
|
||
}
|
||
|
||
private:
|
||
socket_stream& client_;
|
||
struct frame_header header_;
|
||
char* header_buf_;
|
||
size_t header_size_;
|
||
size_t header_len_;
|
||
unsigned long long payload_nread_;
|
||
unsigned long long payload_nsent_;
|
||
bool header_sent_;
|
||
|
||
unsigned status_;
|
||
string* peek_buf_;
|
||
|
||
void make_frame_header();
|
||
|
||
void update_head_2bytes(unsigned char ch1, unsigned ch2);
|
||
bool peek_head_2bytes();
|
||
bool peek_head_len_2bytes();
|
||
bool peek_head_len_8bytes();
|
||
bool peek_head_masking_key();
|
||
|
||
};
|
||
|
||
} // namespace acl
|