UNIX Socket FAQ

A forum for questions and answers about network programming on Linux and all other Unix-like systems

You are not logged in.

#1 2002-07-26 11:26 PM

HectorLasso
Administrator
From: Colombia
Registered: 2002-06-12
Posts: 353

Re: 4.14 - Attaching processes IO using sockets.

6

If the program you are running uses printf(), etc (streams from stdio.h) you have to deal with two buffers. The kernel buffers all socket IO, and this is explained in section 2.11. The second buffer is the one that is causing you grief. This is the stdio buffer, and the problem was well explained by Andrew:

(The short answer to this question is that you want to use a pty rather than a socket; the remainder of this article is an attempt to explain why.)

Firstly, the socket buffer controlled by setsockopt() has absolutly nothing to do with stdio buffering. Setting it to 1 is guaranteed to be the Wrong Thing(tm).

Perhaps the following diagram might make things a little clearer:

Process A                   Process B
    +---------------------+     +---------------------+
    |                     |     |                     |
    |    mainline code    |     |    mainline code    |
    |         |           |     |         ^           |
    |         v           |     |         |           |
    |      fputc()        |     |      fgetc()        |
    |         |           |     |         ^           |
    |         v           |     |         |           |
    |    +-----------+    |     |    +-----------+    |
    |    | stdio     |    |     |    | stdio     |    |
    |    | buffer    |    |     |    | buffer    |    |
    |    +-----------+    |     |    +-----------+    |
    |         |           |     |         ^           |
    |         |           |     |         |           |
    |      write()        |     |       read()        |
    |         |           |     |         |           |
    +-------- | ----------+     +-------- | ----------+
              |                           |                  User space
  ------------|-------------------------- | ---------------------------
              |                           |                Kernel space
              v                           |
         +-----------+               +-----------+
         | socket    |               | socket    |
         | buffer    |               | buffer    |
         +-----------+               +-----------+
              |                           ^
              v                           |
      (AF- and protocol-          (AF- and protocol-
       dependent code)             dependent code)

Assuming these two processes are communicating with each other (I've deliberately omitted the actual comms mechanisms, which aren't really relevent), you can see that data written by process A to its stdio buffer is completely inaccessible to process B. Only once the decision is made to flush that buffer to the kernel (via write()) can the data actually be delivered to the other process.

The only guaranteed way to affect the buffering within process A is to change the code. However, the default buffering for stdout is controlled by whether the underlying FD refers to a terminal or not; generally, output to terminals is line-buffered, and output to non-terminals (including but not limited to files, pipes, sockets, non-tty devices, etc.) is fully buffered. So the desired effect can usually be achieved by using a pty device; this, for example, is what the 'expect' program does.

Since the stdio buffer (and the FILE structure, and everything else related to stdio) is user-level data, it is not preserved across an exec() call, hence trying to use setvbuf() before the exec is ineffective.

A couple of alternate solutions were proposed by Roger Espel Llima (espel@drakkar.ens.fr):

If it's an option, you can use some standalone program that will just run something inside a pty and buffer its input/output. I've seen a package by the name pty.tar.gz that did that; you could search around for it with archie or AltaVista.

Another option (**warning, evil hack**) , if you're on a system that supports this (SunOS, Solaris, Linux ELF do; I don't know about others) is to, on your main program, putenv() the name of a shared executable (*.so) in LD_PRELOAD, and then in that .so redefine some commonly used libc function that the program you're exec'ing is known to use early. There you can 'get control' on the running program, and the first time you get it, do a setbuf(stdout, NULL) on the program's behalf, and then call the original libc function with a dlopen() + dlsym(). And you keep the dlsym() value on a static var, so you can just call that the following times.

(Editors note: I still haven't done an expample for how to do pty's, but I hope I will be able to do one after I finish the non-blocking example code.)

From: Alexandre Jasmin

I made this small program while experimenting with ptys. It uses forkpty(), a BSD fature.

What it does is to listen for a connection on a port, then it call forkpty(). Like fork(), this function return a diferent value for the parent and child process. After the call the child run in a pty.

In this program, the child process simply call exec while the parent process use read() and write() in a loop to copy everything that come from the pty to the socket and vice versa.

It lacks handling of SIGCHLD and SIGPIPE, makes I/O one byte at time and support only one connection. But it should give an idea.

If you know a better way of doing this, email it to me please.

Also note that not all systems have forkpty() in the same header file or library.

I was able to compile this program on Linux with glibc, FreeBSD, MacOS X and Solaris 8.

Solaris don't have a forkpty so I also wrote a forkpty remplacement for Solaris.

Finaly you may have some problems if you use telnet to test this program. It will work better if you write a simple raw TCP client or hack the code so that it handle some telnet fatures. See 4.13.

----------------------- 
  pty_serv.c 
----------------------- 

#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 

/* 
 * forkpty may be in diferent places 
 * depending of your OS 
 */ 

#ifdef LINUX /* You need to link with libutil */ 
#include <pty.h> 
#endif 
   
#ifdef FREEBSD /* You need to link with libutil */ 
#include <libutil.h> 
#endif 

#ifdef MACOSX /* Aparently no link flags are needed */ 
#include <stdint.h> 
#include <util.h> 
#endif 

