thrift之TTransport层的分帧传输类TFramedTransport
帧传输类就是按照一帧的固定大小来传输数据,所有的写操作首先都是在内存中完成的直到调用了flush操作,然后传输节点在flush操作之后将所有数据根据数据的有效载荷写入数据的长度的二进制块发送出去,允许在接收的另一端按照固定的长度来读取。
帧传输类同样还是从缓存基类TBufferBase继承而来,实现的接口当然也基本相同,只是实现的方式不同而已,下面就来看看具体的实现过程和原理。
这个类所采用的默认缓存长度是512(static const int DEFAULT_BUFFER_SIZE = 512;),两个基本构造函数一个采用默认的缓存长度,另一个可以指定一个需要的缓存长度。下面还是重点分析慢读、读帧等操作的实现过程:
(1)慢读实现如下:
uint32_t TFramedTransport::readSlow(uint8_t* buf, uint32_t len) {
uint32_t want = len;//想要读取的长度
uint32_t have = rBound_ - rBase_;//内存缓存中已经有的数据的长度
assert(have < want);//如果以后数据长度满足需要读的长度就不需要采用慢读
// 如果我们有一些数据在缓存,拷贝出来并且返回它。
// 我们没有试图读取更多的数据而是不得不返回它,因为我们不能保证在下面的
// 传输层实际上有更多的数据,因此应该尝试阻塞式读它。
if (have > ) {
memcpy(buf, rBase_, have);//拷贝出缓存中已有的数据
setReadBuffer(rBuf_.get(), );//重新设置缓存基地址
return have;//返回
}
// 读取另一帧。
if (!readFrame()) {
// EOF. No frame available.
return ;
}
// 处理我们已有的数据
uint32_t give = std::min(want, static_cast<uint32_t>(rBound_ - rBase_));//已有数据想要读取长度取短的
memcpy(buf, rBase_, give);//拷贝
rBase_ += give;//调整缓存基地址
want -= give;//计算还有多少想要的数据没有得到
return (len - want);//返回实际读取长度
}
缓存中没有数据的时候就会调用读取帧的函数readFrame,这个函数实现如下:
bool TFramedTransport::readFrame() {
//首先读下一帧数据的长度
int32_t sz;//存放长度的变量
uint32_t size_bytes_read = ;//读取长度数据的字节数
while (size_bytes_read < sizeof(sz)) {//表示长度的数据小于存放长度数据的字节数
uint8_t* szp = reinterpret_cast<uint8_t*>(&sz) + size_bytes_read;//长度变量转换为指针
uint32_t bytes_read = transport_->read(szp, sizeof(sz) - size_bytes_read);//读取
if (bytes_read == ) {//如果返回为0表示没有数据了
if (size_bytes_read == ) {//没有任何数据读到,返回false
return false;
} else {
// 部分的帧头部,抛出异常。
throw TTransportException(TTransportException::END_OF_FILE,
"No more data to read after "
"partial frame header.");
}
}
size_bytes_read += bytes_read;//以读取的长度
}
sz = ntohl(sz);//长整数的网络字节序转换为主机字节序
if (sz < ) {//帧的长度不能是负数涩,抛出异常
throw TTransportException("Frame size has negative value");
}
// 读取有效数据负载,重新设置缓存标记。
if (sz > static_cast<int32_t>(rBufSize_)) {
rBuf_.reset(new uint8_t[sz]);//接收基地址
rBufSize_ = sz;//缓存大小
}
transport_->readAll(rBuf_.get(), sz);//调用readAll读取sz长度的数据
setReadBuffer(rBuf_.get(), sz);//设置读缓存基地址
return true;
}
从上面实现代码看出,在按帧读取的过程中,首先需要读取这一帧的头部信息,而这个头部信息就是这一帧的长度,后面就根据头部信息中给定的长度来读取数据部分,读出来的数据放入缓存中。读取头部信息时注意处理异常的情况,还有就是读出来的数据需要经过网络字节序到主机字节序的转换。下面继续看慢写函数和flush刷新函数的实现过程,慢写函数实现如下(快读和快写基类TBufferBase的实现已经满足要求了,所以不需要再去单独实现了):
void TFramedTransport::writeSlow(const uint8_t* buf, uint32_t len) {
// 直到有足够的双缓冲大小
uint32_t have = wBase_ - wBuf_.get();//缓存空间已经有多少数据
uint32_t new_size = wBufSize_;
if (len + have < have /* overflow */ || len + have > 0x7fffffff) {//如果长度溢出或大于2GB了
throw TTransportException(TTransportException::BAD_ARGS,
"Attempted to write over 2 GB to TFramedTransport.");//抛出异常
}
while (new_size < len + have) {//缓存空间的长度小于已有数据的长度和需要写入数据长度的和
new_size = new_size > ? new_size * : ;如果缓存空间长度是大于0的话就扩容一倍的空间
}
uint8_t* new_buf = new uint8_t[new_size];// 分配新空间
memcpy(new_buf, wBuf_.get(), have);// 拷贝已有的数据到新空间.
wBuf_.reset(new_buf);// 缓存地址重新设置
wBufSize_ = new_size;// 缓存新长度
wBase_ = wBuf_.get() + have;//新的开始写入地址
wBound_ = wBuf_.get() + wBufSize_;//写入界限
memcpy(wBase_, buf, len);//拷贝数据到新缓存地址
wBase_ += len;//更新缓存基地址
}
上面代码就是实现把从上层传输的数据写入缓存中以供下层发送使用,这段代码需要注意的是while循环,这个while循环保证有足够的缓存来存放写入的数据到缓存中,每次增长的长度是上次的一倍;还需要注意的是,分配了新的空间需要把原来还没有真正写入的数据拷贝到新缓存中来,不然就会造成内容丢失;最后就是更新缓存的基地址和长度等描述缓存的信息。继续看flush函数的实现代码:
void TFramedTransport::flush() {
int32_t sz_hbo, sz_nbo;
assert(wBufSize_ > sizeof(sz_nbo));//断言缓存长度应该大于个字节sizeof(int32_t)
sz_hbo = wBase_ - (wBuf_.get() + sizeof(sz_nbo));// 滑动到第一帧数据的开始位置。
sz_nbo = (int32_t)htonl((uint32_t)(sz_hbo));//主机字节序转换为网络字节序
memcpy(wBuf_.get(), (uint8_t*)&sz_nbo, sizeof(sz_nbo));//头部长度拷贝写缓存
if (sz_hbo > ) {//保证缓存有需要写入的数据
//如果底层传输写抛出了异常注意确保我们处于安全的状态
//(例如内部缓冲区清理),重置我们写入前的状态(因为底层没有传输成功)
wBase_ = wBuf_.get() + sizeof(sz_nbo);//得到
// 写入长度和帧
transport_->write(wBuf_.get(), sizeof(sz_nbo)+sz_hbo);
}
// 刷新底层传输.
transport_->flush();
}
刷新函数就是把缓存中的数据真正的发送出去,但是在写入到底层时,底层传输可能不会真正成功(如网络突然断了),这个时候底层会抛出异常,那么我们需要捕获异常,以便重新处理这些数据,只有数据真正写入成功的时候我们才计算我们写如数据的长度。所以还有写结束和读结束函数writeEnd、readEnd,它们都只有简单的一句代码就是计算真正完成读写数据的长度。
整个按帧传输的类的功能介绍完毕了,主要需要注意的就是缓存的操作,保证数据不丢失。将来实现考虑分配内存使用c语言的malloc类函数,而不是使用new操作,这样也能提高不少的效率。
thrift之TTransport层的分帧传输类TFramedTransport的更多相关文章
- thrift之TTransport层的内存缓存传输类TMemoryBuffer
内存缓存是简单的在内存进行读写操作的一种传输,任何时候想在上面写入数据都是放入缓存中,任何时候读操作数据也是来至于缓存.内存缓存的分配使用c语言的malloc类函数,分配的长度是需要长度的两倍,需要考 ...
- thrift之TTransport层的缓存传输类TBufferedTransport和缓冲基类TBufferBase
本节主要介绍缓冲相关的传输类,缓存的作用就是为了提高读写的效率.Thrift在实现缓存传输的时候首先建立一个缓存的基类,然后需要实现缓存功能的类都可以直接从这个基类继承.下面就详细分析这个基类以及一个 ...
- thrift之TTransport层的堵塞的套接字I/O传输类TSocket
本节将介绍第一个实现具体传输功能的类TSocket,这个类是基于TCP socket实现TTransport的接口.下面具体介绍这个类的相关函数功能实现. 1.构造函数 分析一个类的功能首先看它的定义 ...
- thrift之TTransport类体系原理及源码详细解析1-类结构和抽象基类
本章主要介绍Thrift的传输层功能的实现,传输的方式多种多样,可以采用压缩.分帧等,而这些功能的实现都是相互独立,和上一章介绍的协议类实现方式比较雷同,还是先看看这部分的类关系图,如下: 由上面的类 ...
- HTTP2.0的二进制分帧
1.帧的类型: 在二进制分帧的结构中,头部有8个字节(64Bit),其中有一个字节(8Bit)来标志帧的类型: HTTP2.0规定了如下帧类型: DATA: 用于传输HTTP消息体 HEADERS:用 ...
- thrift之默认传输类TTransportDefaults和虚拟传输类TVirtualTransport
默认传输类TTransportDefaults提供了抽象类TTransport的默认实现,实现了非虚拟的方法(*_virt) read(), readAll(), write(),borrow() a ...
- 在使用TCP协议进行消息发送时,对消息分帧
成帧与解析 阅读 <java TCP/IP Socket 编程>第三章笔记 成帧技术(frame)是解决如何在接收端定位消息的首尾位置的问题.在进行数据收发时,必须指定消息接收者如何确定何 ...
- UDS帧传输
说明 在UDS协议中,其中有一点我视作为基础,即帧传输.也即是数据传输这一块,在UDS的帧传输中,分为4种: SF单帧 FF第一帧 CF连续帧 FC流控制帧 首先,我们抛开以上的东西,假设一个销售商( ...
- HTML的窗口分帧
下面通过一个后台管理的部分设计来说明窗口分帧 frameset.html代码 <!-- <frameset>标签(常用来做后台管理界面) 属性:rows(行).cols(列).可以使 ...
随机推荐
- LApacheMP基础环境搭建
一.安装前准备 1.下载所需软件包: apr | http://apache.etoak.com/apr/ apr-util | http://apache.etoak.com/apr/ autoco ...
- nginx rewirte
server { listen 8888; server_name jobPhp; root F:\ck\Porject\quanRelease\quanJob; try_files $uri $ur ...
- sql Server中SET QUOTED_IDENTIFIER的使用
在存储过程中经常会有 Sql代码 SET QUOTED_IDENTIFIER on SET QUOTED_IDENTIFIER off 这样的语句,那么SET QUOTED_IDENTIFIER到 ...
- NPOI 读写Excel
实例功能概述: 1.支持Excel2003以及2007 2.支持Excel读取到DataTable(TableToExcel) 3.支持DataTable导出到Excel(TableToExcel) ...
- Servlet 利用Cookie实现一周内不重复登录
import java.io.IOException;import java.io.PrintWriter; import javax.servlet.ServletException;import ...
- 使用Intellij idea开发
使用IntelliJ IDEA开发SpringMVC网站(一)开发环境 使用IntelliJ IDEA开发SpringMVC网站(二)框架配置 使用IntelliJ IDEA开发SpringMVC网站 ...
- 如何通过maven ,将本地jar 安装到仓库中去。
场景: 现在很多公司,都有 maven 的私服 ,在maven项目中,基本上有两个仓库 ,一个是maven的公共仓库,一个是私服仓库: 有的时候,我们download 别人的代码的时候,pom文件中报 ...
- JVM调优-Java垃圾回收之分代回收
为什么要进行分代回收? JVM使用分代回收测试,是因为:不同的对象,生命周期是不一样的.因此不同生命周期的对象采用不同的收集方式. 可以提高垃圾回收的效率. Java程序运行过程中,会产生大量的对象, ...
- sqlite query用法
本文转自http://blog.csdn.net/double2hao/article/details/50281273,在此感谢作者 query(table, columns, selection, ...
- 对比poj3050
#include <stdio.h> const int MAXN = 10; const int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, ...