如何用C++封裝一個簡單的資料流操作類(附原始碼),從而用於網路上的資料傳輸和解析?

一隻會鏟史的貓發表於2021-08-03

歷史溯源

由於歷史原因,我們目前看到的大部分的網路協議都是基於ASCII碼這種純文字方式,也就是基於字串的命令列方式,比如HTTP、FTP、POP3、SMTP、Telnet等。早期作業系統UNIX(或DOS),使用者操作介面就是控制檯,控制檯的輸入輸出方式就決定了使用者只能通過敲擊鍵盤的方式將協議命令輸入到網路,這也就導致了回車換行"\r\n"會作為一次命令結束的標識。

比如HTTP協議,與主機建立連線後,輸入"GET / HTTP/1.1\r\n"即可獲取網站的主頁。

比如email協議,早期的電子郵件協議只支援ASCII碼這種純文字傳輸,但隨著全世界人民對物質文化生活的不斷嚮往,這種落後的傳輸方式,已經無法滿足世界人民對美好生活的追求,比如影像、視訊、音訊、Office檔案如何在郵件中展現?不同國家(非英語國家)字符集該如何傳輸和展現?

換句話說,就是這種非ASCII的二進位制富文字,該如何傳輸和呈現?

MIME的誕生

此時MIME標準誕生了,MIME的出現更多的是一種向下相容的無奈,而不是革命。通過對二進位制資料或非ASCII碼資料進行base64或quoted-printable編碼,來實現純ASCII碼的傳輸。顯然這種方式會讓你的郵件體變大,傳輸效率下降。尤其附件很多時,通過MIME的boundary來解析郵件的附件也是一筆額外的負擔。

同時MIME的標準也被HTTP協議所採用,我們可以通過content-type指定傳輸的內容是什麼型別,通過MIME的boundary來對Form-Data資料進行擴充套件,讓我們Post資料時也能夠在“表格”資料中插入檔案,從而達到上傳檔案的效果

顯然這種方式不如二進位制簡潔,但卻非常的直觀,所見即所得,一眼就能看明白。但就傳輸效率上不如二進位制方式。

又比如websocket協議雖然建立會話時採用的是HTTP協議,但後續的資料幀格式卻是一個二進位制格式。如下:

在這種格式下,為了表示每幀資料長度,就一定會有一個“資料長度”項,比如上面的payload len,當該值小於126時,直接表示資料區(payload data)長度;為126時用後面的2個位元組表示資料區長度,為127時用後面的8個位元組表示資料區長度。此時就涉及到了網路位元組序和主機位元組序的轉換,如果資料區是一個二進位制內容的話,我們就很難使用string的操作方式將整個資料包文拼接起來(可以用memcpy來拼接)。當然,我們這篇文章不是對websocket協議的講解,而是通過該協議的資料區引出二進位制資料流封裝的必要性。如果是文字協議,各種開發語言對string的封裝已經足夠強大,已經沒有封裝的必要。除非你想重新改造字串操作來提升效率或其它目的,比如我的前一篇文章:
為何寫伺服器程式需要自己管理記憶體,從改造std::string字串操作說起。。。
話不多說,下面是一個簡單的資料流的封裝類CDataStream,非常簡單。

.h標頭檔案


#include <windows.h>

// 資料流
class CDataStream  
{
public:
	CDataStream(BOOL bNetworkOrder = FALSE);
	virtual ~CDataStream();

	// 關聯一塊stream
	void Attach(const BYTE* pStream, int iStreamSize){
		m_pStream = (BYTE*)pStream;
		m_iStreamSize = iStreamSize;
		m_iCurrPos = 0;
	}

	// 解除關聯
	void Detach(){
		m_pStream = NULL;
		m_iStreamSize = 0;
		m_iCurrPos = 0;
	}

	void Reset(){
		m_iCurrPos = 0;
	}

	// 獲取流資料
	const BYTE* GetStreamData(){
		return m_pStream;
	}
	int GetStreamSize(){
		return m_iCurrPos;
	}

	// 在當前位置上移動iDistance距離
	int Offset(int iDistance);

	// 移動到新位置
	int MoveTo(int iNewPos);

	void MoveToBegin(){
		m_iCurrPos = 0;
	}
	void MoveToEnd(){
		m_iCurrPos = m_iStreamSize;
	}

