提要
在讲解PhxRPC
的之前,介绍streambuf
是必要的,本篇会带大家走一遍 steambuf
继承重写流程,分别实现两套用缓冲区的流操作读入写出。
一个是文件的读入写出,一个是socket
网络数据的读入写出,他们之间的流操作方式会有差异,这个差异是用来提醒流操作和缓冲区半毛线关系都没有,当你实现了缓冲区。
如果不把它和流绑定到一起,那么很不幸,你就只能跟操作一个对象一样,调用streambuf
函数进行处理了。
这些实现总共用到如下几个streambuf
和流的函数:
setg() // 设置输入缓冲区
setp() // 设置输出缓冲区
pbase() // 输出缓冲区的起始地址
eback() // 输入缓冲区的起始地址
gptr() // 当前输入缓冲区数据指针
pptr() // 当前输出缓冲区数据指针
pbump() // 移动输出缓冲区的指针
gbump() // 移动输入缓冲区的指针
sputc() // 将一个字节写入输出缓冲区
sync() // 调用flush函数时会调用此函数,将输出缓冲区的数据写入目标对象,同时清空缓冲区
overflow() // 当输出缓冲区溢出时,调用此函数将输出缓冲区的数据写入目标对象中
underflow() // 当输入缓冲区空了时,调用此函数将数据源中的数据源读取到缓冲区缓冲区中
flush() // 调用sync函数
good() // 判断是否调用成功
FileStreamBuf.cc
文件
#include
#include
#include
#include
class FileBufferStreamBuf : public std::streambuf {
public:
FileBufferStreamBuf(FILE* fp, size_t buf_size, size_t put_back_size):
fp_(fp),
buf_size_(buf_size),
put_back_size_(put_back_size) {
char* gbuf = new char[buf_size_ + put_back_size_];
char* pbuf = new char[buf_size_];
// 此设计第一次调用时就会触发underflow函数,将数据读入缓冲区中
setg(gbuf, gbuf, gbuf); // 参数(输入缓冲区起始地址,输入缓冲区指针指向,输入缓冲区的结束地址)
setp(pbuf, pbuf + buf_size_); // 参数(输出缓冲区的起始地址,输出缓冲区的结束地址)
}
// 调用flush()函数刷新的时候会调用此函数
int sync() {
int sent = 0;
int total = pptr() - pbase();
while(sent < total) {
int ret = fwrite(pbase() + sent, 1, total - sent, fp_); // 写入文件操作
if (ret > 0) {
sent += ret;
} else {
return -1;
}
}
pbump(-total); // 移动缓冲区指针到起始位置
return 0;
}
// 当缓冲区溢出时,调用此函数将缓冲区的数据写入目标对象中
int overflow(int c) {
if (-1 == sync()) {
return EOF;
} else {
sputc(static_cast<char>(c));
return c;
}
}
// 当缓冲区读取完毕时,调用此函数从数据源中调用数据到缓冲区中
int underflow() {
char* base = eback();
// 处理put back区域
size_t put_back_size = put_back_size_ > gptr() - eback() ? gptr() - eback() : put_back_size_;
memmove(base, gptr() - put_back_size, put_back_size);
base += put_back_size;
int ret = fread(base, 1, buf_size_, fp_);
if (ret > 0) {
setg(eback(), base, base + ret);
return *gptr();
} else {
return EOF;
}
}
~FileBufferStreamBuf() {
delete [] pbase();
delete [] eback();
}
private:
size_t buf_size_;
size_t put_back_size_; // 用于putback操作回退字符
FILE* fp_;
};
这里解决一下看完上面代码后可能会有的疑惑:
什么是put-back
区域?put-back
区域有什么作用?
可能有人用过cin
的cin.putback(ch)
,将输入流中的字符ch
返回到输入流中。
put-back
区域的作用就在这里,它允许你将已经读取的数据再重新放回去,它是缓冲区的开头一部分多出来的连续空间,用于你进行回退操作。
sync
,underflow
和ovewflow
三个函数都必须写吗?
并不是,需要根据你的需求,必要情况下你可能还会要重写其他的虚函数,PhxRPC
只用到了这几个函数,所以这里只说了这几个。
可否将fp
文件句柄的生成过程写在这个类里面呢?
可以,不过不建议,你可以再封装一层类,这样可以各执其职。
TestFileSTreamBuf.cc
#include "FileStreamBuf.cc"
#include
#include
#include
#include
#include
using namespace std;
int main() {
FILE* write_fd = fopen("test_write.txt", "w+");
FILE* read_fd = fopen("test_read.txt", "r+");
FileBufferStreamBuf write_file_buffer(write_fd, 16, 10);
FileBufferStreamBuf read_file_buffer(read_fd, 16, 10);
// 将流与缓冲区绑定
ostream os(&write_file_buffer);
istream is(&read_file_buffer);
os << "const double eps = 1e-8;" << endl;
os << "int n;" << endl;
os << "struct Point {" << endl;
os << " double x, y;" << endl;
os << " Point() {}" << endl;
os << " Point(double x, double y): x(x), y(y) {}" << endl;
os << " Point operator + (const Point &p) const {" << endl;
os << " return Point(x + p.x, y + p.y);" << endl;
os << " }" << endl;
os << " Point operator - (const Point &p) const {" << endl;
os << " return Point(x - p.x, y - p.y);" << endl;
os << " }" << endl;
os << "}" << endl;
// 将数据写入目标对象,同时刷新输出缓冲区,只影响输出缓冲区
if (!os.flush().good()) {
cout << "os Error!" << endl;
}
// 以下代码很挫,只简单说一下实现
// 就是读取一个字符,就回退两个字符,再重新读取三个字符
// 这样做的原因是为了验证put-back区域是否真实有效
string in_x;
char ch1, ch2, ch;
while(is.get(ch)) {
if (ch == 's') {
cout << ch;
ch1 = ch;
is.get(ch2);
cout << ch2;
} else {
cout << ch;
is.putback(ch);
is.putback(ch2);
is.putback(ch1);
is.get(ch);
is.get(ch1);
is.get(ch2);
}
}
fclose(write_fd);
fclose(read_fd);
}
SocketStreamBuf.cc
文件
#include
#include
#include
#include
#include
#include
#include
class SocketBufferStreamBuf : public std::streambuf {
public:
SocketBufferStreamBuf(int socketfd, size_t buf_size):
socketfd_(socketfd),
buf_size_(buf_size) {
char* gbuf = new char[buf_size_];
char* pbuf = new char[buf_size_];
setg(gbuf, gbuf, gbuf);
setp(pbuf, pbuf + buf_size_);
}
// 调用flush()函数刷新的时候会调用此函数
int sync() {
int sent = 0;
int total = pptr() - pbase();
while(sent < total) {
int ret = send(socketfd_, pbase() + sent, total - sent, 0);
if (ret > 0) {
sent += ret;
} else {
return -1;
}
}
pbump(-sent);
return 0;
}
// 当缓冲区溢出时,调用此函数将缓冲区的数据写入目标对象中
int overflow(int c) {
if (-1 == sync()) {
return EOF;
} else {
sputc(static_cast<char>(c));
return c;
}
}
// 当缓冲区读取完毕时,调用此函数从数据源中调用数据到缓冲区中
int underflow() {
int ret = recv(socketfd_, eback(), buf_size_, 0);
if (ret > 0) {
setg(eback(), eback(), eback() + ret);
return *gptr();
} else {
return EOF;
}
}
~SocketBufferStreamBuf() {
delete[] eback();
delete[] pbase();
}
private:
size_t buf_size_;
int socketfd_;
};
跟FileStreamBuf.cc
文件的内容
TestSocketStreamBufClient.cc
文件
#include "SocketStreamBuf.cc"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
int client_socketfd = socket(AF_INET, SOCK_STREAM, 0);
int flags = 1;
if (setsockopt(client_socketfd, SOL_SOCKET, SO_REUSEADDR, (char*)&flags, sizeof(flags)) < 0) {
cout << "client reuseaddr failed!" << endl;
}
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(7777);
client_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(client_socketfd, (struct sockaddr*)&client_addr, sizeof(client_addr));
if (res == -1) {
cout << "client connect failed!" << endl;
return -1;
}
SocketBufferStreamBuf SocketSb(client_socketfd, 16);
iostream ios(&SocketSb);
ios << "start" << endl;
ios << "client: tmqtan hello!" << endl;
ios << "client: what?" << endl;
ios << "client: WTF" << endl;
ios << "end" << endl;
if (!ios.flush().good()) {
cout << "client send error" << endl;
return -1;
}
string temp;
while(getline(ios, temp)) {
cout << temp << endl;
if (strcmp(temp.c_str(), "end") == 0) {
break;
}
}
}
TestSocketStreamBufServer.cc
文件
#include "SocketStreamBuf.cc"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
// 服务器设置
int server_socketfd = socket(AF_INET, SOCK_STREAM, 0);
int flags = 1;
if (setsockopt(server_socketfd, SOL_SOCKET, SO_REUSEADDR, (char*)&flags, sizeof(flags)) < 0) {
cout << "server reuseaddr failed!" << endl;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(7777);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_socketfd, (struct sockaddr*) &server_addr, sizeof(server_addr)) < 0) {
cout << "bind error!" << endl;
return -1;
}
if (listen(server_socketfd, 1024) < 0) {
cout << "listen error!" << endl;
return -2;
}
int connect_fd;
while(1) {
if((connect_fd = accept(server_socketfd, (struct sockaddr*) NULL, NULL)) == -1) {
continue;
}
break;
}
SocketBufferStreamBuf socketSb(connect_fd, 16);
iostream ios(&socketSb);
ios << "server start:" << endl;
ios << "server: tmqtan hello!" << endl;
ios << "server: what?" << endl;
ios << "server: WTF" << endl;
ios << "server: Test Socket" << endl;
ios << "end" << endl;
if (!ios.flush().good()) {
cout << "socket write error!" << endl;
return -1;
}
string temp;
cout << "server recv:" << endl;
while(getline(ios, temp)) {
cout << temp << endl;
if (strcmp(temp.c_str(), "end") == 0) {
break;
}
}
return 0;
}
可以注意到,FileBufferStreamBuf
和SocketBufferStreamBuf
这两个类分别用了两个不同的流进行绑定FileBufferStreamBuf
用的是ostream
和istream
,而SocketBufferStreamBuf
用的是iostream
。可以稍微体验一下两者处理的不同之处。
当不同的流绑定缓冲区时可能相应的操作会有一些变化。
**
如果想了解更多关于
streambuf
的知识
http://www.cplusplus.com/reference/streambuf