user1689430
user1689430

Reputation: 77

C++ continuous read file

I've a producer/consumer set-up: Our client is giving us data that our server processes, and our client is giving data to our server by constantly writing to a file. Our server uses inotify to look for any file modifications, and processes the new data.

Problem: The file reader in the server has a buffer of size 4096. I've a unit test that simulates the above situation. The test constantly writes to an open file, which the file reader constantly tries to read an process. But, I noticed that after the first record is read, which is much smaller than 4096, an error flag is set in the ifstream object. This means that any new data arriving is not being processed. A simple workaround seems to be to call ifstream::clear after every read, and this does solve the issue. But, what is going on? Is this the right solution?

Upvotes: 2

Views: 5130

Answers (2)

BreakBadSP
BreakBadSP

Reputation: 860

If you are good with opening and closing file again and again, The right solution to this problem would be to store the last read pos and start from there once file is updated:

Exact algo will be :

  1. set start_pos = 0 , end pos =0
  2. update end_pos = infile.tellg(),
  3. move get pointer to start_pos (use seekg()) and read the block (end_pos - start_pos).
  4. update start_pos = end_pos and then close the file.
  5. sleep for some time and open file again.
  6. if file stream is still not good , close the file and jump to step 5.
  7. if file stream is good, Jump to step 1.

All c++ reference is present at http://www.cplusplus.com/reference/istream/istream/seekg/ you can literally utilize the sample code given here.

Exact code will be:

`
#include <iostream>     
#include <fstream>      

int main(int argc, char *argv[]) {

    if (argc != 2)
    {
        std::cout << "Please pass filename with full path \n";
        return -1;
    }

    int end_pos = 0, start_pos = 0;
    long length;
    char* buffer;
    char *filePath = argv[1];
    std::ifstream is(filePath, std::ifstream::binary);  

    while (1)
    {
        if (is) {

            is.seekg(0, is.end);
            end_pos = is.tellg(); //always update end pointer to end of the file  
            is.seekg(start_pos, is.beg); // move read pointer to the new start position 
            // allocate memory:
            length = end_pos - start_pos;
            buffer = new char[length];

            // read data as a block: (end_pos - start_pos) blocks form read pointer 
            is.read(buffer, length);    
            is.close();    
            // print content:
            std::cout.write(buffer, length);    
            delete[] buffer;
            start_pos = end_pos; // update start pointer    
        }

        //wait and restart with new data 
        sleep(1);
        is.open(filePath, std::ifstream::binary);    
    }    
    return 0;
}

`

Upvotes: 2

Dietmar K&#252;hl
Dietmar K&#252;hl

Reputation: 153820

First off, depending on your system it may or may not be possible to read a file another process writes to: On Windows the normal settings when opening a file make the access exclusive. I don't know enough about Window to tell whether there are other settings. On POSIX system a file with suitable permissions can be opened for reading and writing by different processes. From the sounds of it you are using Linux, i.e., something following the POSIX specification.

The approach to polling a file upon change isn't entirely ideal, though: As you noticed, you get an "error" every time you reach the end of the current file. Actually, reaching the end of a file isn't really an error but trying to decode something beyond end of file is an error. Also, reading beyond the end of file will still set std::ios_base::eofbit and, thus, the stream won't be good(). If you insist on using this approach there isn't much choice than reading up to the end of the file and dealing with the incomplete read somehow.

If you have control over creating the file, however, you can do a simple trick: Instead of having the file be a normal file, you can create it is mkfifo to create a named pipe using the file name the writing program will write to: When opening a file on a POSIX system it doesn't create a new file if there is already one but uses the existing file. Well, file or whatever else is addressed by the file name (in addition to files and named pipe you may see directories, character or block special devices, and possibly others).

Named pipes are curious beasts intended to have two processes communicate with each other: What is written to one end by one process is readable at the other end by another process! The named pipe itself doesn't have any content, i.e., if you need both the content of the file and the communication with another process you might need to replicate the content somewhere. Opening a named pipe for reading which will block whenever it has reached the current end of the file, i.e., initially the read would block until there is a writer. Similarly writes to the named pipe will block until there is a reader. Once there two processes communicating the respective other end will receive an error when reading or writing the named pipe after the other process has exited.

Upvotes: 1

Related Questions