	// 讀寫位元組
	void WriteByte(BYTE byValue);
	BYTE ReadByte();

	// 讀寫WORD
	void WriteWord(WORD wValue);
	WORD ReadWord();

	// 讀寫DWORD
	void WriteDWord(DWORD dwValue);
	DWORD ReadDWord();

	// 讀寫int64
	void WriteInt64(__int64 i64Value);
	__int64 ReadInt64();

	// 讀寫Float
	void WriteFloat(float fValue);
	float ReadFloat();

	// 讀寫double
	void WriteDouble(double dValue);
	double ReadDouble();

	// 讀寫資料流
	void WriteData(unsigned char* pData, int iDataLen);
	BYTE* ReadData(int iDataLen);

	// 讀寫字串
	void WriteString(const char* pszValue);
	const char* ReadString();

	// =============運算子過載=============
	CDataStream& operator<<(BYTE byValue)		{	WriteByte(byValue);	return *this;	}
	CDataStream& operator<<(WORD wValue)		{	WriteWord(wValue);	return *this;	}
	CDataStream& operator<<(DWORD dwValue)		{	WriteDWord(dwValue); return *this;	}
	CDataStream& operator<<(__int64 i64Value)	{	WriteInt64(i64Value); return *this;	}
	CDataStream& operator<<(float fValue)		{	WriteFloat(fValue);	return *this;	}
	CDataStream& operator<<(double dValue)		{	WriteDouble(dValue);	return *this;	}
	CDataStream& operator<<(const char* pszValue)	{	WriteString(pszValue); return *this;	}
	
	CDataStream& operator>>(BYTE& byValue)		{	byValue = ReadByte();	return *this;	}
	CDataStream& operator>>(WORD& wValue)		{	wValue = ReadWord();	return *this;	}
	CDataStream& operator>>(DWORD& dwValue)		{	dwValue = ReadDWord();	return *this;	}
	CDataStream& operator>>(__int64& i64Value)	{	i64Value = ReadInt64();	return *this;	}
	CDataStream& operator>>(float& fValue)		{	fValue = ReadFloat();	return *this;	}	
	CDataStream& operator>>(double& dValue)		{	dValue = ReadDouble();	return *this;	}
	CDataStream& operator>>(const char*& pszValue)	{	pszValue = ReadString();	return *this;	}


public:

	// WORD值反序
	static WORD Swap(WORD wValue){
		WORD wRet = 0;
		((BYTE*)&wRet)[0] = ((BYTE*)&wValue)[1];
		((BYTE*)&wRet)[1] = ((BYTE*)&wValue)[0];
		return wRet;
	}

	// DWORD反序
	static DWORD Swap(DWORD dwValue){
		DWORD dwRet = 0;
		((BYTE*)&dwRet)[0] = ((BYTE*)&dwValue)[3];
		((BYTE*)&dwRet)[1] = ((BYTE*)&dwValue)[2];
		((BYTE*)&dwRet)[2] = ((BYTE*)&dwValue)[1];
		((BYTE*)&dwRet)[3] = ((BYTE*)&dwValue)[0];
		return dwRet;
	}

	// i64(long long)反序
	static __int64 Swap(__int64 i64Value){
		__int64 i64Ret = 0;
		((BYTE*)&i64Ret)[0] = ((BYTE*)&i64Value)[7];
		((BYTE*)&i64Ret)[1] = ((BYTE*)&i64Value)[6];
		((BYTE*)&i64Ret)[2] = ((BYTE*)&i64Value)[5];
		((BYTE*)&i64Ret)[3] = ((BYTE*)&i64Value)[4];
		((BYTE*)&i64Ret)[4] = ((BYTE*)&i64Value)[3];
		((BYTE*)&i64Ret)[5] = ((BYTE*)&i64Value)[2];
		((BYTE*)&i64Ret)[6] = ((BYTE*)&i64Value)[1];
		((BYTE*)&i64Ret)[7] = ((BYTE*)&i64Value)[0];
		return i64Ret;
	}
	
