websocketとhttpの関係およびwebsocketプロトコルの実装

コンテンツ

序文。

websocketとhttpの関係

httpの欠点は、WebSocketが必要な理由につながります

問題の原因---サーバーはクライアントにデータをアクティブに送信できません。サーバーに特定の状態変化がある場合、リアルタイムでデータをクライアントにアクティブにプッシュできません。

問題を解決する---WebSocket全二重通信プロトコルが誕生し、サーバーはクライアントにデータをアクティブに送信できます

WebSocketの機能

パケット分析

私たちの生活におけるWebSocketのシナリオ例(サーバー(バックエンド)はデータをリアルタイムでWebクライアント(フロントエンド)に更新します)

ブロックでのWebSocketプロトコルの実装の分析、reactorに基づいてWebSocketアプリケーション層プロトコルをカプセル化する方法(どのプロトコルが正確にカプセル化および実装されているか)

プロセス分析

 ハンドシェイクの詳細:

TCP接続が完了した後、ハンドシェイクの意味 

詳細な分析:ハンドシェイクデータと通常のインタラクションデータを区別する方法は? 

ハンドシェイクの詳細コア:Sec-WebSocket-Key ---> Sec-WebSocket-Accept

変換データプッシュの詳細---データのパックとアンパック。  

カスタムプロトコルを作成するには、3つの必要な部分があります。tcpに基づくカスタムアプリケーション層プロトコル

オンラインテストツール+私のテスト結果

この記事を要約する


序文。

  • この記事のWebSocketの実装はreactorに基づいているため、以前に作成したコードの一部をreactorの実装に使用する必要があります。reactorに慣れていない場合は、Kangkangにアクセスしてください。

epollは、ほとんどすべての可視サーバーの基盤となるフレームワークであるreactorを高度にカプセル化しますか?spm = 1001.2014.3001.5502

websocketとhttpの関係

httpの欠点は、WebSocketが必要な理由につながります

  • HTTPは、ステートレスコネクションレス、   非永続的な 一方向の半二重アプリケーション層プロトコルです。
  • ステートレスとは、履歴接続用のメモリがなく、各接続が新しい接続です
  • コネクションレス型と非永続性は、実際には同じこと、要求、応答を意味し、持続しません。
  • 一方向の半二重とは、通信要求はクライアントによってのみ開始でき、サーバーは要求にのみ応答でき、サーバーはクライアントにデータをアクティブに送信できないことを意味します

