La relación entre websocket y http y la implementación del protocolo websocket

contenido

Prefacio.

La relación entre websocket y http

Las desventajas de http conducen a por qué se necesita websocket

Causa el problema --- el servidor no puede enviar datos al cliente de forma activa, si hay un cierto cambio de estado en el servidor, no puede enviar los datos al cliente de forma activa en tiempo real

Resuelva el problema --- Nació el protocolo de comunicación full-duplex websocket, y el servidor puede enviar datos activamente al cliente

Características de websocket

Análisis de paquetes

El escenario de ejemplo de websocket en nuestra vida (el servidor (backend) actualiza los datos en tiempo real al cliente web (frontend))

Análisis de la implementación del protocolo websocket en bloques, cómo encapsular el protocolo de capa de aplicación websocket en base a reactor (qué protocolos se encapsulan e implementan exactamente)

Análisis de proceso

 Detalles del apretón de manos:

Una vez completada la conexión TCP, el significado de un apretón de manos 

Análisis detallado: ¿Cómo distinguir entre datos de apretón de manos y datos de interacción normal? 

Núcleo de detalles del protocolo de enlace: Sec-WebSocket-Key ---> Sec-WebSocket-Accept

Los detalles de la transferencia de datos de transformación --- empaquetado y desempaquetado de datos.  

Hay tres partes necesarias para hacer un protocolo personalizado: Un protocolo de capa de aplicación personalizado basado en tcp

Herramienta de prueba en línea + Mis resultados de prueba

Resume este artículo


Prefacio.

  • Dado que la implementación de websocket en este artículo se basa en reactor, necesito usar parte del código que escribí antes para la implementación de reactor. Si no está familiarizado con reactor, puede ir a Kangkang

epoll encapsula altamente reactor, el marco subyacente de casi todos los servidores

La relación entre websocket y http

Las desventajas de http conducen a por qué se necesita websocket

  • HTTP es un protocolo de capa de aplicación semidúplex unidireccional sin estado , sin conexión y    no persistente  .
  • Lo que es apátrida, no hay memoria para las conexiones históricas, cada conexión es una nueva conexión
  • Sin conexión y no persistente en realidad significan lo mismo, una solicitud, una respuesta, y no durarán.
  • Half-duplex unidireccional significa que la solicitud de comunicación solo puede ser iniciada por el cliente, el servidor solo puede responder a la solicitud y el servidor no puede enviar datos activamente al cliente

Causa el problema --- el servidor no puede enviar datos al cliente de forma activa , si hay un cierto cambio de estado en el servidor, no puede enviar los datos al cliente de forma activa en tiempo real

  • En respuesta a los problemas anteriores, http todavía se usa al principio, pero el problema de que el servidor necesita enviar datos al cliente de forma activa se resuelve mediante sondeos cronometrados y sondeos largos .

  • Sondeo cronometrado, pregunte constante y regularmente al servidor, ¿necesita enviar un mensaje ?, siga solicitando, envíe una consulta regularmente, si el servidor necesita enviar datos al cliente, cuando llegue la consulta, puede enviar el cambio de estado y el datos al cliente
  • Desventajas del sondeo regular: puede haber un retraso grave, y el sondeo continuo y la ocupación inútil de los recursos del lado del servidor aumentan la presión en el lado del servidor y establecen constantemente conexiones y desperdician recursos           Hay muchas solicitudes no válidas         El establecimiento de conexiones consume gran parte de mi ancho de banda y recursos. Necesito procesar conexiones rápidamente, y muchas veces no tengo datos para actualizar, y hay una gran cantidad de solicitudes no válidas   ( el servidor dijo que soy pasivo )

Aprenda del caso de vida de un hermano, fácil de entender:      [Protocolo WebSocket] La próxima evolución de la comunicación web Quiero ilustrar esto con algunos ejemplos simples y obvios. Como parte del plan HTML5, la especificación WebSocket desarrollada introdujo el WebSocket https://blog.csdn.net/qq_41103843/article/details/124116838?utm_source=app&app_version=5.3.0&code=app_1562916241&uLinkId=usr1mkqgl919blen  El websocket front-end escrito por el jefe mencionado anteriormente implementa una sala de chat web, y la explicación de websocket también es bastante buena.Mis ejemplos para llevar se basan en él.

Solemos pedir comida para llevar (ejemplo de encuesta)

0 segundos: ¿Ha llegado la comida? (Cliente)
0 segundos: El envío está en curso. (Takeaway brother)
1 segundo: ¿Ha llegado la comida? (Cliente)
1 segundo: El envío está en curso. (Takeaway brother)
2 segundos: ¿Ha llegado la comida? (Cliente)
2 segundos: Entregando. (Takeaway brother)                  2 segundos y tantas consultas no son válidas Consulta

