当前位置:网站首页>Websocket server code protocol analysis, learn to do their own protocol ideas.
Websocket server code protocol analysis, learn to do their own protocol ideas.
2022-07-21 03:03:00 【Johnny burst purple】
Let's first introduce HTTP and Websocket The relationship between .
HTTP Yes 1.1 and 1.0 The said , It's called keep-alive , The multiple HTTP Request to merge into one , however Websocket It's actually a The new agreement , Follow HTTP The agreement has little to do with , Just for compatible Handshake specification of existing browsers , That is to say, it is HTTP A supplement to the agreement can be understood through such a diagram
There is intersection , But not all , It should be noted that both of them are based on tcp Support On the basis of .
Let's first introduce http and websoket The relationship between :
First ,Websocket It's a Persistent protocol , be relative to HTTP such non-durable In terms of the agreement . Let's take a simple example , It is widely used at present PHP Life cycle to explain .
HTTP Through the life cycle of Request To define , That's one Request One Response , So in HTTP1.0 in , This time, HTTP The request is over .
stay HTTP1.1 Improved in , So that there is a keep-alive, in other words , In a HTTP Connecting , Can send multiple Request, Receive multiple Response. But remember Request = Response , stay HTTP China is always like this , That is to say a request There can only be one response. And this response It's also passive , Can't initiate .
Websocket be based on HTTP The agreement is not very accurate , Think of it as Websocket Compatible with HTTP, Or borrow HTTP The protocol to complete part of the handshake .
Look at this picture
Whether it's IPv4 Message or TCP,UDP message , They all have a certain format , The picture above shows websocket Corresponding message format .
In the future, if you are based on tcp do Custom protocol , You need to master these key steps .
- opcode
- Bag length
- mask-key ( It can be understood as the way of data encryption )
- Data section
ad locum , We go through websocket The framework code of the server , Let's explain , In the agreement , how Encapsulate all parts of the message format . At the end of the article , I will give the complete code .
First, let's sort it out ,websocket The client and websocket Server connection process :
One thing we need to know , Why do we say http Agreement or websocket Agreement is be based on tcp Of , In fact, these agreements are just tcp On the Carry out a series of functional packaging , A specific protocol that is encapsulated by a specific protocol rule standard .
When websoket Client and websocket The server is set up tcp After connection . First websocket The client will send the corresponding handshake data to the server port , Carry out the handshake process , Of course, the handshake here , No Refers to tcp Three handshakes during connection ( Three handshakes are tcp Connection process , Here is after connection ).
The data sent by the client contains a paragraph similar to
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
The data of .
The server side decides to name one by one GUID Value , This value is globally unique , It can be understood as websocket A rule of agreement .
The server will match the data sent by the customer service end with the globally unique GUID Value . Get the corresponding hash value through a hash function , Then get the final result from the server through a specific coding method
Sec-WebSocket-Accept :
Use pseudo code to explain this process :
str = strcat(linebuf, GUID); // Connect part of the handshake data sent by the client to the server GUID
sta = SHA1(str); // It is converted to the corresponding hash value by a specific hash function
base64_encode(sta); // By specific encoding , Get the final value Sec-WebSocket-Accept :
Specific handshake code
It is recommended to watch the code , Because the complete code defines many structures and encapsulated functions , So it may not be easy to understand the partial code alone , Therefore, it is recommended to just read and understand the local code later , The specific process , Finally, I suggest you take a good look at the complete code .
int handshark(struct sockitem *si, struct reactor *mainloop) {
char linebuf[256];
char sec_accept[32];
int level = 0;
unsigned char sha1_data[SHA_DIGEST_LENGTH+1] = {
0};
char head[BUFFER_LENGTH] = {
0};
do {
memset(linebuf, 0, sizeof(linebuf));
level = readline(si->recvbuffer, level, linebuf);
if (strstr(linebuf,"Sec-WebSocket-Key") != NULL) {
strcat(linebuf, GUID);
SHA1((unsigned char*)&linebuf+19,strlen(linebuf+19),(unsigned char*)&sha1_data);
base64_encode(sha1_data,strlen(sha1_data),sec_accept);
sprintf(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);
printf("response\n");
printf("%s\n\n\n", head);
#if 0
if (write(cli_fd, head, strlen(head)) < 0) //write ---> send
perror("write");
#else
memset(si->recvbuffer, 0, BUFFER_LENGTH);
memcpy(si->sendbuffer, head, strlen(head)); // to send
si->slength = strlen(head);
// to set epollout events
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
//ev.data.fd = clientfd;
si->sockfd = si->sockfd;
si->callback = send_cb;
si->status = WS_DATATRANSFORM;
ev.data.ptr = si;
epoll_ctl(mainloop->epfd, EPOLL_CTL_MOD, si->sockfd, &ev);
#endif
break;
}
} while((si->recvbuffer[level] != '\r' || si->recvbuffer[level+1] != '\n') && level != -1);
return 0;
}
After handshake , It involves the data exchange process between the server and the client , Next code , It will show you ,websocket How is the message format encapsulated by code .
Notice these two functions
decode_packet // unpacking
encode_packet // Package
int transform(struct sockitem *si, struct reactor *mainloop) {
int ret = 0;
char mask[4] = {
0};
char *data = decode_packet(si->recvbuffer, mask, si->rlength, &ret);
// there decode_packet It is to unpack data packets , Pick out the data section , Discard message header .
printf("data : %s , length : %d\n", data, ret);
ret = encode_packet(si->sendbuffer, mask, data, ret);
// there encode_packet Is to add the header to the data part , To form a complete websocket package .
si->slength = ret;
memset(si->recvbuffer, 0, BUFFER_LENGTH);
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
//ev.data.fd = clientfd;
si->sockfd = si->sockfd;
si->callback = send_cb;
si->status = WS_DATATRANSFORM;
ev.data.ptr = si;
epoll_ctl(mainloop->epfd, EPOLL_CTL_MOD, si->sockfd, &ev);
return 0;
}
summary :
- such as http,websocket agreement , These seemingly tall and incomprehensible agreements , Their essence is based on tcp Connected code , complete Deeper encapsulation , Of course, in the process of packaging , We can use some methods to enhance the efficiency of network programming , For example, it is used here I/O In multiplexing epoll, Finish right Reactor Encapsulation ,Reactor It is an efficient event processing mode .
- Encapsulation of message format , Used several Structure For each part of message format encapsulate , So as to realize the Add head and remove head The function of .
- Key points of custom protocol :
1. opcode 2. Bag length 3. Non essential election :mask-key( Encrypt data ) 4. Data section
Complete server-side code :
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/epoll.h>
#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#define BUFFER_LENGTH 1024
#define GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
enum WEBSOCKET_STATUS {
WS_HANDSHARK,
WS_DATATRANSFORM,
WS_DATAEND,
};
struct sockitem {
//
int sockfd;
int (*callback)(int fd, int events, void *arg);
char recvbuffer[BUFFER_LENGTH]; //
char sendbuffer[BUFFER_LENGTH];
int rlength;
int slength;
int status;
};
// mainloop / eventloop --> epoll -->
struct reactor {
int epfd;
struct epoll_event events[512];
};
struct reactor *eventloop = NULL;
int recv_cb(int fd, int events, void *arg);
int send_cb(int fd, int events, void *arg);
#if 1 // websocket
char* decode_packet(char *stream, char *mask, int length, int *ret);
int encode_packet(char *buffer,char *mask, char *stream, int length);
struct _nty_ophdr {
unsigned char opcode:4,
rsv3:1,
rsv2:1,
rsv1:1,
fin:1;
unsigned char payload_length:7,
mask:1;
} __attribute__ ((packed));
struct _nty_websocket_head_126 {
unsigned short payload_length;
char mask_key[4];
unsigned char data[8];
} __attribute__ ((packed));
struct _nty_websocket_head_127 {
unsigned long long payload_length;
char mask_key[4];
unsigned char data[8];
} __attribute__ ((packed));
typedef struct _nty_websocket_head_127 nty_websocket_head_127;
typedef struct _nty_websocket_head_126 nty_websocket_head_126;
typedef struct _nty_ophdr nty_ophdr;
int base64_encode(char *in_str, int in_len, char *out_str) {
BIO *b64, *bio;
BUF_MEM *bptr = NULL;
size_t size = 0;
if (in_str == NULL || out_str == NULL)
return -1;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_write(bio, in_str, in_len);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bptr);
memcpy(out_str, bptr->data, bptr->length);
out_str[bptr->length-1] = '\0';
size = bptr->length;
BIO_free_all(bio);
return size;
}
int readline(char* allbuf,int level,char* linebuf) {
int len = strlen(allbuf);
for (;level < len; ++level) {
if(allbuf[level]=='\r' && allbuf[level+1]=='\n')
return level+2;
else
*(linebuf++) = allbuf[level];
}
return -1;
}
int handshark(struct sockitem *si, struct reactor *mainloop) {
char linebuf[256];
char sec_accept[32];
int level = 0;
unsigned char sha1_data[SHA_DIGEST_LENGTH+1] = {
0};
char head[BUFFER_LENGTH] = {
0};
do {
memset(linebuf, 0, sizeof(linebuf));
level = readline(si->recvbuffer, level, linebuf);
if (strstr(linebuf,"Sec-WebSocket-Key") != NULL) {
strcat(linebuf, GUID);
SHA1((unsigned char*)&linebuf+19,strlen(linebuf+19),(unsigned char*)&sha1_data);
base64_encode(sha1_data,strlen(sha1_data),sec_accept);
sprintf(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);
printf("response\n");
printf("%s\n\n\n", head);
#if 0
if (write(cli_fd, head, strlen(head)) < 0) //write ---> send
perror("write");
#else
memset(si->recvbuffer, 0, BUFFER_LENGTH);
memcpy(si->sendbuffer, head, strlen(head)); // to send
si->slength = strlen(head);
// to set epollout events
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
//ev.data.fd = clientfd;
si->sockfd = si->sockfd;
si->callback = send_cb;
si->status = WS_DATATRANSFORM;
ev.data.ptr = si;
epoll_ctl(mainloop->epfd, EPOLL_CTL_MOD, si->sockfd, &ev);
#endif
break;
}
} while((si->recvbuffer[level] != '\r' || si->recvbuffer[level+1] != '\n') && level != -1);
return 0;
}
int transform(struct sockitem *si, struct reactor *mainloop) {
int ret = 0;
char mask[4] = {
0};
char *data = decode_packet(si->recvbuffer, mask, si->rlength, &ret);
printf("data : %s , length : %d\n", data, ret);
ret = encode_packet(si->sendbuffer, mask, data, ret);
si->slength = ret;
memset(si->recvbuffer, 0, BUFFER_LENGTH);
struct epoll_event ev;
ev.events = EPOLLOUT | EPOLLET;
//ev.data.fd = clientfd;
si->sockfd = si->sockfd;
si->callback = send_cb;
si->status = WS_DATATRANSFORM;
ev.data.ptr = si;
epoll_ctl(mainloop->epfd, EPOLL_CTL_MOD, si->sockfd, &ev);
return 0;
}
void umask(char *data,int len,char *mask) {
int i;
for (i = 0;i < len;i ++)
*(data+i) ^= *(mask+(i%4));
}
char* decode_packet(char *stream, char *mask, int length, int *ret) {
nty_ophdr *hdr = (nty_ophdr*)stream;
unsigned char *data = stream + sizeof(nty_ophdr);
int size = 0;
int start = 0;
//char mask[4] = {0};
int i = 0;
//if (hdr->fin == 1) return NULL;
if ((hdr->mask & 0x7F) == 126) {
// Someone here may be confused ,mask Isn't it mask flag bit ? How to judge like this .
// Because mask Although it only occupies one place , But point to this one , That is, it points to the whole byte ,
//0x7F == 0111 1111 , I didn't use it when comparing mask position
nty_websocket_head_126 *hdr126 = (nty_websocket_head_126*)data;
size = hdr126->payload_length;
for (i = 0;i < 4;i ++) {
mask[i] = hdr126->mask_key[i];
}
start = 8;
} else if ((hdr->mask & 0x7F) == 127) {
nty_websocket_head_127 *hdr127 = (nty_websocket_head_127*)data;
size = hdr127->payload_length;
for (i = 0;i < 4;i ++) {
mask[i] = hdr127->mask_key[i];
}
start = 14;
} else {
size = hdr->payload_length;
memcpy(mask, data, 4);
start = 6;
}
*ret = size;
umask(stream+start, size, mask);
return stream + start;
}
int encode_packet(char *buffer,char *mask, char *stream, int length) {
nty_ophdr head = {
0};
head.fin = 1;
head.opcode = 1;
int size = 0;
if (length < 126) {
head.payload_length = length;
memcpy(buffer, &head, sizeof(nty_ophdr));
size = 2;
} else if (length < 0xffff) {
nty_websocket_head_126 hdr = {
0};
hdr.payload_length = length;
memcpy(hdr.mask_key, mask, 4);
memcpy(buffer, &head, sizeof(nty_ophdr));
memcpy(buffer+sizeof(nty_ophdr), &hdr, sizeof(nty_websocket_head_126));
size = sizeof(nty_websocket_head_126);//8
//size = sizeof(nty_ophdr) + sizeof(nty_websocket_head_126);
//size = 8;
} else {
nty_websocket_head_127 hdr = {
0};
hdr.payload_length = length;
memcpy(hdr.mask_key, mask, 4);
memcpy(buffer, &head, sizeof(nty_ophdr));
memcpy(buffer+sizeof(nty_ophdr), &hdr, sizeof(nty_websocket_head_127));
size = sizeof(nty_websocket_head_127);
//size = sizeof(nty_ophdr) + sizeof(nty_websocket_head_127);
//size = 14;
}
memcpy(buffer+2, stream, length);
//memcpy(buffer+size, stream, length);
return length + 2;
//return length + size ;
}
#endif
static int set_nonblock(int fd) {
// Set to non blocking
int flags;
flags = fcntl(fd, F_GETFL, 0);
if (flags < 0) return flags;
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) return -1;
return 0;
}
int send_cb(int fd, int events, void *arg) {
struct sockitem *si = (struct sockitem*)arg;
send(fd, si->sendbuffer, si->slength, 0); //
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
//ev.data.fd = clientfd;
si->sockfd = fd;
si->callback = recv_cb;
ev.data.ptr = si;
memset(si->sendbuffer, 0, BUFFER_LENGTH);
epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);
}
// ./epoll 8080
int recv_cb(int fd, int events, void *arg) {
//int clientfd = events[i].data.fd;
struct sockitem *si = (struct sockitem*)arg;
struct epoll_event ev;
int ret = recv(fd, si->recvbuffer, BUFFER_LENGTH, 0);
if (ret < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
//
return -1;
} else {
}
ev.events = EPOLLIN;
//ev.data.fd = fd;
epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);
close(fd);
free(si);
} else if (ret == 0) {
//
//
printf("disconnect %d\n", fd);
ev.events = EPOLLIN;
//ev.data.fd = fd;
epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, fd, &ev);
close(fd);
free(si);
} else {
//printf("Recv: %s, %d Bytes\n", si->recvbuffer, ret);
si->rlength = 0;
if (si->status == WS_HANDSHARK) {
printf("request\n");
printf("%s\n", si->recvbuffer);
handshark(si, eventloop);
} else if (si->status == WS_DATATRANSFORM) {
transform(si, eventloop);
} else if (si->status == WS_DATAEND) {
}
}
}
int accept_cb(int fd, int events, void *arg) {
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(struct sockaddr_in));
socklen_t client_len = sizeof(client_addr);
int clientfd = accept(fd, (struct sockaddr*)&client_addr, &client_len);
if (clientfd <= 0) return -1;
set_nonblock(clientfd);
char str[INET_ADDRSTRLEN] = {
0};
printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
ntohs(client_addr.sin_port));
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
//ev.data.fd = clientfd;
struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
si->sockfd = clientfd;
si->callback = recv_cb;
si->status = WS_HANDSHARK;
ev.data.ptr = si;
epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, clientfd, &ev);
return clientfd;
}
int main(int argc, char *argv[]) {
if (argc < 2) {
return -1;
}
int port = atoi(argv[1]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
return -1;
}
set_nonblock(sockfd);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
return -2;
}
if (listen(sockfd, 5) < 0) {
return -3;
}
eventloop = (struct reactor*)malloc(sizeof(struct reactor));
// epoll opera
eventloop->epfd = epoll_create(1);
struct epoll_event ev;
ev.events = EPOLLIN;
struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
si->sockfd = sockfd;
si->callback = accept_cb;
ev.data.ptr = si;
epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, sockfd, &ev);
while (1) {
int nready = epoll_wait(eventloop->epfd, eventloop->events, 512, -1);
if (nready < -1) {
break;
}
int i = 0;
for (i = 0;i < nready;i ++) {
if (eventloop->events[i].events & EPOLLIN) {
//printf("sockitem\n");
struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr;
si->callback(si->sockfd, eventloop->events[i].events, si);
}
if (eventloop->events[i].events & EPOLLOUT) {
struct sockitem *si = (struct sockitem*)eventloop->events[i].data.ptr;
si->callback(si->sockfd, eventloop->events[i].events, si);
}
}
}
}
边栏推荐
- HMS core audio editing service supports 7 kinds of audio effects to help one-stop audio processing
- About Variables
- Event object of DOM
- What is Excel Macro? Tutorial on using Excel macros
- Illustration leetcode - 731 My schedule II (difficulty: medium)
- 【英雄哥七月集训】第 19天:二叉树
- cnvd_ 2019_ twenty-two thousand two hundred and thirty-eight
- [scientific literature measurement] keyword mining and visualization
- 基于Ansible实现Apache Doris快速部署运维指南
- [AD learning record] Why are schematic diagrams and PCBs in the same folder, and PCB cannot be generated?
猜你喜欢
Technical dry goods | mindspire self-developed high-order optimizer source code analysis and practical application
Event object of DOM
硅谷课堂笔记(上)
Developers must read: 2022 mobile application operation growth insight white paper
Do you dare to use BigDecimal without mastering these pits?
【科学文献计量】中英文文献标题及摘要分词字数与频数统计与可视化
[Muduo socket] InetAddress encapsulates the socket address type
[Muduo log system 2] timestamp time
Vben admin time selector related configuration and setting of unselectable time
Vivo official website app full model UI adaptation scheme
随机推荐
qt里调用win32函数
Distributed notes (05) - etcd of distributed lock (distributed lock principle, etcd characteristics, distributed lock implementation scheme)
[disadvantages of select and poll, and advantages of epoll]
What are the advantages of building websites with old domain names?
Date get the name of the working day from the object
webSocket学习与使用
Excel printing skills summary of twelve excel printing skills
[scientific literature measurement] statistics and visualization of word number and frequency of Chinese and English literature titles and abstracts
Swagger重点配置项
Event object of DOM
What is integer lifting (instance)
Software testing career development direction (don't be confused, roll up quickly)
HMS core machine learning service creates a new "sound" state of simultaneous interpreting translation, and AI makes international exchanges smoother
Ilrunitme foreach has GC
Technical dry goods | mindspire self-developed high-order optimizer source code analysis and practical application
Technical dry goods | cosinesimilarity based on mindspire
Partial voice feature recording
jmeter压力测试 设置一秒发送一次请求
STL vector的操作
Monomer or microservice? You think it's an architectural tradeoff? It's actually a cognitive load!