如何用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协议,但后续的数据帧格式却是一个二进制格式。如下:
在这种格式下,为了表示每帧数据长度,就一定会有一个“数据长度”项,比如上面的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;
}
感谢阅读!
如何用C++封装一个简单的数据流操作类(附源码),从而用于网络上的数据传输和解析?的更多相关文章
- 2.NetDh框架之简单高效的日志操作类(附源码和示例代码)
前言 NetDh框架适用于C/S.B/S的服务端框架,可用于项目开发和学习.目前包含以下四个模块 1.数据库操作层封装Dapper,支持多种数据库类型.多库实例,简单强大: 此部分具体说明可参考博客: ...
- Android UI开发: 横向ListView(HorizontalListView)及一个简单相册的完整实现 (附源码下载)
http://blog.csdn.net/yanzi1225627/article/details/21294553 原文
- 急急如律令!火速搭建一个C#即时通信系统!(附源码分享——高度可移植!)
(2016年3月更:由于后来了解到GGTalk开源即时通讯系统,因此直接采用了该资源用于项目开发,在此对作者表示由衷的感谢!) —————————————————————————————————— 人 ...
- 一文详解如何用 TensorFlow 实现基于 LSTM 的文本分类(附源码)
雷锋网按:本文作者陆池,原文载于作者个人博客,雷锋网已获授权. 引言 学习一段时间的tensor flow之后,想找个项目试试手,然后想起了之前在看Theano教程中的一个文本分类的实例,这个星期就用 ...
- 教你用纯Java实现一个网页版的Xshell(附源码)
前言 最近由于项目需求,项目中需要实现一个WebSSH连接终端的功能,由于自己第一次做这类型功能,所以首先上了GitHub找了找有没有现成的轮子可以拿来直接用,当时看到了很多这方面的项目,例如:Gat ...
- Android Studio 一个完整的APP实例(附源码和数据库)
前言: 这是我独立做的第一个APP,是一个记账本APP. This is the first APP, I've ever done on my own. It's a accountbook APP ...
- 通过一个简单的数据库操作类了解PHP链式操作的实现
class Model{ public $table; //操作的表; private $opt; //查询的参数; private $pri; //表的主键; private $lastSql; / ...
- struts1实现简单的登录功能(附源码)
环境:MyEclipse 14 ...
- oracle常见为题汇总,以及一个简单数据连接操作工厂
本人软件环境:win8.1 64位操作系统,vs2013,安装好了与oracle数据库对应的客户端 连接oracle数据库.以及操作数据库 1.使用IIS建立网站,浏览网页时候,提示“ ...
随机推荐
- 深入了解Debug和Release的区别
原文地址:https://blog.csdn.net/sky___ice/article/details/8993885 一: Bin 目录用来存放编译的结果,bin是二进制binrary的英文缩写, ...
- JDK并发包二
JDK并发包二 线程复用--线程池 在线程池中,总有那么几个活跃的线程,当程序需要线程时可以从池子中随便拿一个控线程,当程序执行完毕,线程不关闭,而是将这个线程退会到池子,等待使用. JDK提供了一套 ...
- .Net Core with 微服务 - Seq 日志聚合
上一次我们介绍并演示了如果使用 Consul 做为我们微服务的注册中心,来实现服务的注册与发现.那么本次我们讲会演示如何做日志聚合.日志聚合比较常用的有 ELK 等,但是这次我想要介绍的是一款比较小众 ...
- SpringCloud(6)Eureka架构与CAP原则与取舍策略
一:Eureka架构 Register(服务注册):把自己的 IP 和端口注册给 Eureka. Renew(服务续约):发送心跳包,每 30 秒发送一次,告诉 Eureka 自己还活着.如果 90 ...
- 根据所处位置提取单元格内容的函数(left、right、mid)和查找字符串位于单元格内容第几位的函数(find)
1.从左到右提取:left(value,num_chars) 注释:value为操纵单元格,num_chars表示截取的字符的数量 2.从右往左提取:right(value,num_chars) 注释 ...
- 二、RabbitMQ 进阶特性及使用场景 [.NET]
前言 经过上一篇的介绍,相信大家对RabbitMQ 的各种概念有了一定的了解,及如何使用RabbitMQ.Client 去发送和消费消息. 特性及使用场景 1. TTL 过期时间 TTL可以用来指定q ...
- zbxtable的使用
实验环境: zabbix server 172.16.1.121 mysql 172.16.1.121 访问端 172.16.1.122 54.1 zbxtable 1 说明 ZbxTable使用Go ...
- Innodb中有哪些锁?
0.前言 上一篇从MySQL层面上了解锁,那么这篇我们从存储引擎上来了解, 以MySQL默认存储引擎Innodb来说,看看有哪些锁?(MySQL版本为8) 1.Shared and Exclusive ...
- CentOS-yum安装chrome+chromeDriver+xvfb
安装chrome 创建yum源文件 $ vim /etc/yum.repos.d/google-chrome.repo [google-chrome] name=google-chrome baseu ...
- Mybatis学习(7)实现mybatis分页
上一篇文章里已经讲到了mybatis与spring MVC的集成,并且做了一个列表展示,显示出所有article 列表,但没有用到分页,在实际的项目中,分页是肯定需要的.而且是物理分页,不是内存分页. ...