サービス側はc++を用いてwebsocketプロトコルの解析と通信を実現する

6039 ワード

WebSocketが設計した目的は,クライアントブラウザにC/Sアーキテクチャのようなデスクトップシステムのリアルタイム通信能力を備えさせることである.ブラウザはJavaScriptを介してWebSocket接続の確立をサーバに要求し,接続が確立されるとクライアント側とサーバ側はTCP接続により直接データを交換することができる.WebSocket接続は本質的にTCP接続であるため、データ伝送の安定性とデータ伝送量の大きさの面では、ポーリングやComet技術と比較して大きな性能優位性を有する.次の簡単なWebアプリケーションはそれぞれポーリング方式とWebSocket方式で実現され、トラフィックと負荷が増大した場合、WebSocket方式は従来のAjaxポーリング方式に比べて大きな性能優位性を有する.
では、WebSocketについて詳しく説明します.参考資料参照のリンクをクリックして、解析プロトコルや通信に戻ることができます.プロトコルを解析することは、性子に耐え、1バイトずつ解析し、手順に従ってプログラムを少しずつ書かなければなりません.しかし、ドキュメントを読み、バイトごとの属性の意味を知って解析するのは簡単です.プロトコルの説明によれば、データ復号が完了すると、符号化はやや容易になり、復号の逆方向動作が少なくなる.サービス側はc++を使用してWebSocket通信を完了し、主に以下の3つのプログラミングを完了する必要がある.
1.サービス側がh 5クライアントから開始したWebSocket接続握手:
int wsHandshake(std::string &request, std::string &response)
{
	//   http     
	int ret = WS_STATUS_UNCONNECT;

	std::istringstream stream(request.c_str());
	std::string reqType;
	std::getline(stream, reqType);

	if (reqType.substr(0, 4) != "GET ")
	{
		return ret;
	}

	std::string header;
	std::string::size_type pos = 0;
	std::string websocketKey;

	while (std::getline(stream, header) && header != "\r")
	{
		header.erase(header.end() - 1);

		pos = header.find(": ", 0);

		if (pos != std::string::npos)
		{
			std::string key = header.substr(0, pos);
			std::string value = header.substr(pos + 2);

			if (key == "Sec-WebSocket-Key")
			{
				ret = WS_STATUS_CONNECT;
				websocketKey = value;

				break;
			}
		}
	}

	if (ret != WS_STATUS_CONNECT)
	{
		return ret;
	}

	//   http     
	response = "HTTP/1.1 101 Switching Protocols\r
"; response += "Upgrade: websocket\r
"; response += "Connection: upgrade\r
"; response += "Sec-WebSocket-Accept: "; const std::string magicKey("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); std::string serverKey = websocketKey + magicKey; char shaHash[32]; memset(shaHash, 0, sizeof(shaHash)); sha1::calc(serverKey.c_str(), serverKey.size(), (unsigned char *)shaHash); serverKey = base64::base64_encode(std::string(shaHash)) + "\r
\r
"; string strtmp(serverKey.c_str()); response += strtmp; return ret; }

