C++流的streambuf详解及TCP流的实现
2023-09-11 14:17:32 时间
stringstream -- stringbuf -- string
addr.sin_port = ::htons(port); return !::connect(sock, (sockaddr *) addr, sizeof(addr)); int close() { #ifdef WIN32 return ::closesocket(sock); #else return ::close(sock); #endif protected: // Buffered get int underflow() override { auto n = ::recv(sock, buf, BUFSIZE, 0); return n 0 ? (setg(buf, buf, buf + n), *gptr()) : EOF; // Unbuffered put int overflow(int c) override { if (c == EOF) return close(); char b = c; return ::send(sock, b, 1, 0) 0 ? c : EOF; std::streamsize xsputn(const char *s, std::streamsize n) override { auto x = ::send(sock, s, n, 0); return x 0 ? x : 0; // flush int sync() override { #ifdef WIN32 return 0; #else return flush(sock); #endif // WIN32 //streamsize showmanyc() override { return 1; } private: SOCKET sock; char buf[BUFSIZE]; class tstream : public std::iostream { public: tstream() : std::iostream( _buf) {} tstream(SOCKET sock) : _buf(sock), std::iostream( _buf) {} tstream(char *ip, unsigned short port) : tstream() { connect(ip, port); } bool connect(char *ip, unsigned short port) { return _buf.connect(ip, port); private: tcpbuf _buf; };
上面的文件流和字符串流是C++标准库已经提供了的,现在我的目标是实现一个使用TCP协议通信的socket流
tstream -- tcpbuf -- socket(tcp)
首先来分析一下streambuf的内部实现
streambuf内部实现术语说明:
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) xsputn(s, n) ---- 将缓冲区s的n个字符写入到流中并返回写入的字符数 overflow(c) ----- 当put缓冲区不可用时调用,向流中写入一个字符;当c==EOF时,流写入结束缓冲区不可用是指gnext(pnext) == NULL或者gnext(pnext) = glast(plast)
public函数 缓冲区管理 pubsetbuf : setbuf() pubseekoff : seekoff() pubseekpos : seekpos() pubsync : sync() 输入函数(get)NOTE: 下面的缓冲区指的是用于get操作的缓冲区
in_avail : get缓冲区内还有多少个字符可获取 snextc : return sbumpc() == EOF ? EOF : sgetc() sbumpc : 缓冲区不可用时返回uflow();否则返回(++gnext)[-1] sgetc : 缓冲区不可用时返回underflow();否则返回*gnext sgetn : xsgetn() sputbackc : 缓冲区不可用时返回pbackfail(c);否则返回*(--gnext) sungetc : 类似于sputbackc,不过默认调用pbackfail(EOF) 输出函数(put)NOTE: 下面的缓冲区指的是用于put操作的缓冲区
sputc : 缓冲区不可用时,返回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() 除了read这种一次读入多个字符的函数外,一般的函数都是调用snextc()一次读入一个字符 snextc()当缓冲区不可用时会触发uflow(),uflow()会调用underflow()触发一次读取的操作,如果读到了流的末尾,可以返回EOF我们可以在underflow()函数里重新设置gfirst gnext glast,使得snextc()不会不断的调用uflow(),而可以先读取缓冲区里的数据
若缓冲区不为空,此函数需要重新移动gnext到新的位置并返回*gnext 流中没有数据时(或者说读到了流的末尾时)返回EOFaddr.sin_port = ::htons(port); return !::connect(sock, (sockaddr *) addr, sizeof(addr)); int close() { #ifdef WIN32 return ::closesocket(sock); #else return ::close(sock); #endif protected: // Buffered get int underflow() override { auto n = ::recv(sock, buf, BUFSIZE, 0); return n 0 ? (setg(buf, buf, buf + n), *gptr()) : EOF; // Unbuffered put int overflow(int c) override { if (c == EOF) return close(); char b = c; return ::send(sock, b, 1, 0) 0 ? c : EOF; std::streamsize xsputn(const char *s, std::streamsize n) override { auto x = ::send(sock, s, n, 0); return x 0 ? x : 0; // flush int sync() override { #ifdef WIN32 return 0; #else return flush(sock); #endif // WIN32 //streamsize showmanyc() override { return 1; } private: SOCKET sock; char buf[BUFSIZE]; class tstream : public std::iostream { public: tstream() : std::iostream( _buf) {} tstream(SOCKET sock) : _buf(sock), std::iostream( _buf) {} tstream(char *ip, unsigned short port) : tstream() { connect(ip, port); } bool connect(char *ip, unsigned short port) { return _buf.connect(ip, port); private: tcpbuf _buf; };
相关文章
- C++ 面向对象编程
- c++中关于初始化型参列表的一些问题
- C++实现车轮轨迹
- 第五届蓝桥杯C++B组国(决)赛真题
- C++ 内联函数
- C/C++基础讲解(五十四)之图形篇(VGA256色模式编程)
- C语言/C++基础之元旦新年倒计时程序包含天、时、分、秒(附源码)
- C语言/C++常见习题问答集锦(十三)
- Open3D (C++) 使用点云创建数字高程模型DEM
- paip.java c# .net php python调用c++ c dll so windows api 总结
- C++中string类的操作函数。
- 【华为OD机试 2023】 优秀学员统计(C++ Java JavaScript Python)
- C++学习心得与c语言到c++衔接技巧
- 解答私信@被c++折磨头秃的花季美少女 //C++ 写一个带命令行参数的程序,可以实现将参数求和、求平均值以及排序之后输出(参数的数量不确定)。
- C++禁止使用拷贝构造函数和赋值运算符方法
- TCP 三次握手四次挥手, ack 报文的大小.tcp和udp的不同之处、tcp如何保证可靠的、tcp滑动窗口解释
- AI模型C++部署:ubuntu安装Cython并使用C/C++调用python动态库【附加c++与python互相调用算法demo程序接口的源码】
- c++ vector C++ vector存放结构体 并且排序
- C++ STL算法
- TCP报文格式和三次握手——三次握手三个tcp包(header+data),此外,TCP 报文段中的数据部分是可选的,在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。
- DNS同时占用UDP和TCP端口53——传输数据超过512时候用tcp,DNS服务器可以配置仅支持UDP查询包
- webrtc Native C++ 客户端的内存释放问题
- c/c++ 静态代码检查工具
- 【基础知识】7、排序算法 C++ 程序