問題の原因---サーバーはクライアントにデータをアクティブに送信できません。サーバーに特定の状態変化がある場合、リアルタイムでデータをクライアントにアクティブにプッシュできません。

  • 上記の問題に対応するため、最初はhttpを使用しますが、サーバーがクライアントにデータをアクティブに送信する必要があるという問題は、時限ポーリング長時間ポーリングによって解決されます。

  • 時限ポーリング、常に定期的にサーバーに問い合わせる、メッセージを送信する必要がありますか、リクエストを続ける必要がありますか、クエリを定期的に送信します。サーバーがクライアントにデータを送信する必要がある場合、クエリが来ると、ステータス変更とクライアントへのデータ
  • 定期的なポーリングのデメリット:深刻な遅延が発生する可能性があり、継続的なポーリングとサーバー側のリソースの無駄な占有により、サーバー側へのプレッシャーが高まり、接続とリソースの浪費が絶えず確立され   ます。       無効なリクエストが多数あります        。接続の確立帯域幅とリソースを大量に消費します。接続を迅速に処理する必要があり、更新するデータがないことが多く、無効なリクエストが多数あります  (サーバーは私がパッシブであると言っています

兄弟のライフケースから学び、理解しやすい:      [WebSocketプロトコル]Web通信の次の進化これをいくつかの簡単で明白な例で説明したいと思います。HTML5計画の一部として、開発されたWebSocket仕様はWebSocketを導入しましたhttps://blog.csdn.net/qq_41103843/article/details/124116838?utm_source=app&app_version=5.3.0&code=app_1562916241&uLinkId=usr1mkqgl919blen によって書かれたフロントエンドWebSocket上記の上司がWebチャットルームを実装していて、WebSocketの説明もかなり良いです。私の持ち帰りの例はすべてそれに基づいています。

通常、テイクアウトを注文します(ポーリング例)

0秒:食べ物は到着しましたか?(お客様)
0秒:発送中です。(お持ち帰り兄弟)
1秒:食べ物は届きましたか?(お客様)
1秒:発送中です。(お持ち帰り兄弟)
2秒:食べ物は届きましたか?(お客様)
2秒:お届けします。(お持ち帰り兄弟)                  2秒とたくさんのお問い合わせは無効ですお問い合わせ

3秒:食べ物は届きましたか?(お客様)
3秒:はい、これがお持ち帰りです。(持ち帰り兄弟)

  • ロングポーリングにアップグレードすると、遅延の問題を解決するだけで、サーバーのステータスデータをクライアントにリアルタイムでプッシュするという目的を   達成できます       が、http接続は常に開いており、長い接続、システムリソースの浪費クライアントサーバーが応答するのを待つ必要があります。サーバーは常にクライアントによって占有されています       (サーバーは常に占有されています、無効な占有です)     

長いポーリング寿命の例  

0秒:食べ物は到着しましたか?(クライアント)

テイクアウトが3秒で配信されるまで、真ん中の通話は電話を切り続け  
ます。はい、これがテイクアウトです。(持ち帰り兄弟)

問題を解決する---WebSocket全二重通信プロトコルが誕生し、サーバーはクライアントにデータをアクティブに送信できます

  • ステージはハンドシェイクステージ+データ交換ステージ   に分けられ       ます        
  • メインコア:全二重、サーバーはクライアントにデータをアクティブに送信できます

WebSocketの機能

  • TCPプロトコルに基づいて、サーバー側の実装は比較的簡単です
  • HTTPプロトコルとの互換性が高く、デフォルトのポートも80と443であり、ハンドシェイクフェーズはHTTPプロトコルに基づいています。
  • データ形式は比較的軽量で、パフォーマンスオーバーヘッドは小さく、通信は効率的です。
  • テキストまたはバイナリデータを送信できます

パケット分析

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                         

#请求头部

  • 注:上記のボックスのWebSocketへのアップグレードは、クライアントがWebSocket接続を確立することを望んでいることをサーバーに実際に通知します。サポートしますか?サーバーがサポートする場合、応答メッセージで2つのヘッダーフィールドが返される必要があります。
  • 応答
HTTP/1.1 101 Switching Protocols #响应行,状态行

Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
# 响应报头
  • Sec-WebSocket-Acceptは、実際には、クライアントのキーに基づいてハッシュした後に返される結果です。

  • 私たちの生活の中でのWebSocketのシナリオ例(サーバー(バックエンド)はデータをリアルタイムでWebクライアント(フロントエンド)に更新します

  1. 弾幕のリアルタイムリフレッシュ
  2. WeChatQRコードをスキャンした後のページジャンプ
  3. 株式データのリアルタイム更新

ブロックでのWebSocketプロトコルの実装の分析、reactorに基づいてWebSocketアプリケーション層プロトコルをカプセル化する方法(どのプロトコルが正確にカプセル化および実装されているか)

  • プロセス分析

  •  ハンドシェイクの詳細:

  • TCP接続が完了した後、ハンドシェイクの意味 

  • ハンドシェイク:サーバーがWebSocketプロトコルをサポートしていることを確認します。つまり、サーバーがクライアントに応答し、アップグレードリクエストを受け取り、WebSocketのアップグレードをサポートしていることを確認します。

詳細な分析:ハンドシェイクデータと通常のインタラクションデータを区別する方法は? 

  • 実際、コアは、さまざまなステージ、状態、つまりステートマシンを区別し、各状態を区別することです。
  • ステートマシン---httpプロトコルの最下層も存在し、接続を確立するためのハンドシェイクの段階であるか、データの段階であるかを問わず、さまざまな段階を区別する必要があるため、プロトコルのカプセル化は不可欠な部分です。相互作用...  これらのhttpプロトコルの最下層は区別する必要があります。
  • 各接続には、次の3つの状態があります
enum WEBSOCKET_STATUS {
	WS_HANDSHARK,//握手状态
	WS_DATATRANSFORM,//数据交互状态
	WS_DATAEND,//断开状态
};

ハンドシェイクの詳細コア:Sec-WebSocket-Key ---> Sec-WebSocket-Accept

  • なぜ暗号化キーのレイヤーを通過する必要があるのですか->キーを受け入れる?  
  • 安全を期すために、WebSocketリクエストを処理できることを証明します。キー認証。
  • 暗号化プロセスの擬似コードは次のとおりです。
//伪代码如下
str = Sec-WebSocket-Key;
//拿出Sec-WebSocket-Key客户端序列串

str += GUID;

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

accept = base64_encode(sha);
  • 変換データプッシュの詳細---データのパックとアンパック。  

  • WebSocketのユニークなデータフレームフォーマットは次のとおりです。ハンドシェイク後のデータ送信では、データフレームのフォーマットに従って送信するデータを処理する必要があります。

  • カスタムプロトコルを作成するには、3つの必要な部分があります。tcpに基づくカスタムアプリケーション層プロトコル

  1.  オペコード例:FIN RSV
  2. パッケージの長さ
  3. マスクキー 
  • データカプセル化機能+データ解凍機能(具体的なカプセル化と解凍のプロセスを完全には理解していません。後で機会があれば、Xiaojieはそれを再分析したいと考えています。以下は参照用の前任者のコードです)
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;
}
  • データを受信した後、データを処理するには、さまざまな状態に応じてさまざまな関数を呼び出す必要があることに注意してください。

  •  全体的なコード:
#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;

}

  • オンラインテストツール+私のテスト結果

