天天看點

如何用C++封裝一個簡單的資料流操作類(附源碼),進而用于網絡上的資料傳輸和解析?

曆史溯源

由于曆史原因,我們目前看到的大部分的網絡協定都是基于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協定,但後續的資料幀格式卻是一個二進制格式。如下:

如何用C++封裝一個簡單的資料流操作類(附源碼),進而用于網絡上的資料傳輸和解析?

在這種格式下,為了表示每幀資料長度,就一定會有一個“資料長度”項,比如上面的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);

}
           

輸出結果

如何用C++封裝一個簡單的資料流操作類(附源碼),進而用于網絡上的資料傳輸和解析?

感謝您的閱讀!

繼續閱讀