2.握手が終わったら接続が確立します.次に、h 5クライアントからWebSocketを介して送信されたデータフレームを受信し、復号する.
int wsDecodeFrame(std::string inFrame, std::string &outMessage)
{
	int ret = WS_OPENING_FRAME;


	const char *frameData = inFrame.c_str();
	const int frameLength = inFrame.size();


	if (frameLength < 2)
	{
		ret = WS_ERROR_FRAME;
	}


	//         
	if ((frameData[0] & 0x70) != 0x0)
	{
		ret = WS_ERROR_FRAME;
	}


	// fin :  1         ,  0          
	ret = (frameData[0] & 0x80);


	if ((frameData[0] & 0x80) != 0x80)
	{
		ret = WS_ERROR_FRAME;
	}


	// mask ,  1       
	if ((frameData[1] & 0x80) != 0x80)
	{
		ret = WS_ERROR_FRAME;
	}


	//    
	uint16_t payloadLength = 0;
	uint8_t payloadFieldExtraBytes = 0;
	uint8_t opcode = static_cast(frameData[0] & 0x0f);


	if (opcode == WS_TEXT_FRAME)
	{
		//   utf-8      
		payloadLength = static_cast(frameData[1] & 0x7f);


		if (payloadLength == 0x7e)
		{
			uint16_t payloadLength16b = 0;


			payloadFieldExtraBytes = 2;


			memcpy(&payloadLength16b, &frameData[2], payloadFieldExtraBytes);


			payloadLength = ntohs(payloadLength16b);
		}
		else if (payloadLength == 0x7f)
		{
			//     ,    
			ret = WS_ERROR_FRAME;
		}
	}
	else if (opcode == WS_BINARY_FRAME || opcode == WS_PING_FRAME || opcode == WS_PONG_FRAME)
	{
		//    /ping/pong     
	}
	else if (opcode == WS_CLOSING_FRAME)
	{
		ret = WS_CLOSING_FRAME;
	}
	else
	{
		ret = WS_ERROR_FRAME;
	}


	//     
	if ((ret != WS_ERROR_FRAME) && (payloadLength > 0))
	{
		// header: 2  , masking key: 4  
		const char *maskingKey = &frameData[2 + payloadFieldExtraBytes];
		char *payloadData = new char[payloadLength + 1];


		memset(payloadData, 0, payloadLength + 1);
		memcpy(payloadData, &frameData[2 + payloadFieldExtraBytes + 4], payloadLength);


		for (int i = 0; i < payloadLength; i++)
		{
			payloadData[i] = payloadData[i] ^ maskingKey[i % 4];
		}


		outMessage = payloadData;


		delete[] payloadData;
	}


	return ret;
}

3.データフレームを復号した後、サービス側は対応する処理を行った後、結果をWebSocketプロトコルに従って符号化し、h 5クライアントに送る.
int wsEncodeFrame(std::string inMessage, std::string &outFrame, enum WS_FrameType frameType)
{
	int ret = WS_EMPTY_FRAME;
	const uint32_t messageLength = inMessage.size();


	if (messageLength > 32767)
	{
		//           
		return WS_ERROR_FRAME;
	}


	uint8_t payloadFieldExtraBytes = (messageLength <= 0x7d) ? 0 : 2;


	// header: 2  , mask    0(   ),     masking key    ,   4  
	uint8_t frameHeaderSize = 2 + payloadFieldExtraBytes;
	uint8_t *frameHeader = new uint8_t[frameHeaderSize];


	memset(frameHeader, 0, frameHeaderSize);


	// fin  1,     0,     frameType
	frameHeader[0] = static_cast(0x80 | frameType);


	//       
	if (messageLength <= 0x7d)
	{
		frameHeader[1] = static_cast(messageLength);
	}
	else
	{
		frameHeader[1] = 0x7e;


		uint16_t len = htons(messageLength);


		memcpy(&frameHeader[2], &len, payloadFieldExtraBytes);
	}


	//     
	uint32_t frameSize = frameHeaderSize + messageLength;


	char *frame = new char[frameSize + 1];


	memcpy(frame, frameHeader, frameHeaderSize);
	memcpy(frame + frameHeaderSize, inMessage.c_str(), messageLength);
	frame[frameSize] = '\0';
	outFrame = frame;


	delete[] frame;
	delete[] frameHeader;


	return ret;
}

4.握手は1回のみで、その後、第2ステップおよび第3ステップを繰り返し実行し、サービス側とh 5クライアントとの通信を完了する.これはc++バージョンにすぎず、javaバージョンに簡単に変更できます.以下に、上記の方法で使用されるいくつかの列挙を示します.
enum WS_Status
{

	WS_STATUS_CONNECT = 0,

	WS_STATUS_UNCONNECT = 1,

};
enum WS_FrameType
{
	WS_EMPTY_FRAME = 0xF0,
	WS_ERROR_FRAME = 0xF1,


	WS_TEXT_FRAME = 0x01,
	WS_BINARY_FRAME = 0x02,


	WS_PING_FRAME = 0x09,
	WS_PONG_FRAME = 0x0A,


	WS_OPENING_FRAME = 0xF3,


	WS_CLOSING_FRAME = 0x08


};