3 segundos: ¿Llegó la comida? (Cliente)
3 segundos: Sí, señor, esta es su comida para llevar. (hermano para llevar)

  • La actualización a un sondeo largo solo puede resolver el problema de la demora y puede lograr el propósito de enviar los datos de estado del servidor al cliente en tiempo real,           pero la conexión http siempre está abierta, conexión larga, desperdicio de recursos del sistema , el cliente necesidades Esperando a que el servidor responda, el servidor siempre está ocupado por el cliente        ( El servidor siempre está ocupado, ocupación no válida )     

Ejemplo de larga vida de votación  

0 segundos: ¿Ha llegado la comida? (cliente)

. . . La llamada del medio sigue colgando hasta que se entrega la comida para llevar en  
3 segundos: sí, señor, esta es su comida para llevar. (hermano para llevar)

Resuelva el problema --- Nació el protocolo de comunicación full-duplex websocket, y el servidor puede enviar datos activamente al cliente

  • La etapa se divide en        una etapa de apretón de manos + etapa de intercambio de datos           
  • Núcleo principal: dúplex completo, el servidor puede enviar datos de forma activa al cliente

Características de websocket

  • Basado en el protocolo TCP, la implementación del lado del servidor es relativamente fácil
  • Tiene buena compatibilidad con el protocolo HTTP, los puertos predeterminados también son 80 y 443, y la fase de negociación se basa en el protocolo HTTP.
  • El formato de datos es relativamente liviano, la sobrecarga de rendimiento es pequeña y la comunicación es eficiente
  • Puede enviar texto o datos binarios

Análisis de paquetes

GET /chat HTTP/1.1 #请求行          

Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13                         

#请求头部

  • Nota: La actualización a websocket en el cuadro anterior en realidad informa al servidor que el cliente desea establecer una conexión websocket. ¿Lo admite? Si el servidor lo admite, debe haber dos campos de encabezado en el mensaje de respuesta.
  • respuesta
HTTP/1.1 101 Switching Protocols #响应行,状态行

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
# 响应报头
  • Sec-WebSocket-Accept es en realidad el resultado devuelto después del hash basado en la clave del cliente

  • El escenario de ejemplo de websocket en nuestra vida (servidor ( backend ) actualiza datos en tiempo real al cliente web ( frontend ))

  1. Actualización en tiempo real del aluvión
  2. Salto de página después de escanear el código QR de WeChat
  3. Actualización en tiempo real de los datos de stock

Análisis de la implementación del protocolo websocket en bloques, cómo encapsular el protocolo de capa de aplicación websocket en base a reactor (qué protocolos se encapsulan e implementan exactamente)

  • Análisis de proceso

  •  Detalles del apretón de manos:

  • Una vez completada la conexión TCP, el significado de un apretón de manos 

  • Apretón de manos: asegúrese de que el servidor admita el protocolo websocket, es decir, el servidor responde al cliente, he recibido su solicitud de actualización y admito la actualización de websocket

Análisis detallado: ¿Cómo distinguir entre datos de apretón de manos y datos de interacción normal? 

  • De hecho, el núcleo es distinguir entre diferentes etapas, estados --- máquinas de estado, para distinguir cada estado
  • Máquina de estado --- La capa inferior del protocolo http también existe, y la encapsulación del protocolo es una parte indispensable, porque es necesario distinguir diferentes etapas, ya sea la etapa de protocolo de enlace para establecer una conexión o la etapa de datos interacción...  Debe distinguirse la capa inferior de estos protocolos http.
  • Cada conexión tiene los siguientes tres estados
enum WEBSOCKET_STATUS {
	WS_HANDSHARK,//握手状态
	WS_DATATRANSFORM,//数据交互状态
	WS_DATAEND,//断开状态
};

Núcleo de detalles del protocolo de enlace: Sec-WebSocket-Key ---> Sec-WebSocket-Accept

  • ¿Por qué necesita pasar por capas de clave de cifrado -> Aceptar clave?  
  • Para estar seguros, para demostrar que pueden manejar solicitudes de websocket. Autenticación de claves.
  • El pseudocódigo del proceso de encriptación es el siguiente: Obtener aceptar.
//伪代码如下
str = Sec-WebSocket-Key;
//拿出Sec-WebSocket-Key客户端序列串

str += GUID;

sha = SHA-1(str); //SHA-1一种hash

