0

I am writing a programmer-user-friendly inetd-style wrapper, but all within C. This daemon will run all the time, and it has to be fast, so inetd is not an option. Moreover, I hope that my code below will be useful to others---in fact, as a novice, I found it strange that it does not exist already many times over. (I wrote my code because my earlier question whether this exists in some library violated guidelines.)

I have a couple of remaining problems:

  1. I do not know how to obtain the clientip in xxx.xxx.xxx.xxx form. In tcpserver_clientip(), this needs to be written to the c->ipaddress and then returned by my tcpserver_clientip() function. I would also love to know how to get the reverse-lookup the client DNS name, tcpserver_clientname(), but this is for another day.

  2. I do not know how to close the local port, so that it is immediately released upon orderly program termination. right now, the port becomes available again after a few seconds. relying on process death to release a resource seems like a bug to me. setsockopt also seems strange to do this---I want to close it on valid exit, not set an option when I open it.

  3. I would prefer if, instead of using tcpserver_clientwrite(), listener() could print to stdout; and instead of using tcpserver_clientread(), listener() could read from stdin. Is this possible in linux (and C) without losing a great deal of speed? I do not believe it is. that is, I do not believe one can hook stdin/stdout into the socket recvfrom and sendto. but rather than guess, I wanted to ask.

  4. does anyone know why sigaction() fails, where signal() works? (for protecting me from zombie children?) this is under linux gcc. the prototypes of sigaction and signal seem to be different.

  5. for tcp (not udp), is there a sample program like this that works without forking? I have only seen this type of code with a fork. presumably, the reader/writer functions would have to carry a structure as to which of many clients a particular in/out just came from/is going to. and presumably, avoiding the fork could save a lot more time.

also, if anyone notices a security hole or other bug, please let me know.

So here is my layout and purpose:

/*

NAME

    tcpserver.h 

DESCRIPTION

    a high-level library implementation of a tcp server, with an inetd-like
    ease-of-use.  the library takes care of forking, reaping, etc. The focus
    right now are clean text connections ('\0' terminated, but overflow safe).

BUGS

USER INTERFACE

    the user calls the tcpserver function with a callback, the listener():
*/

static int tcpserver(const int port, const int msgmaxlen, int (*listener)(void *));
static int tcpserver_verbose(const int port, const int msgmaxlen, int (*listener)(void *));

/*
    the listener() has access to the following functions:
*/

static int  tcpserver_clientwrite( const void *, const char *msg );
static char *tcpserver_clientread( const void * );
static char *tcpserver_clientip( void * );  // this will store inside the structure, so its volatile
static int  tcpserver_clientport( const void *);

static const char *tcpserver_version= "tcpserver.h 0.1";

here is my example of how I want to use it:

USE EXAMPLE

#include <stdio.h>
#include "tcpserver.h"

int listener( void *c ) {
  printf("listener %d> Hello IP='%s' on port %d\n", getpid(), 
     tcpserver_clientip(c), tcpserver_clientport(c));

  int terminate=0;
  do {
    char *msgin= tcpserver_clientread( c );
    printf("listener %d r> '%s'\n", getpid(), msgin);
    tcpserver_clientwrite( c, msgin );

    printf("listener %d w> '%s'\n", getpid(), msgin);
    fflush(stdout);
    terminate= (((strncmp(msgin, "quit", 4)==0)&&(msgin[4]=='\r'))
        ||((strncmp(msgin, "bye", 3)==0)&&(msgin[3]=='\r')));
  } while (!terminate);

  printf("\nlistener %d> **** listener requested orderly termination ****\n", getpid());

  exit(EXIT_SUCCESS);
}

int main(int argc, char *argv[]) {
  const int PORT= (argc>=2) ? atoi(argv[1]) : (32001);
  const int MAXMSGLEN=1024;
  printf("Parent %d.  version %s\n", getpid(), tcpserver_version);
  return tcpserver( PORT, MAXMSGLEN, &listener );
}

and here is the code itself, also stuffed into the same .h file:

/****************************************************************/

#ifndef INETVERBOSE
#define INETVERBOSE 1
#endif

#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/socket.h>

/********************************************************************************************************************************
 * Storage for each client connection
 ****************************************************************/

#define MAXIPSTRLEN

struct tcpserver_clientconnection {
  struct sockaddr_in cliaddr; // Socket Library
  int connfd; // Socket Library
  socklen_t clilen; // Socket Library

  // read/write to client
  char *msgbuf;  // the buffer for a single longest message
  int msgmaxlen;  // its maximum length
  int verbose;  // makes debugging easier

  // write by tcpserver, read-only to client
  unsigned int port;
  char ipaddress[MAXIPSTRLEN];
};



/********************************************************************************************************************************
 * The Actual Server
 ****************************************************************/

