0

我正在编写一个程序员用户友好的inetd 风格的包装器,但都在C 中。这个守护进程将一直运行,而且它必须很快,所以inetd 不是一个选项。此外,我希望我下面的代码对其他人有用——事实上,作为一个新手,我发现它已经多次不存在很奇怪。(我写了我的代码,因为我之前的问题是否存在于某些库中违反了准则。)

我还有几个问题:

  1. 不知道怎么获取xxx.xxx.xxx.xxx形式的clientip。在 tcpserver_clientip() 中,这需要写入 c->ipaddress,然后由我的 tcpserver_clientip() 函数返回。我也很想知道如何反向查找客户端 DNS 名称 tcpserver_clientname(),但这是另一天。

  2. 我不知道如何关闭本地端口,以便在程序有序终止时立即释放。现在,该端口在几秒钟后再次可用。依靠进程死亡来释放资源对我来说似乎是一个错误。setsockopt 这样做似乎也很奇怪——我想在有效退出时关闭它,而不是在打开它时设置选项。

  3. 如果不是使用 tcpserver_clientwrite(),我宁愿 listener() 可以打印到标准输出;而不是使用 tcpserver_clientread(),listener() 可以从标准输入读取。这在linux(和C)中是否可能而不会损失很多速度?我不相信它是。也就是说,我不相信可以将 stdin/stdout 挂接到套接字 recvfrom 和 sendto。但与其猜测,我想问。

  4. 有谁知道为什么 sigaction() 失败,signal() 在哪里工作?(为了保护我免受僵尸孩子的伤害?)这是在 linux gcc 下。sigaction 和 signal 的原型似乎不同。

  5. 对于 tcp(不是 udp),是否有这样的示例程序无需分叉即可工作?我只见过这种带叉子的代码。据推测,读取器/写入器功能必须携带一个结构,即特定输入/输出刚刚来自/将要到达的许多客户端。据推测,避免分叉可以节省更多时间。

另外,如果有人注意到安全漏洞或其他错误,请告诉我。

所以这是我的布局和目的:

/*

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";

这是我想如何使用它的示例:

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 );
}

这是代码本身,也填充到同一个 .h 文件中:

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

#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

2

不知道怎么获取xxx.xxx.xxx.xxx形式的clientip。

你不需要那种形式的。您需要它的二进制形式,如 sockaddr_in。其他任何东西都只是化妆品。

不知道怎么关闭本地端口

使用关闭()。

如果不是使用 tcpserver_clientwrite(),我宁愿 listener() 可以打印到标准输出;而不是使用 tcpserver_clientread(),listener() 可以从标准输入读取。这在linux(和C)中可能吗

是的。

不损失很多速度?

在不损失任何速度的情况下。我不知道你为什么不这么认为。并不是说这是个好主意。

您的代码并不像您想象的那么奇妙。例如,它一次只处理一个客户端,而不是并行处理它们,因为您在启动它之后等待每个孩子。你也在子里等,这是一个bug。您的消息发送/接收 API 对 send() 和 recv() 没有任何改进。

于 2013-10-13T06:50:15.093 回答
1

我怎样才能使侦听器可以读取标准输入,而不是调用 tcpserver_read() 来通过 recv 获取消息?

你可以像 一样inetd将连接的套接字作为标准输入和标准输出传递给服务器。

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