#ifdef SOLARIS /* Use the code in my_forkpty.c */ 
int my_forkpty (int *amaster, char *name, void *unused1, void *unused2); 
#define forkpty my_forkpty 
#endif 

#define EXEC_FILE "./test" 

#define PORT 1234 

int 
get_connect_from (uint16_t port) 
{ 
  int srv_sock, client_sock; 
  struct sockaddr_in srv_name, client_name; 
  int size; 

  srv_sock = socket (PF_INET, SOCK_STREAM, 0); 
  if (srv_sock < 0) 
    { 
      perror ("socket()"); 
      exit (EXIT_FAILURE); 
    } 

  srv_name.sin_family = AF_INET; 
  srv_name.sin_port = htons (port); 
  srv_name.sin_addr.s_addr = htonl (INADDR_ANY); 
  if (bind (srv_sock, (struct sockaddr *) &srv_name, sizeof (srv_name)) < 0) 
    { 
      perror ("bind()"); 
      exit (EXIT_FAILURE); 
    } 

  if (listen (srv_sock, 1) < 0) 
    { 
      perror ("listen()"); 
      exit (EXIT_FAILURE); 
    } 

  size = sizeof(client_name); 
  client_sock = accept (srv_sock, (struct sockaddr *) &client_name, &size); 
  if (client_sock < 0) 
    { 
      perror ("accept"); 
      exit (EXIT_FAILURE); 
    } 

  return client_sock; 
} 

int 
main (void) 
{ 
  int sock; 
  fd_set descriptor_set; 
  char c; 

  int pty; 
   
  sock = get_connect_from(PORT); 
   
  switch (forkpty (&pty, /* pseudo-terminal master end descriptor */ 
                   NULL, /* This can be a char[] buffer used to get... */ 
                              /* ...the device name of the pseudo-terminal */ 
                   NULL, /* This can be a struct termios pointer used... */ 
                              /* to set the terminal attributes */ 
                   NULL)) /* This can be a struct winsize pointer used... */ 
    { /* ...to set the screen size of the terminal */ 
    case -1: /* Error */ 
      perror ("fork()"); 
      exit (EXIT_FAILURE); 

    case 0: /* This is the child process */ 
      close (sock); 
      execl(EXEC_FILE, EXEC_FILE, NULL); 

      perror("exec()"); /* Since exec* never return */ 
      exit (EXIT_FAILURE); 

    default: /* This is the parent process */ 
      while (1) 
        { 
          FD_ZERO (&descriptor_set); 
          FD_SET (sock, &descriptor_set); 
          FD_SET (pty, &descriptor_set); 
           
          if (select (FD_SETSIZE, &descriptor_set, NULL, NULL, NULL) < 0) 
            { 
              perror ("select()"); 
              exit (EXIT_FAILURE); 
            } 
           
          if (FD_ISSET (sock, &descriptor_set)) 
            { 
              if ( (read (sock, &c, 1) != 1) 
                   || (write (pty, &c, 1) != 1) ) 
                { 
                  fprintf (stderr, "Disconnected\n"); 
                  exit (EXIT_FAILURE); 
                } 
            } 
             
          if (FD_ISSET (pty, &descriptor_set)) 
            { 
              if ((read (pty, &c, 1) != 1) 
                  || (write (sock, &c, 1) != 1) ) 
                { 
                  fprintf (stderr, "Disconnected\n"); 
                  exit (EXIT_FAILURE); 
                } 
            } 
        } 
    } 
     
  return EXIT_FAILURE; 
} 


----------------------- 
  my_forkpty.c 
----------------------- 
#include <fcntl.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <sys/ioctl.h> 
#include <sys/stream.h> 
#include <sys/stropts.h> 

/* fork_pty() remplacement for Solaris. 
 * This ignore the last two arguments 
 * for the moment 
 */ 
int 
my_forkpty (int *amaster, 
            char *name, 
            void *unused1, 
            void *unused2) 
{ 
  int master, slave; 
  char *slave_name; 
  pid_t pid; 
   
  master = open("/dev/ptmx", O_RDWR); 
  if (master < 0) 
    return -1; 

  if (grantpt (master) < 0) 
    { 
      close (master); 
      return -1; 
    } 

  if (unlockpt (master) < 0) 
    { 
      close (master); 
      return -1; 
    } 

  slave_name = ptsname (master); 
  if (slave_name == NULL) 
    { 
      close (master); 
      return -1; 
    } 

  slave = open (slave_name, O_RDWR); 
  if (slave < 0) 
    { 
      close (master); 
      return -1; 
    } 

  if (ioctl (slave, I_PUSH, "ptem") < 0 
      || ioctl (slave, I_PUSH, "ldterm") < 0) 
    { 
      close (slave); 
      close (master); 
      return -1; 
    } 

  if (amaster) 
    *amaster = master; 

  if (name) 
    strcpy (name, slave_name); 
   
  pid = fork (); 
  switch (pid) 
    { 
    case -1: /* Error */ 
      return -1; 
    case 0: /* Child */ 
      close (master); 
      dup2 (slave, STDIN_FILENO); 
      dup2 (slave, STDOUT_FILENO); 
      dup2 (slave, STDERR_FILENO); 
      return 0; 
    default: /* Parent */ 
      close (slave); 
      return pid; 
    } 

  return -1; 
}

Offline

Board footer

Powered by FluxBB