IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Websocket服务器代码协议解析,学会自己做协议思路。 -> 正文阅读

[系统运维]Websocket服务器代码协议解析,学会自己做协议思路。

首先介绍一下HTTP和Websocket的关系。

HTTP有 1.1 和 1.0 之说,也就是所谓的 keep-alive ,把多个HTTP请求合并为一个,但是 Websocket 其实是一个新协议,跟HTTP协议基本没有关系,只是为了兼容现有浏览器的握手规范而已,也就是说它是HTTP协议上的一种补充可以通过这样一张图理解
在这里插入图片描述

有交集,但是并不是全部,需要注意的是它们两个都是建立在tcp支持的基础上的。
首先介绍一下http和websoket的关系:
首先,Websocket是一个持久化的协议,相对于HTTP这种非持久的协议来说。简单的举个例子吧,用目前应用比较广泛的PHP生命周期来解释。

HTTP的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,那么在 HTTP1.0 中,这次HTTP请求就结束了。

在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response , 在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。

Websocket基于HTTP协议这种说法不太准确,可以认为是Websocket兼容了HTTP,或者说借用了HTTP的协议来完成一部分握手。

请看这张图

在这里插入图片描述不管是IPv4的报文或者TCP,UDP报文,它们都具有一定的格式,上图则是websocket对应的报文格式

以后如果是自己基于tcp做自定义协议,需要掌握这几个关键步骤。

  • 操作码
  • 包长度
  • mask-key (可以理解为为数据加密的方式)
  • 数据部分

在这里,我们通过websocket服务器的框架代码,来为大家讲解一下,在协议中,如何封装报文格式中的各个部分。在文章末尾,我会给出完整代码。

首先我们来梳理一下,websocket客户端与websocket服务器连接的过程:

我们需要知道的一点是,为什么我们说http协议或者websocket协议是基于tcp的,其实这些协议不过就是在tcp的连接上进行一系列的功能封装,通过特定的协议规则标准封装从而实现的特定协议。
当websoket客户端和websocket服务器建立了tcp连接后。首先websocket客户端会发送相应握手数据到达服务器端口,进行握手过程,当然这里说的握手,不是指的tcp连接时的三次握手过程(三次握手是tcp连接过程,这里是连接之后)。

客户端发送过来的数据中包含一段类似
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
的数据。
服务器端定了一个一个名为GUID的值,这个值是全局唯一的,可以把它理解为websocket协议的一个规则。

服务器会将客服端发来的数据和这个全局唯一的GUID 值进行连接。通过一个哈希函数得到对应的哈希值,再通过特定编码方式得到服务器端得到的最终的
Sec-WebSocket-Accept :

使用伪代码来解释这个过程:

str = strcat(linebuf, GUID);   //连接客户端发来的握手数据的一部分与服务器端的GUID
sta = SHA1(str); // 通过特定哈希函数转化为对应哈希值
base64_encode(sta); //通过特定编码方式,得到最终值Sec-WebSocket-Accept :

具体握手代码
建议观看代码方式,由于完整代码定义了较多结构体和封装好的函数,所以单独看局部代码可能不太容易理解,所以之后的局部代码都建议只是阅读了解大概即可,具体过程,建议最后好好看下完整代码。

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;
}

握手完成后,就涉及到服务器与客户端的数据交流过程, 接下来的代码,就会为大家呈现,websocket的报文格式是怎样通过代码进行封装的。
注意这两个函数
decode_packet //拆包
encode_packet //成包

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);
	//这里的decode_packet就是对数据包进行拆包处理,挑选出数据部分,舍弃报文头。

	printf("data : %s , length : %d\n", data, ret);

	ret = encode_packet(si->sendbuffer, mask, data, ret);
	//这里的encode_packet就是对数据部分加头,形成完整的websocket包。
	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;
}

总结:

  • 比如http,websocket协议,这些看似高大上难以理解的协议,它们的本质其实就是基于tcp连接的代码上,完成更深层次的封装,当然在封装的过程中,我们可以使用一些增强网络编程效率的办法,比如这里用了I/O多路复用中的epoll,完成对Reactor的封装,Reactor就是一种高效的事件处理模式。
  • 对报文格式的封装,使用了几个结构体对报文格式的各部分进行封装,从而实现了对于一份数据的加头和去头的功能。
  • 自定义协议离不开的关键点
    1.操作码 2.包长度 3.非必选:mask-key(对数据加密) 4.数据部分

完整服务器端代码:



#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) {
		//这里可能有人会迷糊,mask不是掩码标志位吗?怎么这样来判断。
		//其实是因为mask虽然只占了一个位,但指向这一位,即是指向了那一整个字节,
		//0x7F == 0111 1111 ,比较的时候没有用到mask位

		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) {  //设置为非阻塞
	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);
			}
		}

	}

}





  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 19:20:33  更:2022-07-20 19:22:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/18 20:54:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码