	// 下面的函式也是將64位長整形反序,但比較難理解,不如上面的函式簡單、粗暴和直觀
	// 即使你現在能整明白,下次未必能“見字如面”
	static __int64 Swap64(__int64 i64Value)
	{
		return i64Value >> 56|
			(i64Value & 0x00ff000000000000) >> 40 |
			(i64Value & 0x0000ff0000000000) >> 24 |
			(i64Value & 0x000000ff00000000) >> 8  | 
			(i64Value & 0x00000000ff000000) << 8  | 
			(i64Value & 0x0000000000ff0000) << 24 |
			(i64Value & 0x000000000000ff00) << 40 |
			i64Value << 56;
	}
	

	// 浮點型按照IEEE745標準不存在網路位元組序和機器位元組序,這裡只是給出實現方法
	// float反序
	static float Swap(float fValue){
		float fRet = fValue;
		Swap((BYTE*)&fRet, sizeof(float));
		return fRet;
	}

	// double反序
	static double Swap(double dValue){
		double dRet = dValue;
		Swap((BYTE*)&dRet, sizeof(double));
		return dRet;
	}

	// 記憶體資料反序
	static void Swap(BYTE* pData, int iDataLen);
	
	// 記憶體反序後返回新記憶體
	static BYTE* SwapClone(BYTE* pData, int iDataLen);
	
protected:
	BOOL m_bNetworkOrder;		// 資料流是否為網路位元組序,預設為FALSE
	BYTE *m_pStream;			// stream快取
	int m_iStreamSize;			// 快取大小	
	int m_iCurrPos;				// 當前資料位置
};

.cpp實現檔案

#include "DataStream.h"
#include <assert.h>
#include <stdlib.h>


// 將一塊記憶體反序
void CDataStream::Swap(BYTE* pData, int iDataLen)
{
	if(NULL == pData || iDataLen <= 0)
		return;

    for(int i = 0 ; i < iDataLen / 2; i++)  
	{  
		BYTE temp = pData[i];  
		pData[i] = pData[iDataLen - i - 1];  
		pData[iDataLen - i - 1] = temp;  
	}  
}

// 將一塊記憶體反序後返回新記憶體
BYTE* CDataStream::SwapClone(BYTE* pData, int iDataLen)
{
	if(NULL == pData || iDataLen <= 0)
		return NULL;

	BYTE* pSwap = (BYTE*)malloc(iDataLen);
	int j = 0;
	for(int i = iDataLen-1; i >= 0; i--)
	{
		pSwap[j]  = pData[i];
		j++;
	}
	return pSwap;
}

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
CDataStream::CDataStream(BOOL bNetworkOrder)
{
	m_bNetworkOrder = bNetworkOrder;		
	
	m_pStream = NULL;
	m_iStreamSize = 0;
	m_iCurrPos = 0;
}

CDataStream::~CDataStream()
{
	m_pStream = NULL;
	m_iStreamSize = 0;
	m_iCurrPos = 0;
}


// 在當前位置上移動iDistance距離
int CDataStream::Offset(int iDistance)
{
	int iNewPos = m_iCurrPos+iDistance;
	if(iNewPos < 0)
		m_iCurrPos = 0;
	else if(iNewPos > m_iStreamSize)
		m_iCurrPos = m_iStreamSize;
	else
		m_iCurrPos = iNewPos;

	return m_iCurrPos;
}

// 移動到新位置
int CDataStream::MoveTo(int iNewPos)
{
	if(iNewPos < 0)
		m_iCurrPos = 0;
	else if(iNewPos > m_iStreamSize)
		m_iCurrPos = m_iStreamSize;
	else
		m_iCurrPos = iNewPos;

	return m_iCurrPos;
}

// 讀寫位元組
void CDataStream::WriteByte(BYTE byValue)
{
	assert(m_iCurrPos+1 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+1 > m_iStreamSize)
		return;

	*(m_pStream+m_iCurrPos) = byValue;
	m_iCurrPos++;
}
BYTE CDataStream::ReadByte()
{
	assert(m_iCurrPos+1 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+1 > m_iStreamSize)
		return 0;

	BYTE byValue = *(m_pStream+m_iCurrPos);
	m_iCurrPos++;
	
	return byValue;
}

