zoukankan      html  css  js  c++  java
  • C++流的streambuf详解及TCP流的实现

    前言

    streambuf是C++流(iostream)与流实体(或者叫原始流,文件、标准输入输出等)交互的桥梁

    # 文件流
    fstream <--> filebuf <--> file
    # 字符串流
    stringstream <--> stringbuf <--> string
    

    文件流和字符串流是C++标准库已经提供了的,现在我的目标是实现一个使用TCP协议通信的socket流,所以首先我要读取STL关于‘流’的源代码,然后就有了这篇文章

    tstream <--> tcpbuf <--> socket(tcp)
    

    streambuf内部实现

    术语说明:

    • get 相当于 从流中读取数据
    • put 相当于 写入数据到流中
    • 字符,C/C++中的char,也可以理解为字节

    streambuf内部持有三个用于get的指针gfirst,gnext,glast和三个用于put的指针pfirst,pnext,plast,这些指针分别可以使用eback(),gptr(),egptr()pbase(),pptr(),epptr()函数获得,在代码中需要使用这些函数获取指针,为了方便描述,我直接使用这些指针变量名

    下面是其他几个受保护的成员函数的作用

    • gbump(n) : gnext+=n
    • setg : setg(gfirst, gnext, glast)
    • pbump(n) : pnext+=n
    • setp : setp(pfirst, pnext, plast)

    小结:

    • get缓冲区通过setg()设置,setg的三个参数分别对应gfirst,gnext,glast
    • put缓冲区通过setp()设置,setp的两个参数分别对应pfirst,plast
    • 如果继承自streambuf的子类不通过setg和setp设置缓冲区,也就是读写缓冲区为空,那么这个流可以说是不带读缓冲和写缓冲的流,这时gfirst = gnext = glast = pfirst = pnext = plast = NULL

    子类需要override(覆写)几个虚函数来封装具体的流的实现

    虚函数(protected)

    这些函数有些需要子类实现,来屏蔽不同的流的具体实现,向上提供统一的接口

    缓冲区管理

    • setbuf ---------- 设置缓冲区
    • seekoff --------- 根据相对位置移动内部指针
    • seekpos --------- 根据绝对位置移动内部指针
    • sync ------------ 同步缓冲区数据(flush),默认什么都不做
    • showmanyc ------- 流中可获取的字符数,默认返回0

    输入函数(get)

    • underflow(c) ---- 当get缓冲区不可用时调用,用于获取流中当前的字符,注意获取和读入的区别,获取并不使gnext指针前移,默认返回EOF
    • uflow() --------- 默认返回underflow(),并使gnext++
    • xsgetn(s, n) ---- 从流中读取n个字符到缓冲区s中并返回读到的字符数:默认从当前缓冲区中读取n个字符,若当前缓冲区不可用,则调用一次uflow()
    • pbackfail ------- 回写失败时调用

    输出函数(put)

    • overflow(c) ----- 当put缓冲区不可用时调用,向流中写入一个字符;当c==EOF时,流写入结束;与输入函数的uflow()相对
    • xsputn(s, n) ---- 将缓冲区s的n个字符写入到流中并返回写入的字符数;与输入函数的xsputn相对

    缓冲区不可用是指gnext(pnext) == NULL或者gnext(pnext) >= glast(plast)

    public函数

    缓冲区管理

    • pubsetbuf : setbuf()
    • pubseekoff : seekoff()
    • pubseekpos : seekpos()
    • pubsync : sync()

    输入函数(get)

    • in_avail : (用于get的)缓冲区内还有多少个字符可获取,缓冲区可用时返回glast-gnext,否则返回showmanyc()
    • snextc : return sbumpc() == EOF ? EOF : sgetc()
    • sbumpc : 缓冲区不可用时返回uflow();否则返回(++gnext)[-1]
    • sgetc : 缓冲区不可用时返回underflow();否则返回*gnext
    • sgetn : xsgetn()
    • sputbackc : 缓冲区不可用时返回pbackfail(c);否则返回*(--gnext)
    • sungetc : 类似于sputbackc,不过默认调用pbackfail(EOF)

    输出函数(put)

    • sputc : (用于put操作的)缓冲区不可用时,返回overflow(c);否则*pnext++ = c,返回pnext
    • sputn : xsputn()

    iostream与streambuf的调用关系

    下面就iostream常用的几个函数说明他们的调用关系

    • read(char *s, int n) -> buf.sgetn(s, n)
    • getline() -> buf.sgetc(), buf.snextc(); 首先调用一次sgetc()来判断当前字符是否为EOF,然后不断地调用snextc()读取下一个字符,直到读到\n
    • peek() -> buf.sgetc()
    • sync() -> buf.pubsync()

    总结

    • 在istream对象中,除了read这种一次读入多个字符的函数外,一般的读取流的函数(operator>>())、get、getline都是调用snextc()一次读入一个字符
    • istream的readsome(buf, size)函数本质还是调用了read,大致相当于read(buf, min(in_avail(), size))
    • snextc函数,当缓冲区不可用时会触发uflow(),uflow()会调用underflow()触发一次读取原始流的操作,如果读到了流的末尾,可以返回EOF;缓冲区可用时直接从缓冲区中读取一个字符return *gnext++
    • underflow函数的作用是:当读取缓冲区不足时,从原始流中读取一段数据并调用setg重新设置gfirst gnext glast三个指针,将读到的数据缓存起来,并返回当下的字符return *gnext;原始流中没有数据时(或者说读到了流的末尾时)返回EOF
    • 只要原始流还可访问(读取或写入),xsgetn与xsputn就需要尽可能的从原始流中读取(写入)n个字符。因为有些流比如tcp socket一次可能接收不完所需要的字符数,这就需要循环接收直到收到n个字符为止。
    • [gfirst, glast)永远是已经从流实体里读到的数据如果他们不为空的话

    TCP流的实现

    tcpbuf不可用的特性

    TCP流属于网络连接,不像读取本地的文件那样可以自由移动文件指针,所以有一些流的特性是不可用的

    • seekoff ----- pubseekoff
    • seekpos ----- pubseekpos
    • showmanyc --- in_avail
    • underflow --- sgetc

    参考代码

    https://github.com/luzhlon/tstream/blob/master/src/tstream.h

    参考资料

    http://www.cplusplus.com/reference/streambuf/streambuf/

  • 相关阅读:
    Adobe PS
    深入学习二叉树(04)平衡二叉树
    深入学习二叉树(03)二叉查找树
    C 知识点
    实战【docker 镜像制作与使用】
    从0到1了解 CI/CD
    单例模式的几种实现方式,使用场景以及优缺点
    设计模式之策略模式浅谈以及简单例子
    WPF几种渐变色
    Linq学习以及简单用法
  • 原文地址:https://www.cnblogs.com/luzhlon/p/7055385.html
Copyright © 2011-2022 走看看