static int _tcpserver(const int port, const int msgmaxlen, int (*listener)(void *c), const int verbose) {

  void die(const char *s) { fprintf(stderr, "tcpserver.h death: %s : ", s); perror(""); exit(EXIT_FAILURE); }

  const int parentpid= getpid();
  const int listenfd=socket(AF_INET,SOCK_STREAM,0);

  struct sockaddr_in servaddr;

  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
  servaddr.sin_port=htons(port);

  if (verbose) fprintf(stderr, "[parent %d] ---------------- Listening on Port %d\n", parentpid, port);
  if (bind(listenfd,(struct sockaddr *)&(servaddr),sizeof(servaddr)))
    die("Sorry, we could not bind to the socket");

  // safety --- reap all children
  signal(SIGCHLD, SIG_IGN);  // sigaction(SIGCHLD, SA_NOCLDWAIT)

  if (listen(listenfd,1024)) die("Cannot listen to you.");

  for(;;) {
    // collect what we need to pass
    struct tcpserver_clientconnection c;
    c.port= port;

    c.clilen=sizeof(c.cliaddr);
    c.connfd = accept(listenfd,(struct sockaddr *)&(c.cliaddr),&(c.clilen));

    if (verbose) fprintf(stderr, "[parent %d] incoming connection ", parentpid);

    pid_t childpid;
    if ((childpid = fork()) == 0) {
      close(listenfd);  // the child can close its listenfd connection

      if (verbose) { fprintf(stderr, "[Spawned Child Process %d for connection]\n", getpid()); }
      c.verbose= verbose;
      c.msgmaxlen= msgmaxlen;
      c.msgbuf= (char *) calloc( msgmaxlen, sizeof(char) );
      if (!c.msgbuf) die("cannot allocate memory to message buffer");
      listener(&c);
      free(c.msgbuf);
    }

    int status;
    waitpid(childpid, &status, 0);
    if (verbose) fprintf(stderr, "[Parent Asynch: Child %d was stopped with return value %d]\n", childpid, status);
    close(c.connfd);  // release the connection
  }
  if (verbose) fprintf(stderr, "[%d] Main Program Bye", parentpid);
  return EXIT_SUCCESS;
}


static inline int tcpserver_verbose(const int port, const int msgmaxlen, int (*listener)(void *c)) {
  return _tcpserver(port, msgmaxlen, listener, 1);
}
static inline int tcpserver(const int port, const int msgmaxlen, int (*listener)(void *c)) {
  return _tcpserver(port, msgmaxlen, listener, 0);
}


/********************************************************************************************************************************
 * Listener (Client) Access Functions
 ****************************************************************/
static char *tcpserver_clientip(void *v) { 
  struct tcpserver_clientconnection *c= (struct tcpserver_clientconnection *)v;

  const char *peer_addr(int sockfd, char *ipnamedest, size_t ipnamedestsiz) {
    struct sockaddr_in adr_inet;
    unsigned int len_inet = sizeof(adr_inet);
    int z = getpeername(sockfd, (struct sockaddr *)&adr_inet, &len_inet);
    if ( z == -1 ) return NULL;    /* Failed */
    z = snprintf(ipnamedest, ipnamedestsiz, "%s:%d", inet_ntoa(adr_inet.sin_addr), (unsigned)ntohs(adr_inet.sin_port));
    if ( z == -1 ) return NULL;    /* Ipnamedestfer too small */
    return ipnamedest;
  }

  strcpy(c->ipaddress, "unknown");
  //peer_addr( listenfd, c->ipaddress, MAXIPSTRLEN );
  if (c->verbose) fprintf(stderr, "IP=%s\n", c->ipaddress);

  return c->ipaddress;
}

static int tcpserver_clientport( const void *v ) { 
  struct tcpserver_clientconnection *c= (struct tcpserver_clientconnection *)v;
  return c->port;
}

static char *tcpserver_clientread( const void *v ) {
  struct tcpserver_clientconnection *c= (struct tcpserver_clientconnection *)v;
  if (!c->msgbuf) perror("internal error: NULL msgbuf");
  int rc= recvfrom(c->connfd, c->msgbuf, c->msgmaxlen, 0, (struct sockaddr *)&(c->cliaddr), &(c->clilen));
  if (rc<0) { perror("what is wrong with you?\n"); return NULL; }
  c->msgbuf[c->msgmaxlen -1]='\0';  // for safety...always
  if (rc>0) c->msgbuf[rc]='\0';  // for safety...always
  if (c->verbose) fprintf(stderr, "clientread> '%s'\n", c->msgbuf); 
  return c->msgbuf;
}

static int tcpserver_clientwrite( const void *v, const char *msg ) {
  struct tcpserver_clientconnection *c= (struct tcpserver_clientconnection *)v;

  if (!msg) perror("why would you want to write a NULL msg??");
  int n=strlen(msg);
  if (n==(-1)) n= strlen(c->msgbuf);  // -1 means textmode
  if (n > (c->msgmaxlen)) n= c->msgmaxlen;
  if (c->verbose) fprintf(stderr, "clientwriting> '%s'\n", c->msgbuf); 
  sendto(c->connfd, c->msgbuf, n, 0, (struct sockaddr *)&(c->cliaddr),sizeof((c->cliaddr)));
  if (c->verbose) fprintf(stderr, "clientwritten> '%s'\n", c->msgbuf); 
  return n;
}
4

2 に答える 2

1

recv を介してメッセージを取得するために tcpserver_read() を呼び出す代わりに、リスナーが stdin を読み取れるようにするにはどうすればよいですか?

接続されたソケットを stdin および stdout としてサーバーに渡すと同じようinetdに作成できます。

             dup2(c->connfd, 0);
             close(c->connfd);
             dup2(0, 1);
于 2014-09-23T09:59:06.010 に答える