webtail——文件读取

先介绍下背景,之前遇到过服务器上需要长时间tail一个日志,之前经常是通过一个终端连到服务器上,但是对于长时间观察,就不太方便:老是要开着终端,没法用手机等移动设备查看;多人共享查看比较麻烦,都需要登录到服务器上。

webtail实现类似于tail,能够持续读取一个文件,并将文件内容通过websocket,实时推送到web端。webtail文件读取基于linux的inotify,所以没有可移植性,websocket使用基于asio的websocketpp,代码维护在这里

webtail代码维护在https://gitorious.org/webtail/webtail,目前还是一个简单可用的小应用,还有很多可以提升的地方。

本文介绍下文件读取的部分。

文件读取分为两个部分,文件读取和文件监控。

文件读取:
由于unix的文件多次打开独立维护file table,共享v-node table(参照APUE 3.10 file sharing)。因此只维护一个文件描述符和当前文件偏移,每次读取通过fstat获取文档当前大小,和维护的文件偏移进行对比,如果小于文件大小,则读取文件直到末尾,并输出。
这里有几点是模仿tailf的:

首先是不修改文件访问时间。这是通过在open系统调用中,增加O_NOATIME,按照man open的解释:Do not update the file last access time (st_atime in the inode) when the file is read(2). This flag is intended for use by indexing or backup programs, where its use can significantly reduce the amount of disk activity. This flag may not be effective on all file systems. One example is NFS, where the server main‐tains the access time. 启动了这个参数,通过read调用读取文件的时候,不会修改atime了。

其次是首次读取的时候,读取文件最后10行(由于websocket发送没有做缓存,所以从web端没法看见)。这里代码也是参照了tailf:
[cce lang="cpp"]
char *buffer = new char[initLen * BUFSIZ];
char *p = buffer;
char lineBuffer[BUFSIZ];
int readSize;
int lineCount = 0;
bool head = true;
int i = 0, j = 0;
while((readSize = ::read(fd, lineBuffer, BUFSIZ - 1)) > 0) {
for(i = 0, j = 0; i < readSize; ++i) {
// read line, save to buffer
if(lineBuffer[i] == '\n') {
std::memcpy(p, lineBuffer + j, i - j + 1);
std::memset(p + i - j + 1, '\0', 1);
j = i + 1;
if(++lineCount >= initLen) {
lineCount = 0;
head = false;
}
p = buffer + (lineCount * BUFSIZ);
}
}
// read break in the middle of line
if(j < i) {
// finished read all files
if(readSize < BUFSIZ) {
std::memcpy(p, lineBuffer + j, i - j + 1);
std::memset(p + i - j + 1, '\0', 1);
++lineCount;
} else if (j == 0){ // long line drop?
continue;
} else {
// not finished, seek to line begin
curPos = lseek(fd, j - i -1, SEEK_CUR);
}
}
}

std::string initReadResult;
if(head) {
for(i = 0; i < lineCount; ++i) {
initReadResult += (buffer + i * BUFSIZ);
}
} else {
for(i = lineCount; i < initLen; ++i) {
initReadResult += (buffer + i * BUFSIZ);
}

for(i = 0; i < lineCount; ++i) {
initReadResult += (buffer + i * BUFSIZ);
}
}

curPos = lseek(fd, 0, SEEK_CUR);
delete buffer;
[/cce]
首先声明一段用来保存最终n行的缓存,缓存最长行长度是BUFSIZ(这个在linux中的定义是8192字节),然后每次读取BUFSIZ-1个字节(最后一个用来放\0,类似fgets的实现),解析其中的换行符(由于只打算在linux中使用,所以只解析\n)。最后根据状态,从缓存中读取最后n行数据到string中。

如果不是初始读取,之前已经说过逻辑了,将fstat获取到的文件大小和保存的当前偏移做比较,如果有新的内容,则读取,没有就直接返回。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据