accept = base64_encode(sha);
  • Los detalles de la transferencia de datos de transformación --- empaquetado y desempaquetado de datos.  

  • El siguiente es el formato de marco de datos único de websocket. La transmisión de datos después del protocolo de enlace debe procesar los datos que se enviarán de acuerdo con el formato del marco de datos.

  • Hay tres partes necesarias para hacer un protocolo personalizado: Un protocolo de capa de aplicación personalizado basado en tcp

  1.  Código de operación, por ejemplo: FIN RSV
  2. longitud del paquete
  3. máscara-clave 
  • Función de encapsulación de datos + función de desempaquetado de datos (no he entendido completamente el proceso específico de encapsulación y desempaquetado. Si existe la posibilidad más adelante, Xiaojie espera volver a analizarlo. El siguiente es el código de los predecesores como referencia)
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) {

		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;
}
  • Tenga en cuenta nuevamente que después de recibir datos, debe llamar a diferentes funciones de acuerdo con diferentes estados para procesar los datos

  •  Código general:
#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>

typedef struct sockaddr SA;

#define BUFFSIZE			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);
	//arg 传入 sockitem* 

	char recvbuffer[BUFFSIZE];
	char sendbuffer[BUFFSIZE];

	int rlen;//recvlen
	int slen;//sendlen

	int status;//存储状态
};

//mainloop / eventloop 
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);


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


//读取一行, allbuff整个缓冲区, level 当前ind linebuff 存储一行
int readline(char* allbuff, int level, char* linebuff ) {
	int n = strlen(allbuff);

	for (; level < n; ++level) {
		//\r\n 回车换行, 表示行末
		if (allbuff[level] == '\r' && allbuff[level + 1] == '\n') {
			return level + 2;
		} else {
			*(linebuff++) = allbuff[level]; //存储行数据
		}
	}
	return -1;
}


//握手, 
int handshark(struct sockitem* si, struct reactor* mainloop) {
	char linebuff[256];//存储一行
	char sec_accept[32];//存储进行处理之后的子序列
	unsigned char sha1_data[SHA_DIGEST_LENGTH + 1] = {0};
	char head[BUFFSIZE] = {0};//存储整个头部信息
  int level = 0;
	//读取Sec-WebSocket-Key并且处理获取accept-key返回密匙
	do {
		memset(linebuff, 0, sizeof(linebuff));//清空
		level = readline(si->recvbuffer, level, linebuff);

		if (strstr(linebuff, "Sec-WebSocket-Key") != NULL) {
			//说明是key 值, 需要进行加密处理
			strcat(linebuff, GUID);//str += GDID
			SHA1((unsigned char*)&linebuff + 19, strlen(linebuff + 19), (unsigned char*)&sha1_data);  
			//SHA1(str);
			base64_encode(sha1_data, strlen(sha1_data), sec_accept);
			//将数据全部放入到head中, 写响应信息
			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);


			//然后进行将其加入到reactor中
			memset(si->recvbuffer, 0, BUFFSIZE);
			memcpy(si->sendbuffer, head, strlen(head));//to send
			si->slen = strlen(head);

			//to set epollout events;
			struct epoll_event ev;
			ev.events = EPOLLOUT | EPOLLET;
			
			//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);
			//握手之后接下来server 需要关注send数据
			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->rlen, &ret);


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

	ret = encode_packet(si->sendbuffer, mask, data, ret);
	si->slen = ret;

	memset(si->recvbuffer, 0, BUFFSIZE);

	struct epoll_event ev;
	ev.events = EPOLLOUT | EPOLLET;
	//ev.data.fd = clientfd;
	si->sockfd = si->sockfd;
	si->callback = send_cb;
	si->status = WS_DATATRANSFORM;//标识IO事件处于数据交互状态.
	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) {

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



//设置非阻塞

static int set_nonblock(int fd) {
	int flags;
	flags = fcntl(fd, F_GETFL, 0);
	if (flags < 0) return -1;
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) return -1;
	return 0;
}


int send_cb(int fd, int events, void* arg) {
	//发送sendbuffer中的数据
	struct sockitem* si = (struct sockitem*)arg;
	send(fd, si->sendbuffer, si->slen, 0);
	//设置关注读事件, 写完这批交互数据,接下来该继续读了

	struct epoll_event ev;
	
	ev.events = EPOLLIN | EPOLLET;
	
	si->sockfd = fd;//从新设置sockfd
	si->callback = recv_cb;
	ev.data.ptr = si;

	memset(si->sendbuffer, 0, BUFFSIZE);
	//发送完数据从新将缓冲区置为0
	epoll_ctl(eventloop->epfd, EPOLL_CTL_MOD, fd, &ev);

}

//关闭连接
int close_connection(struct sockitem* si, unsigned int event) {
	struct epoll_event ev;
	
	close(si->sockfd);//关闭连接
	//将关注IO事件结点从监视红黑树中删除
	ev.events = event;

	epoll_ctl(eventloop->epfd, EPOLL_CTL_DEL, si->sockfd, &ev);
	free(si);
	return 0;
}



