在Linux或Mac上的C中,非阻塞读取/写入stdin / stdout

时间:2019-01-04 01:50:40

标签: nonblocking stdio

我有两个程序通过命名管道(在Mac上)进行通信,但是命名管道的缓冲区大小太小。程序1在读取管道2之前将50K字节写入管道1。命名管道为8K(在我的系统上),因此程序1阻塞直到数据被消耗。程序2从管道1读取20K字节,然后将20K字节写入pipe2。 Pipe2无法容纳20K,因此程序2现在会阻塞。它仅在程序1读取时释放。但是程序1被阻塞,等待程序2。死锁

我认为我可以通过创建一个垫片程序来解决此问题,该垫片程序读取stdin非阻塞并写入stdout非阻塞,将数据临时存储在大缓冲区中。我使用cat数据测试了该程序| ./gasket 0 | ./gasket 1> out,期望out是数据的副本。但是,虽然第一次调用垫片会按预期方式工作,但第二个程序中的读取操作在所有数据被使用之前会返回0,并且在调用后不会返回除0以外的任何值。

我在MAC和Linux上都尝试了以下代码。两者的行为相同。我已经添加了日志记录,以便可以看到第二次对垫片的调用得到的数据开始没有任何数据,即使它没有读取第一次调用所写的所有数据。

#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>

#define BUFFER_SIZE 100000
char buffer[BUFFER_SIZE];
int elements=0;
int main(int argc, char **argv)
{
  int total_read=0, total_write=0;
  FILE *logfile=fopen(argv[1],"w");

  int flags = fcntl(fileno(stdin), F_GETFL, 0);
  fcntl(fileno(stdin), F_SETFL, flags | O_NONBLOCK);
  flags = fcntl(fileno(stdout), F_GETFL, 0);
  fcntl(fileno(stdout), F_SETFL, flags | O_NONBLOCK);

  while (1) {
    int num_read=0;
    if (elements < (BUFFER_SIZE-1024)) { // space in buffer
      num_read = fread(&buffer[elements], sizeof(char), 1024, stdin);
      elements += num_read;
      total_read += num_read;
      fprintf(logfile,"read %d (%d) elements \n",num_read, total_read); fflush(logfile);
    }
    if (elements > 0) { // something in buffer that we can write
      int num_written = fwrite(&buffer[0],sizeof(char),elements, stdout); fflush(stdout);
      total_write += num_written;
      fprintf(logfile,"wrote %d (%d) elements \n",num_written, total_write); fflush(logfile);
      if (num_written > 0) { // copy data to top of buffer
        for (int i=0; i<(elements-num_written); i++) {
          buffer[i] = buffer[i+num_written];
        }
        elements -= num_written;
      }
    }
  }
}

我想我可以使垫片成为多线程,并在一个线程中使用阻塞读取,而在另一个线程中使用阻塞写入,但是我想了解为什么非阻塞IO似乎对我来说会中断。

谢谢!

1 个答案:

答案 0 :(得分:0)

我对任何IPC项目的通用解决方案是使客户端和服务器成为非阻塞I / O。为此,需要在写入和读取时都对数据进行排队,以处理OS无法读取或写入或只能读取/写入部分消息的情况。

下面的代码可能看起来像是EXTREME的过大杀伤力,但是如果您能正常工作,则可以在职业生涯的其余时间使用它,无论是命名管道,套接字,网络,都可以使用它。

使用伪代码:

typedef struct {
  const char* pcData, * pcToFree; // pcData may no longer point to malloc'd region
  int   iToSend;
} DataToSend_T;

queue of DataToSend_T qdts;

// Caller will use malloc() to allocate storage, and create the message in
// that buffer.  MyWrite() will free it now, or WritableCB() will free it
// later.  Either way, the app must NOT free it, and must not even refer to
// it again.

MyWrite( const char* pcData, int iToSend ) {
  iSent = 0;

  // Normally the OS will tell select() if the socket is writable, but if were hugely
  // compute-bound, then it won't have a chance to.  So let's call WritableCB() to
  // send anything in our queue that is now sendable.  We have to send the data in
  // order, of course, so can't send the new data until the entire queue is done.
  WritableCB();

  if ( qdts has no entries ) {
     iSent = write( pcData, iToSend );
      // TODO: check error
      // Did we send it all?  We're done.
      if ( iSent == iToSend ) {
          free( pcData );
          return;
      }
  }

  // OK, either 1) we had stuff queued already meaning we can't send, or 2)
  // we tried to send but couldn't send it all.
  add to queue qdts the DataToSend ( pcData + iSent, pcData, iToSend - iSent );
}



WritableCB() {
  while ( qdts has entries ) {
      DataToSend_T* pdts = qdts head;
      int iSent = write( pdts->cData, pdts->iToSend );
      // TODO: check error
      if ( iSent == pdts->iToSend ) {
          free( pdts->pcToFree );
          pop the front node off qdts
      else {
          pdts->pcData  += iSent;
          pdts->iToSend -= iSent;
          return;   
      }
  }
}



// Off-subject but I like a TINY buffer as an original value, that will always
// exercise the "buffer growth" code for almost all usage, so we're sure it works.
// If the initial buffer size is like 1M, and almost never grows, then the grow code
// may be buggy and we won't know until there's a crash years later.

int iBufSize = 1, iEnd = 0;  iEnd is the first byte NOT in a message
char* pcBuf = malloc( iBufSize );

ReadableCB() {
  // Keep reading the socket until there's no more data.  Grow buffer if necessary.
  while (1) {
      int iRead = read( pcBuf + iEnd, iBufSize - iEnd);
      // TODO: check error
      iEnd += iRead;

      // If we read less than we had space for, then read returned because this is
      // all the available data, not because the buffer was too small.
      if ( iRead < iBufSize - iEnd )
          break;

      // Otherwise, double the buffer and try reading some more.
      iBufSize *= 2;
      pcBuf = realloc( pcBuf, iBufSize );
  }

  iStart = 0;
  while (1) {
      if ( pcBuf[ iStart ] until iEnd-1 is less than a message ) {
          // If our partial message isn't at the front of the buffer move it there.
          if ( iStart ) {
              memmove( pcBuf, pcBuf + iStart, iEnd - iStart );
              iEnd -= iStart;
          }
          return;
      }
      // process a message, and advance iStart by the size of that message.
  }
}



main() {
  // Do your initial processing, and call MyWrite() to send and/or queue data.

  while (1) {
       select() // see man page
       if ( the file handle is readable )
           ReadableCB();
       if ( the file handle is writable )
           WritableCB();
       if ( the file handle is in error )
           // handle it;
       if ( application is finished )
           exit( EXIT_SUCCESS );
  }
}