How to Resume Input Stream After Stopped by Eof in C++

How to resume input stream after stopped by EOF in C++?

When you use Ctrl-D while the tty is in canonical mode it closed the system level pipe. Whatever you do to std::cin won't restore the stream into a good state. If you insist in using Ctrl-D to signal the end of the sequence (which is an unusual interface and probably best avoided), you'll need to clear the ICANON flag using tcgetattr() and tcsetattr() for the standard input stream (file descriptor 0). You will need to deal with any control characters.

It is probably easier to read up to the first failure, clear() the state and either ignore() the offending character(s) or check that they have a specific value.

How to restart stdin after Ctrl+D?

In linux and on POSIXy systems in general, the standard input descriptor is not closed when you press Ctrl+D in the terminal; it just causes the pseudoterminal layer to become readable, with read() returning 0. This is how POSIXy systems indicate end of input.

It does not mean the file descriptor (or even the stream handle provided on top of it by the C library) gets closed. As Steve Summit mentioned in a comment, you only need to clear the end-of-input status of the stream using clearerr(), to be able to read further data; this tells the C library that you noticed the status change, but want to try further reading anyway.

A similar situation can occur when a process is writing to a file, and another reads it. When the reader gets to the end of the file, a read() returns 0, which the C library understands as end-of-input; it sets an internal flag, so that unless you call clearerr(), feof() will return true for that stream. Now, if the writer writes more data, and the reader does a clearerr(), the reader can read the newly written additional data.

This is perfectly normal, and expected behaviour.

In summary:

  • End of input is indicated by a read() operation returning 0, but the file descriptor status does not change, and can be used as normal.

  • Ctrl+D on a terminal causes only that to happen; the file descriptors open to the terminal are not affected in any other way, and it is up to the foreground process reading the terminal input to decide what it does. It is allowed to simply go on reading more data.

    Most programs do exit when that happens, but that is a convention, not a technical requirement at all.

  • The C library detects read() returning 0, and sets its internal "end of input seen" flag for that stream. That causes feof() to return true, fgets() to return NULL, fgetc() to return EOF, and so on, for that stream.

  • Calling clearerr() on the stream handle clears the flag, so that the next read attempt will actually try to read further data from the descriptor.

    This is described in the very first sentence in the Description section of the man 3 clearerr man page.

does a C++ piped system call always end with EOF?

Unless that function is interrupted, it will run as you expect. BTW you buf is quite small (only 128 bytes). I would suggest 4 or 8 kilobytes (see getconf(1) with PIPE_BUF):

while( !feof( stream ) && !ferror( stream ))
{
char buf[4096];
memset (buf, 0, sizeof(buf)); //probably useless
int bytesRead = fread(buf, 1, sizeof(buf), stream);
if (bytesRead < 0)
break;
output.write(buf,bytesRead);
}

Read also Advanced Linux Programming ... If you want to read and write from the same process, you'll probably need to multiplex e.g. with poll(2) after having created the pipe(2)-s.... etc etc..

The zeroing of buf with memset is probably not really needed; however, if the above program has bugs, it will make it much more reproducible. This is why I like zeroing memory. and here zeroing memory is much faster than reading it.

See also fread(3)

Trying to Understand fgets()

The semantics of fgets() are quite straight forward: read bytes from the FILE* stream pointer until either:

  • the destination array is full (n-1 bytes have been read and stored into it).
  • a newline was read from the stream and stored into the destination array.
  • the end of file has been reached or a read error occurred (EOF was returned by the fgetc() call or equivalent). end-of-file and read-error conditions can be distinguished by calling ferr() and/or feof() after fgets() returns NULL.

A null terminator is always stored into the array after the bytes read from the stream, unless end of file was reached immediately (fgets() returns NULL) or if the buffer size is specified as having a size of 0.

fgets() behaves as if the stream was read one byte at a time with fgetc().

fgets() consumes the bytes read from the stream and stores them into the destination array. It cannot read the same bytes again unless you explicitly seek backwards into the stream with rewind(), fseek() or fsetpos(), if the stream supports seeking at all. Streams attached to regular files usually support seeking, but files open in text mode require specific handling on some systems, notably Microsoft Windows. Note also that a byte pushed back into the stream with ungetc() will be read by fgets() before any bytes from the actual stream.

fgets() stores the newline character into the destination array if the line was short enough to fit in the array before the null terminator.

fgets() breaks long lines into smaller chunks if the line exceeds the available space in the destination array. Handling long lines is tricky and error prone. Note that when fgets() reads a partial line due to lack of space in the destination array, the next call to fgets() will continue reading from the same line at the point where the previous call stopped.

If upon return from fgets(), the destination array does not end with a newline, one of the following occurred:

  • the current line was too long and only as many bytes as fit in the array were read from the stream.
  • the line read was the last in the file and the file does not end with a newline sequence.
  • a null byte was read from the file.

Mishandling these cases may result in potential undefined behavior and/or exploitable flaws.

Resume file upload/download after lost connection (Socket programming)

There are many ways which you can do this, I suggest you to create a separate type of request to the server that accepts the file's name and file position which is the position where in the file where the connection failed.

That's how you will get the file from the server in the client's side:

int filePosition = 0;

InputStream is = clientSocket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();

do {
baos.write(mybytearray);
bytesRead = is.read(mybytearray);

if(bytesRead != -1)
filePosition += bytesRead;
}
while (bytesRead != -1);

Now if the connection got interrupted for some reason you can send a request again to the server with the same file name and the filePosition, and the server will send the file back like this:

OutputStream outToClient = socke.getOutputStream();
// The file name needs to come from the client which will be put in here below
File myfile = new File("D:\\ "+file_name);
byte[] mybytearray = new byte[(int) myfile.length()];
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(myfile));
bis.skip(filePosition) //Advance the stream to the desired location in the file
bis.read(mybytearray, 0, mybytearray.length);
outToClient.write(mybytearray, 0, mybytearray.length);
System.out.println("Arrays on server:"+Arrays.toString(mybytearray));
outToClient.flush();
bis.close();

And in the client you can open the file stream and specify append = true in the constructor like this:

FileOutputStream fos = new FileOutputStream("D:\\ "+file_name, true);

This could be one way to do this, there are a lot more options. And I also suggest verify the files after the transfer using some hash function like MD5 for example, it creates unique stamp for a given input and it always outputs same result for the same input, which means, you can create the stamp from the same file both in the server and in the client and if the file is truly the same, it will generate the same stamp. Since the stamp's size is very small relative to the file it self and it is also fixed, it can be send between the client/server without much overhead.

You can generate an MD5 hash with this code:

MessageDigest md = MessageDigest.getInstance("MD5");
try (InputStream is = Files.newInputStream(Paths.get("file.txt"))) {
DigestInputStream dis = new DigestInputStream(is, md);
/* Read stream to EOF as normal... */
}
byte[] digest = md.digest();

(taken from: Getting a File's MD5 Checksum in Java)



Related Topics



Leave a reply



Submit