概述
客户端有socket,但网页端有类似socket的websocekt,那么webscoekt到底是如何实现的,今天我们来研究一下。
先抓个包看看websocket通信都发生了啥。
tcp的握手过程暂时不管,先看websocket的握手过程
浏览器的get请求
服务器的回复
websocket握手过程就一个http请求,请求头多带了俩个参数
Upgrade: websocket
Connection: Upgrade
这个时候浏览器要告诉服务器,我要升级到websocket服务,并且会带一个Sec-WebSocket-Key值,Sec-WebSocket-Key 是一个Base64 encode的值,这个是浏览器随机生成的,这时就要服务器去验证并且加密再通过Sec-WebSocket-Accept 应答头返回给浏览器
思路明确了,我们写代码,先写一个socket接收到httpt请求,然后取出来Sec-WebSocket-Key,将其进行加密再返回到浏览器端
//利用epoll创建一个非阻塞的socket
bool Socket::start_socket(int port) {
_server_socket = ::socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_addr;
int oldSocketFlag = fcntl(_server_socket, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if (fcntl(_server_socket, F_SETFL, newSocketFlag) == -1) {
close(_server_socket);
std::cout << "非阻塞失败" << std::endl;
exit(0);
}
int server_len = sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
std::cout << "bind:" << bind(_server_socket, (struct sockaddr *) &server_addr, server_len) << std::endl;
std::cout << "listen:" << listen(_server_socket, 5) << std::endl;
epoll::epoll_add(this->epfd, this->_server_socket, EPOLLIN);
Accept();
return true;
}
//等待连接
int Socket::Accept() {
while (true) {
int nfds = epoll_wait(this->epfd, this->events, EVENT_SIZE, 5);
if (nfds < 0) {
if (errno == EINTR) {
continue;
} else {
break;
}
}
for (int i = 0; i < nfds; i++) {
if (this->events[i].data.fd == this->_server_socket) {
handshake(events[i]);//当第一次请求时进行握手
} else {
switch (events[i].events) {//如果不是第一次连接就对消息进行处理
case EPOLLIN:
std::string socket_message = Socket::Read(events[i].data.fd);
std::string message;
int ret = utils::code::decode_message(socket_message.c_str(), message);
for (const auto &item: poccess) {
if (item(message, ret, events[i].data.fd)) break;
}
break;
}
}
}
}
return 0;
}
//握手的操作
bool Socket::handshake(epoll_event event) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int conn = accept(this->_server_socket, (struct sockaddr *) &client_addr, &client_len);
char buffer[BUF_SIZE];
memset(buffer, 0, sizeof(buffer));
read(conn, buffer, BUF_SIZE);//取出来get请求体
std::map<std::string, std::string> map;
utils::code::decode_accept(buffer, &map);//提取请求头
std::string sec_websocket_accept;
utils::code::encode_accept(&map.find("Sec-WebSocket-Key")->second, &sec_websocket_accept);//对Sec-WebSocket-Key进行加密
std::string buff =
"HTTP/1.1 101 Switching Protocolsrn"
"Upgrade: websocketrn"
"Connection: Upgradern"
"Sec-WebSocket-Accept: " + sec_websocket_accept + "rnrn";
write(conn, buff.c_str(), buff.length());//将应答头返回给浏览器
after_handshake(conn);
epoll::epoll_add(this->epfd, conn, EPOLLIN);
return true;
}
接下来我们看看websocket消息的数据帧,研究他是如何编码的
抓个包看看数据
websocket消息体的结构
编写消息编码和解码的代码:
//消息解码
int utils::code::decode_message(std::string in_messaage, std::string &out_messsage) {
int ret = WS_OPENING_FRAME;
const char *frameData = in_messaage.c_str();
const int frameLength = in_messaage.size();
if (frameLength < 2) {
ret = WS_ERROR_FRAME;
}
//拓展位暂不处理
if ((frameData[0] & 0x70) != 0x0) {
ret = WS_ERROR_FRAME;
}
// fin位: 为1表示已接收完整报文, 为0表示继续监听后续报文
ret = (frameData[0] & 0x80);
if ((frameData[0] & 0x80) != 0x80) {
ret = WS_ERROR_FRAME;
}
// mask位, 为1表示数据被加密
if ((frameData[1] & 0x80) != 0x80) {
ret = WS_ERROR_FRAME;
}
uint16_t payloadLength = 0;
uint8_t payloadFieldExtraBytes = 0;
uint8_t opcode = static_cast<uint8_t >(in_messaage[0] & 0x0f);
if (opcode == WS_TEXT_FRAME) {
payloadLength = static_cast<uint8_t >(in_messaage[1] & 0x7f);
if (payloadLength == 0x7e) {
uint16_t payloadLength16b = 0;
payloadFieldExtraBytes = 2;
memcpy(&payloadLength16b, &frameData[2], payloadFieldExtraBytes);
payloadLength = ntohs(payloadLength16b);
} else if (payloadLength == 0x7f) {
// 数据过长,暂不支持
return WS_ERROR_FRAME;
// ret = WS_ERROR_FRAME;
}
const char *maskingKey = &frameData[2 + payloadFieldExtraBytes];
char *payloadData = new char[payloadLength + 1];
memset(payloadData, 0, payloadLength + 1);
memcpy(payloadData, &frameData[2 + payloadFieldExtraBytes + 4], payloadLength);
for (int i = 0; i < payloadLength; i++) {
payloadData[i] = payloadData[i] ^ maskingKey[i % 4];
}
out_messsage = payloadData;
delete[] payloadData;
return WS_TEXT_FRAME;
}
return opcode;
}
//消息编码
int utils::code::encode_message(std::string in_messaage, std::string &out_message, uint8_t frameType) {
int ret = WS_EMPTY_FRAME;
const uint32_t message_length = in_messaage.size();
if (message_length > 32767) {
return WS_ERROR_FRAME;
}
uint8_t payload_fiel_extr_bytes = (message_length <= 0x7d) ? 0 : 2;
uint8_t frame_header_size = 2 + payload_fiel_extr_bytes;
uint8_t *frame_header = new uint8_t[frame_header_size];
memset(frame_header, 0, frame_header_size);
frame_header[0] = static_cast<uint8_t >(0x80 | frameType);
// 填充数据长度
if (message_length <= 0x7d) {
frame_header[1] = static_cast<uint8_t>(message_length);
} else {
frame_header[1] = 0x7e;
uint16_t len = htons(message_length);
memcpy(&frame_header[2], &len, payload_fiel_extr_bytes);
}
// 填充数据
uint32_t frameSize = frame_header_size + message_length;
char *frame = new char[frameSize + 1];
memcpy(frame, frame_header, frame_header_size);
memcpy(frame + frame_header_size, in_messaage.c_str(), message_length);
frame[frameSize] = '