// 讀寫WORD
void CDataStream::WriteWord(WORD wValue)
{
	assert(m_iCurrPos+2 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+2 > m_iStreamSize)
		return;

	// 如果是網路位元組流則反序
	if(m_bNetworkOrder)
		wValue = Swap(wValue);

	*(WORD*)(m_pStream+m_iCurrPos) = wValue;
	m_iCurrPos += 2;
}
WORD CDataStream::ReadWord()
{
	assert(m_iCurrPos+2 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+2 > m_iStreamSize)
		return 0;

	WORD wValue = *(WORD*)(m_pStream+m_iCurrPos);
	m_iCurrPos += 2;
	
	// 如果是網路位元組流則反序
	if(m_bNetworkOrder)
		wValue = Swap(wValue);

	return wValue;
}

// 讀寫DWORD
void CDataStream::WriteDWord(DWORD dwValue)
{
	assert(m_iCurrPos+4 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+4 > m_iStreamSize)
		return;
	
	// 如果是網路位元組流則反序
	if(m_bNetworkOrder)
		dwValue = Swap(dwValue);

	*(DWORD*)(m_pStream+m_iCurrPos) = dwValue;
	m_iCurrPos += 4;
}
DWORD CDataStream::ReadDWord()
{
	assert(m_iCurrPos+4 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+4 > m_iStreamSize)
		return 0;
	
	DWORD dwValue = *(DWORD*)(m_pStream+m_iCurrPos);
	m_iCurrPos += 4;
	
	// 如果是網路位元組流則反序
	if(m_bNetworkOrder)
		dwValue = Swap(dwValue);

	return dwValue;
}

// 讀寫int64
void CDataStream::WriteInt64(__int64 i64Value)
{
	assert(m_iCurrPos+8 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+8 > m_iStreamSize)
		return;

	// 如果是網路位元組流則反序
	if(m_bNetworkOrder)
		i64Value = Swap(i64Value);

	*(__int64*)(m_pStream+m_iCurrPos) = i64Value;
	m_iCurrPos += 8;
}
__int64 CDataStream::ReadInt64()
{
	assert(m_iCurrPos+8 <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+8 > m_iStreamSize)
		return 0;
	
	__int64 i64Value = *(__int64*)(m_pStream+m_iCurrPos);
	m_iCurrPos += 8;
	
	// 如果是網路位元組流則反序
	if(m_bNetworkOrder)
		i64Value = Swap(i64Value);

	return i64Value;
}

// 讀寫float
void CDataStream::WriteFloat(float fValue)
{
	int iFloatSize = sizeof(float);

	assert(m_iCurrPos+iFloatSize <= m_iStreamSize);
	if(m_iCurrPos+iFloatSize > m_iStreamSize)
		return;

	*(float*)(m_pStream+m_iCurrPos) = fValue;
	m_iCurrPos += iFloatSize;
}
float CDataStream::ReadFloat()
{
	int iFloatSize = sizeof(float);

	assert(m_iCurrPos+iFloatSize <= m_iStreamSize);
	if(m_iCurrPos+iFloatSize > m_iStreamSize)
		return 0;

	float fValue = *(float*)(m_pStream+m_iCurrPos);
	m_iCurrPos += iFloatSize;

	return fValue;
}

// 讀寫double
void CDataStream::WriteDouble(double dValue)
{
	int iDoubleSize = sizeof(double);

	assert(m_iCurrPos+iDoubleSize <= m_iStreamSize);
	if(m_iCurrPos+iDoubleSize > m_iStreamSize)
		return;

	*(double*)(m_pStream+m_iCurrPos) = dValue;
	m_iCurrPos += iDoubleSize;
}
double CDataStream::ReadDouble()
{
	int iDoubleSize = sizeof(double);

	assert(m_iCurrPos+iDoubleSize <= m_iStreamSize);
	if(m_iCurrPos+iDoubleSize > m_iStreamSize)
		return 0;

	double dValue = *(double*)(m_pStream+m_iCurrPos);
	m_iCurrPos += iDoubleSize;

	return dValue;
}