websocketオンラインテストWebSocketオンラインテストツールモノのインターネットhttp://www.websocket-test.com/

 

この記事を要約する

  • この記事では、httpの欠点から始めて、新しいアプリケーション層プロトコルであるwebsocketが必要な理由を分析します。
  • サーバーが積極的にデータをクライアントにプッシュする必要があるという問題を解決するために、バックエンドサーバーは積極的にデータをフロントエンドのWebページにプッシュします。       
  •   長い接続のhttpポーリングも可能ですが、ポーリングの遅延が長く、無効な接続が継続的に確立されるため、サーバーはデータをプッシュする必要がまったくなく    、リソースの浪費になります。長いポーリングは解決しますが、遅延の問題、それは継続しますサーバーを占有することもサーバーリソースの浪費です結局のところ、あなたはサーバーを占有しますが、長いイベントのために一度だけデータをプッシュする必要があります
  • そのため、新しいサーバーは積極的にデータをプッシュでき、tcpベースの全二重WebSocketが誕生しました。
  • Websocketは、ハンドシェイクとデータインタラクションの2つの段階      に分かれてい ます。ハンドシェイクフェーズは、httpのアップグレードに基づいています。
  • recv中のデータフェーズを区別するために、ステートマシンが誕生しました   
  • ハンドシェイクフェーズのコアは、サーバーがWebSocketをサポートしているかどうかをキーが確認することです。key----> accept
  • str + = GUID SHA-1(str)ハッシュの後、base64_encode(str);
  • 次に、必要に応じて、tcpに基づいて独自のアプリケーション層プロトコルをカプセル化します。

固有のデータフレーム形式:1。オペコード2.パケット長3、マスクキー

おすすめ

転載: blog.csdn.net/weixin_53695360/article/details/124109655