//处理读取数据
int recv_cb(int fd, int events, void* arg) {
	struct sockitem* si = (struct sockitem*)arg;
	struct epoll_event ev;

	int ret = recv(fd, si->recvbuffer, BUFFSIZE, 0);
	if (ret < 0) {
		if (errno == EAGAIN || errno == EWOULDBLOCK) {
			return -1;//非阻塞表示缓冲区中没有数据
		} else {

		}

		close_connection(si, EPOLLIN);
	} else if (ret == 0) {
		printf("disconnect %d\n", fd);
		close_connection(si, EPOLLIN);
	} else {
		si->rlen = 0;//重置rlen

		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) {
			close_connection(si, EPOLLOUT | EPOLLET);
		}


	}

}


int accept_cb(int fd, int events, void* arg) {
	//处理新的连接。 连接IO事件处理流程
	struct sockaddr_in cli_addr;
	memset(&cli_addr, 0, sizeof(cli_addr));
	socklen_t cli_len = sizeof(cli_addr);

	int cli_fd = accept(fd, (SA*)&cli_addr, &cli_len);
	if (cli_fd <= 0) return -1;

	char cli_ip[INET_ADDRSTRLEN] = {0};	//存储cli_ip

	printf("recv from ip %s at port %d\n", inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, sizeof(cli_ip)),
		ntohs(cli_addr.sin_port));
	//注册接下来的读事件处理器
	struct epoll_event ev;
	ev.events = EPOLLIN | EPOLLET;
	struct sockitem* si = (struct sockitem*)malloc(sizeof(struct sockitem));
	si->sockfd = cli_fd;
	si->status = WS_HANDSHARK;//等待握手的状态
	si->callback = recv_cb;//设置事件处理器

	ev.data.ptr = si;
	epoll_ctl(eventloop->epfd, EPOLL_CTL_ADD, cli_fd, &ev);

	return cli_fd;

}

int main(int argc, char* argv[]) {
	if (argc != 2) {
		fprintf(stderr, "usage %s <port>", argv[0]);
		return -1;
	}

	int port = atoi(argv[1]);
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	if (sockfd == -1) {
		fprintf(stderr, "socket error");
		return -2;
	}

	set_nonblock(sockfd);

	struct sockaddr_in serv_addr;

	memset(&serv_addr, 0, sizeof(serv_addr));

	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = INADDR_ANY;
	serv_addr.sin_port = htons(port);

	if (bind(sockfd, (SA*)&serv_addr, sizeof(serv_addr)) == -1) {
		fprintf(stderr, "bind error");
		return -3;
	}


	if (listen(sockfd, 5) == -1) {
		fprintf(stderr, "listen error");
		return -4;
	}

	//init eventloop
	eventloop = (struct reactor*)malloc(sizeof(struct reactor));
	//创建监视事件红黑树的根部
	eventloop->epfd = epoll_create(1);

	//注册处理连接IO处理函数
	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 < nready; ++i) {

			if (eventloop->events[i].events & EPOLLIN) {
				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);
			}
		}
	}

	return 0;

}

  • Herramienta de prueba en línea + Mis resultados de prueba

prueba en línea websocket Herramienta de prueba en línea WebSocket Internet de las cosas http://www.websocket-test.com/

 

Resume este artículo

  • Este artículo comienza con las desventajas de http y analiza por qué se necesita websocket, un nuevo protocolo de capa de aplicación.
  • Para resolver el problema de que el servidor necesita empujar activamente los datos al cliente, el servidor back-end empuja activamente los datos a la página web del front-end.       
  •   Aunque también es posible el sondeo http para conexiones largas, el retraso del sondeo es largo y se establecen continuamente conexiones no válidas. Como resultado, el servidor no necesita enviar datos en absoluto, lo que    es una pérdida de recursos. Aunque el sondeo largo resuelve el problema de retraso, continúa Ocupar el servidor también es una pérdida de recursos del servidor.Después de todo, ocupa el servidor pero solo necesita enviar datos una vez para un evento largo
  • Entonces, el nuevo servidor puede enviar datos activamente, y nació el websocket full-duplex basado en tcp
  • Websocket se divide en  dos etapas: protocolo de enlace e interacción de datos.       La fase de protocolo de enlace se basa en actualizaciones de http.
  • Para distinguir la fase de datos durante la recepción, nació la máquina de estado   
  • El núcleo de la fase de negociación es que la clave confirma si el servidor es compatible con websocket.key----> accept
  • Después de str += GUID SHA-1(str) hash luego base64_encode (str);
  • Luego, encapsulamos nuestro propio protocolo de capa de aplicación basado en tcp si es necesario:

Formato de marco de datos único: 1. Código de operación 2. Longitud del paquete 3, clave de máscara

Supongo que te gusta

Origin blog.csdn.net/weixin_53695360/article/details/124109655
Recomendado
Clasificación