tcp支持浏览器websocket协议

一、websocket是什么?

websocket是一个基于应用层的网络协议,建立在tcp协议之上,和http协议可以说是兄弟的关系,但是这个兄弟有点依赖http,为什么这么说?我们都知道http实现了三次握手来建立通信连接,实际上websocket的创始人很聪明,他不想重复的去造轮子,反正我兄弟已经实现了握手,我干嘛还要重新写一套呢?先让它去冲锋陷阵呢,我坐收渔翁之利不是更香吗,所以一般来说,我们会先用HTTP先进性三次握手,再向服务器请求升级为websocket协议,这就好比说,嘿兄弟你先去给我排个队占个坑建个小房子,到时候我在把这房子改造成摩天大楼。而且一般来说80和443端口一般web服务端都会外放出去,这样可以有效的避免防火墙的限制。当然,你创建的websocket服务端进程的端口也需要外放出去。

很多人会想问,web开发 使用 HTTP 协议不是已经差不多够用了吗?为什么还要我再多学一种呢?这不是搞事情嘛,仔细想想,一门新技术的产生必然有原因的,如果没有需求,我们干嘛那么蛋疼去写那么多东西,就是因为 HTTP 这个协议有些业务需求支持太过于鸡肋了,从 HTTP 0.9 到现在的 HTTP3.0 ,HTTP协议可以说说是在普通的web开发领域已经是十分完善且高效的了,说这个协议养活了全球半数的公司也不为过吧,像 2.0 服务器推送技术,3.0 采用了 UDP 而放弃了原来的 TCP ,这些改动都是为了进一步提升协议的性能,然而大家现在还是基本使用的 HTTP 1.1 这个最为经典的协议, 也是让开发者挺尴尬的。

绝大多数的web开发都是应用层开发者,大多数都是基于已有的应用层去开发应用,可以说我们最熟悉、日常打交道最多的就是应用层协议了,底下 TCP/IP 协议我们基本很少会去处理,当然大厂可能就不一样了,自己弄一套协议也是正常的,这大概也是程序员和码农的区别吧,搬砖还是创新,差别还是很大的。网络这种分层协议的好处我在之前的文章也说过了,这种隔离性很方便就可以让我们基于原来的基础去拓展,具有较好的兼容性。

总的来说,它就是一种依赖HTTP协议的,支持全双工通信的一种应用层网络协议。

二、websocket 能解决什么问题?

工程师应该是以解决问题为主的,如果不会解决问题,只会伸手,必然不会长远,有思考,才会有突破,才能高效的处理事情,所以 websocket 到底解决了什么问题呢?它存在的价值是什么?

这还是得从HTTP说起,大家应该都很熟悉这门协议,我们简单说一下它的特点:
  三次握手、四次挥手 的方式建立连接和关闭连接
  支持长连接和短连接两种连接方式
  有同源策略的限制(端口,协议,域名)
  单次 请求-响应 机制,只支持单向通信

其中最鸡肋的就是最后一个特点,单向通信,什么意思呐?就是说只能由一方发起请求(客户端),另一方响应请求(服务端),而且每一次的请求都是一个单独的事件,请求之间还无法具有关联性,也就是说我上个请求和下个请求完全是隔离的,无法具有连续性
  
也许你觉得这样的说法比较难懂,我们来举一个栗子:
  每个人都打过电话吧,电话打通后可以一直聊天是不是觉得很舒服啊,这是一种全双工的通信方式,双方都可以主动传递信息。彼此的聊天也具有连续性。我们简单把这种方式理解为 websocket 协议支持的方式。

如果打电话变成了 HTTP 那种方式呢? 那就不叫打电话了,而是联通爸爸的智能语音助手了,我们知道客户端和服务端本身的身份并不是固定的,只要你可以发起通信,就可以充当客户端,能响应请求,就可以当做服务端,但是在HTTP的世界里一般来说,客户端(大多数情况下是浏览器)和服务器一般是固定的,我们打电话 去查话费,会询问要人工服务还是智能助手,如果选了助手,你只要问她问题,她就会找对应的答案来回答你(响应你),一般都是简单的业务,你不问她也不会跟你闲聊,

但是实际上有很多的业务是需要双方都有主动性的,半双工的模式肯定是不够用的,例如聊天室,跟机器人聊天没意思啊,又例如主动推送,我无聊的时候手都不想点屏幕,你能不能主动一点给我推一些好玩的信息过来。

只要做过前后端分离的同学应该都被跨域的问题折磨过。浏览器的这种同源策略,会导致 不同端口/不同域名/不同协议 的请求会有限制,当然这问题前后端都能处理,然而 websocket 就没有这种要求,他支持任何域名或者端口的访问(协议固定了只能是 ws/wss) ,所以它让人用的更加舒服
所以,上面 HTTP 存在的这些问题,websocket 都能解决!!!

三、websocket 的 工作原理

主动是 websocket 的一大特点,像之前如果客户端想知道服务端对某个事件的处理进度,就只能通过轮训( Poll )的方式去询问,十分的耗费资源,会存在十分多的无效请求,下面我简单说推送技术的三种模型区别:
  pull (主动获取) 即客户端主动发起请求,获取消息
  poll (周期性主动获取) 即周期性的主动发起请求,获取消息
  push (主动推送) 服务端主动推送消息给客户端

pull 和 poll 的唯一区别只在于周期性,但是很明显周期性的去询问,对业务来说清晰度很高,这也是为什么很多小公司都是基于轮训的方式去处理业务,因为简单嘛,能力不够机器来撑。这也是很多公司都会面临的问题,如果业务达到了瓶颈,使劲的堆机器,如果用新技术或者更高级的作法,开发成本和维护成本也会变高,还不如简单一点去增加机器配置。

如果两个人需要通话,首先需要建立一个连接,而且必须是一个长链接,大家都不希望讲几句话就得重新打吧,根据上面说的,websocket 会复用之前 HTTP 建立好的长链接,然后再进行升级,所以他和轮训的区别大致如下所示
在这里插入图片描述
图片省去了建立连接的过程,我们可以发现,基于轮训的方式,必须由客户端手动去请求,才会有响应,而基于 websocket 协议,不再需要你主动约妹子了,妹子也可以主动去约你,这才是公平的世界。

#define BUFFER_LENGTH	1024
#define GUID 		"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"~
enum  WEBSOCKET_STATUS {
	WS_HANDSHARK,
	WS_DATATRANSFORM,
	WS_DATAEND,
};

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

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

 / eventloop --> epoll -->  
struct reactor {
	int epfd;
	struct epoll_event events[512];
};
struct reactor *eventloop = NULL;

static int set_nonblock(int fd){
	int falgs;
	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;
}




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) {


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


	memcpy(buffer+2, stream, length);


	return length + 2;
}~

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

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

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);
	struct epoll_event ev;
	ev.events = EPOLLIN | EPOLLET;
	
	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));
	eventloop->epfd = epoll_create(1);
	
	struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
	si->sockfd = sockfd;
	si->callback = accept_cb;
	struct epoll_event ev;
	ev.events = EPOLLIN;
	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);
			}
		}

	}

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值