Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

non-buffering stdin reading

Tags:

c

linux

unix

posix

My test application is

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

int main(int argc, char *argv[], char *envp[]) {
  int fd[2];

  if(pipe(fd) < 0) { 
    printf("Can\'t create pipe\n");
    exit(-1); 
  }

  pid_t fpid = fork();
  if (fpid == 0) {
    close(0);
    close(fd[1]);
    char *s = (char *) malloc(sizeof(char));
    while(1) if (read(fd[0], s, 1)) printf("%i\n", *s);
  }
  close(fd[0]);
  char *c = (char *) malloc(sizeof(char));
  while (1) {
    if (read(0, c, 1) > 0) write(fd[1], c, 1);
  }
  return 0;
}

I want to see char-code after each entered char. But in fact *s is printed only after '\n' in the console. So seems like stdin (file with desc 0) is buffered. But the read function is buffer-less, isn't it? Where am I wrong.

UPD: I use linux.

So the solution is

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>

int main(int argc, char *argv[], char *envp[]) {
  int fd[2];

  if(pipe(fd) < 0) { 
    printf("Can\'t create pipe\n");
    exit(-1); 
  }

  struct termios term, term_orig;

  if(tcgetattr(0, &term_orig)) {
    printf("tcgetattr failed\n");
    exit(-1); 
  }

  term = term_orig;

  term.c_lflag &= ~ICANON;
  term.c_lflag |= ECHO;
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &term)) {
    printf("tcsetattr failed\n");
    exit(-1);
  }

  pid_t fpid = fork();
  if (fpid == 0) {
    close(0);
    close(fd[1]);
    char *s = (char *) malloc(sizeof(char));
    while(1) if (read(fd[0], s, 1)) printf("%i\n", *s);
  }
  close(fd[0]);
  char *c = (char *) malloc(sizeof(char));
  while (1) {
    if (read(0, c, 1) > 0) write(fd[1], c, 1);
  }
  return 0;
} 
like image 360
Ximik Avatar asked Dec 01 '10 18:12

Ximik


3 Answers

Unfortunately, the behavior you're looking for is not possible with standard ANSI C, and the default mode for UNIX terminal I/O is line-oriented, which means you will always need an inputted \n character to retrieve the input. You'll need to use terminal I/O facilities that let you program in non-canonical mode, so that each key-press triggers an event. On Linux/UNIX, you can look into the <termios.h> header, or the ncurses library.

like image 125
Charles Salvia Avatar answered Nov 10 '22 22:11

Charles Salvia


It seems to me that your solution is a little bit complicated. Still don't understand why do you need pipe and 2 process.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>

int main(int argc, char *argv[], char *envp[]) {
  struct termios term, term_orig;

  if(tcgetattr(0, &term_orig)) {
    printf("tcgetattr failed\n");
    exit(-1);
  }

  term = term_orig;

  term.c_lflag &= ~ICANON;
  term.c_lflag |= ECHO;
  term.c_cc[VMIN] = 0;
  term.c_cc[VTIME] = 0;

  if (tcsetattr(0, TCSANOW, &term)) {
    printf("tcsetattr failed\n");
    exit(-1);
  }

  char ch;
  while (1) {
    if (read(0, &ch, 1) > 0) 
      printf(" %d\n", ch);
  }
  return 0;
}
like image 41
Sam Toliman Avatar answered Nov 10 '22 22:11

Sam Toliman


Unix buffers your tty characters inside the kernel in part so that programs don't have to individually handle line editing unless they want to.

You can instruct the tty driver to give you the bytes immediately. There are various libraries that make this a bit easier than using the raw ioctl. You might start with termios(3).

like image 3
DigitalRoss Avatar answered Nov 10 '22 21:11

DigitalRoss