// 讀寫資料流
void CDataStream::WriteData(unsigned char* pData, int iDataLen)
{
	if(NULL == pData || iDataLen <= 0)
		return;

	assert(m_iCurrPos + iDataLen <= m_iStreamSize);	// 越界斷言	
	if(m_iCurrPos + iDataLen > m_iStreamSize)
		return;

	memcpy(m_pStream+m_iCurrPos, pData, iDataLen);
	m_iCurrPos += iDataLen;
}
BYTE* CDataStream::ReadData(int iDataLen)
{
	if(iDataLen <= 0 || m_iCurrPos >= m_iStreamSize)
		return NULL;

	assert(m_iCurrPos + iDataLen <= m_iStreamSize);	// 越界斷言	
	if(m_iCurrPos + iDataLen > m_iStreamSize)
		return NULL;

	BYTE* pData = m_pStream+m_iCurrPos;
	m_iCurrPos += iDataLen;

	return pData;
}

// 讀寫字串
void CDataStream::WriteString(const char* pszValue)
{
	if(NULL == pszValue)
		return ;
	int iStrLen = strlen(pszValue)+1;						// 末尾0

	assert(m_iCurrPos+iStrLen <= m_iStreamSize);			// 越界斷言
	if(m_iCurrPos+iStrLen > m_iStreamSize)
		return;

	memcpy(m_pStream+m_iCurrPos, pszValue, iStrLen);
	m_iCurrPos += iStrLen;
}
const char* CDataStream::ReadString()
{
	if(m_iCurrPos >= m_iStreamSize)
		return NULL;

	int iCurrPos = m_iCurrPos;
	char* psz = (char*)(m_pStream+m_iCurrPos);				// 字串位置

	while(iCurrPos < m_iStreamSize)
	{
		if(!m_pStream[iCurrPos])							// 字串最後一個字元為0
		{
			m_iCurrPos = iCurrPos;
			break;
		}
		iCurrPos++;
	}

	// 判斷是否合法
	if(m_iCurrPos < m_iStreamSize)
	{
		m_iCurrPos++;										// skip 0
		return psz;
	}

	assert(FALSE);											// 越界斷言
	return NULL;
}


測試程式碼

void TestDataStream()
{
	// 1、測試資料流,寫入資料
	BYTE szBuff[1024] = {0};
	CDataStream ds;
	ds.Attach(szBuff, 1024);
	ds.WriteByte(1);
	ds.WriteWord(2);
	ds.WriteDWord(1000);
	ds.WriteInt64(5678);
	ds.WriteData((BYTE*)"ASDF\0", 5);	
	ds.WriteFloat(1234567890.12f);
	ds.WriteDouble(1234567890.123);
	ds.WriteString("Hello word!");

	// 讀取資料流
	ds.Reset();		// 指向流的頭
	BYTE byValue = ds.ReadByte();
	WORD wValue = ds.ReadWord();
	DWORD dwValue = ds.ReadDWord();
	__int64 i64Value = ds.ReadInt64();
	BYTE* pData = ds.ReadData(5);
	float fValue = ds.ReadFloat();
	double dValue = ds.ReadDouble();

	const char* psz = ds.ReadString();
	printf("CDataStream讀寫測試:\r\n");
	printf("BYTE=%d, WORD=%d, DWORD = %d, INT64 = %I64u, FLOAT = %f, DOUBLE = %f, %s\r\n", 
			byValue, wValue, dwValue, i64Value, fValue, dValue, psz);

	printf("pData = %s\r\n", (char*)pData);

	// 2、測試資料流,過載運算子(<<,>>)的測試
	// 測試運算子過載
	CDataStream dds;
	dds.Attach(szBuff, 1024);
	BYTE byRet = 0;
	WORD wRet = 0;
	DWORD dwRet = 0;
	__int64 i64Ret = 0;
	float fRet = 0;
	double dRet = 0;
	char* pszRet;
	dds << (BYTE)1 << (WORD)2 << (DWORD)3 << (__int64)100 << 30.1f << 128.12 << "Hello word!";
	dds.Reset();
	dds >> byRet >> wRet >> dwRet >> i64Ret >> fRet >> dRet >> pszRet;
	printf("CDataStream測試,運算子過載:\r\n");
	printf("by1 = %d, WORD = %d, DWORD = %d, INT64 = %I64u, FLOAT = %f, DOUBLE = %f, %s\r\n", 
					byRet, wRet, dwRet, i64Ret, fRet, dRet, pszRet);

}

輸出結果

感謝您的閱讀!

相關文章