rdr是winvnc用来io操作的一个库工程。
IO操作无非两种,写入和读取数据,所以这里每个类都相应地分成了IN和OUT两种操作,先分析IN。
构造函数为protected权限,摆明是给子类提供接口规范,derived class。
两个变量根据名字来分析,ptr指向的应该是当前位置,end则是最后一个字节。
inline int check(int itemSize, int nItems=1)
检查剩余缓存是否够用,代码对三种情况进行了处理,当前缓存不足一个itemSize的时候会运行overrun。overrun在这里是一个纯虚函数,稍后我们可以在子类中它的应用。
readU系列
以readU32为例,这里没有用memcpy进行拷贝,而是将源低地址的8位赋值给目标变量的高8位,既默认大端字节序(网络字节序)。
readS只是将readU的返回值作一个有符号类型的强制转换。
char* InStream::readString()
这里默认当前缓存头32位保存的是将要读取内容的长度,然后动态分配了相应长度(+1保证C STYLE字符串),拷贝后返回头指针,意味着由调用函数者来释放。上限是U32 InStream::maxStringLength = 65535;
inline void skip(int bytes)
用来快进当前指针bytes个字节。
virtual void readBytes(void* data, int length)
读取指定长度length的内容到data。这里用了while循环,虽然之前看到了有overrun函数,所以貌似有不读到就阻塞的嫌疑。
readOpaque系列则
这里就是一个字节一个字节的原始拷贝了。因为一个类型指针在内存中永远指向的是低地址。附一个判断CPU大小端的函数
class FdInStream : public InStream
这个类是IO端口的一个缓存,从IO端口读取数据,并且计算流量。
这个是从IO读取长度len数据到buf。
1 用select函数检查端口是否有数据到达。
2 如果没有的话调用构造函数传入的回调函数blockCallback。
3 然后读取数据,虽然::read(fd, buf, len);用了全局符号,但是头文件有定义#define read(s,b,l) recv(s,(char*)b,l,0)。
4 计算流量,先看下获取时间的函数:尝试用两种办法获取当前时间
static void gettimeofday(struct timeval* tv, void*)
startTiming、stopTiming和kbitsPerSecond分别用于开始计算、停止计算和返回从开始至今的传输率。
但都作了最小和最大的传输率限制,暂时猜不透用意。
现在我们看看基类中没有实现的虚函数
FdInStream::overrun(int itemSize, int nItems)
对于比缓冲还大的itemsize只能吼下“OUT”
overrun是在缓冲区剩余未读长度小于itemsize的时候运行的,我们先得理解下几个变量:
ptr 缓冲区中未读数据的首字节地址
end 缓冲区中未读数据的尾字节地址
start 永远都指向了缓冲区的首字节地址
offset 数据传输总量。
首先memmove(start, ptr, end - ptr);把缓冲区的未读数据拷贝到缓冲区的头部,然后调用FdInStream::readWithTimeoutOrCallback从IO读取数据填充缓冲区剩余部分。
对于大量的数据,我们首先把缓冲区中未读数据读取出来,然后调用FdInStream::readWithTimeoutOrCallback直接读取到目标地址,而不再经过缓存区,这样可以提高效率。
也就是说缓冲区虽然方便了管理数据读写管理,但对效率相应地有些影响。
class MemInStream : public InStream
int overrun(int itemSize, int nItems) { throw EndOfStream(); }
可以看出来这个类用于对内存数据的管理。
ZlibInStream : public InStream
本来想到研究zlib库的时候再说,但zlib的接口类非常简洁。它增加的几个变量
构造函数:
B的初始化。
void ZlibInStream::setUnderlying(InStream* is, int bytesIn_)
A的初始化。
int ZlibInStream::overrun(int itemSize, int nItems)
对照FdInStream::overrun函数,看出它是调用了核心处理函数decompress来解决无数据可读的情况
void ZlibInStream::decompress()
它是逻辑很简单,压缩前初始化B的几个关键变量,然后直接调用压缩函数inflate,压缩完后,我们要相应地修改A和C的成员指针到相应地偏移量。
void ZlibInStream::reset()
把A中需要压缩的数据全部压缩完并输出到C中,然后初始化成员变量。我们可以通过函数setUnderlying来切换A,但是记住在之前调用RESET。
这个时候看OUT系列就轻松很多了。
class FdOutStream : public OutStream
void FdOutStream::flush()
所谓的flush就是把缓冲区的数据写入IO。
FdOutStream::writeBytes(const void* data, int length)
发现对于大量数据写入也是先刷新缓冲区,然后绕过缓冲区直接IO写入。
在这里可以清晰地看出offset的用途
它就是用来记录写入IO的数据总量。
int FdOutStream::length()
{
return offset + ptr - start;
}
当然对于外界得到的只能是通过OutStream接口写入的数据总量,因为它会把缓冲区的当前数据量加上去。
MemOutStream
唯一要注意的就是int overrun(int itemSize, int nItems)
在这里如果原来的缓冲不够写,那我们就重新分配一个足够大的缓冲区,把原来缓冲区已写入的数据拷贝过来,然后释放掉旧的缓冲区。
class NullOutStream : public OutStream
这个家伙根本就没有作任何数据的操作,仅仅是计量,猜测是用来计算数据吞吐量的。
class ZlibOutStream : public OutStream
成员变量:
构造函数同样是初始化了B。但这里调用的deflateInit。
void ZlibOutStream::flush()
保证了A中所有需要压缩的数据全部压缩并输出。
int ZlibOutStream::overrun(int itemSize, int nItems)
这个函数并没直接调用flush来解决A空间不足的问题,因为只要保证A中有空间满足itemSize就好,而不是flush这种函数完全清空。
void ZlibOutStream::setUnderlying(OutStream* os)
重置A,记得在它之前调用ZlibOutStream::flush。