C tcp socket magic:clientip,关闭端口,重新加入stdin / stdout,sigaction,speed

时间:2013-10-13 06:38:51

标签: c tcp

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

我还有一些问题:

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

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

  3. 我更愿意,如果不使用tcpserver_clientwrite(),listener()可以打印到stdout;而不是使用tcpserver_clientread(),listener()可以从stdin读取。这可能在Linux(和C)中不会失去很大的速度吗?我不相信。也就是说,我不相信可以将stdin / stdout挂接到套接字recvfrom和sendto。但我想问一下,而不是猜测。

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

  5. 对于tcp(而不是udp),是否有一个这样的示例程序无需分叉?我只用fork看过这种类型的代码。据推测,读取器/写入器功能必须携带一个结构,以确定特定输入/输出的许多客户端中的哪一个来自/将要发生。并且据推测,避免使用前叉可以节省更多时间。

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

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

    /*
    
    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;
    }
    

2 个答案:

答案 0 :(得分:2)

  

我不知道如何以xxx.xxx.xxx.xxx格式获取clientip。

你不需要那种形式。你需要它的二进制形式,如sockaddr_in。其他任何东西都只是装饰性的。

  

我不知道如何关闭本地端口

使用close()。

  

我更愿意,如果不使用tcpserver_clientwrite(),listener()可以打印到stdout;而不是使用tcpserver_clientread(),listener()可以从stdin读取。这可能在linux(和C)

  

没有失去很大的速度?

不会失去任何速度。我不知道为什么你不这么认为。并不是说这是个好主意。

您的代码并不像您想象的那么精彩。例如,它一次只处理一个客户端,而不是并行处理它们,因为您在启动它之后等待每个子节点。您还在等待孩子 孩子,这是一个错误。您的消息发送/接收API与send()和recv()相比没有任何改进。

答案 1 :(得分:1)

  

我怎样才能使听众能够读取stdin而不是调用   tcpserver_read()通过recv获取消息?

你可以使它像inetd一样,将连接的套接字作为stdin和stdout传递给服务器。

             dup2(c->connfd, 0);
             close(c->connfd);
             dup2(0, 1);