代码之家  ›  专栏  ›  技术社区  ›  roschach

使用unix getaddrinfo c函数开始设置服务器

  •  0
  • roschach  · 技术社区  · 6 年前

    我正在用C语言构建一个客户机-服务器应用程序,源代码取自《Unix环境中的高级编程》一书。

    struct addrinfo hint;
    memset(&hint, 0, sizeof(hint));
    hint.ai_flags = AI_CANONNAME;
    hint.ai_socktype = SOCK_STREAM;
    hint.ai_addr = NULL;
    hint.ai_next = NULL;
    ....
    if ((n = sysconf(_SC_HOST_NAME_MAX))<0)
    {
        n = HOST_NAME_MAX;
    }
    if((host = malloc(n)) == NULL)
    {
        printf("malloc error\n");
        exit(1);
    }
    if (gethostname(host, n)<0)
    {
        printf("gethostname error\n");
        exit(1);
    }
    ...
    if((err = getaddrinfo(host, "ruptime", &hint, &ailist))!=0)
    {
        syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
        exit(1);
    }
    for (aip = ailist; aip!=NULL; aip = aip->ai_next)
    {
        if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN))>=0)
        {
            //printf("starting to serve\n");
            serve(sockfd);
            exit(0);
        }
    }
    

    据我所知 getaddrinfo ruptime SOCK_STREAM

    /etc/services/ 具有未使用的端口和指定的名称

    ruptime         49152/tcp #ruptime Unix System Programming
    ruptime         49152/udp #ruptime Unix System Programming
    

    但是文件上说

    AI_PASSIVE 标志在中指定 hints.ai_flags NULL INADDR_ANY IPv4地址, IN6ADDR_ANY_INIT

    hint.ai_flags |= AI_PASSIVE
    ...
    getaddrinfo(NULL, myserviceport, &hint, &aihint)
    

    这两种方法到底有什么区别?第二个也在找 SOCK_DGM ?书中选择第一种方法有什么原因吗?第二种方法是,由于我在代码中指定了端口,它是否允许避免在

    另一个问题。 我必须把主机名传给客户。我认为环回(客户机和服务器在同一台机器上运行)地址是可以的。相反,主机名类似于 ./client MBPdiPippo.lan . 什么定义了可以使用主机名而不是环回地址创建连接的事实?是我路过吗 host 在服务器上?

    全码

    server.c

    #include<stdio.h>
    #include<stdlib.h>
    #include<unistd.h> //_SC_HOST_NAME_MAX
    #include<string.h>
    #include<netdb.h> //Here are defined AF_INET and the others of the family
    #include<syslog.h> //LOG_ERR
    #include<errno.h> //errno
    #include <sys/types.h>
    
    #include"utilities.h"
    #include "error.h"
    
    #define BUFLEN 128
    #define QLEN 10
    
    #ifndef HOST_NAME_MAX
    #define HOST_NAME_MAX 156
    #endif
    
    int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen);
    void serve(int sockfd);
    
    int main(int argc, char* argv[])
    {
        printf("entered main\n");
        struct addrinfo *ailist, *aip, hint;
        int sockfd, err, n;
        char *host;
        if (argc != 1)
        {
            printf("usage: ruptimed\n");
            exit(1);
        }
        if ((n=sysconf(_SC_HOST_NAME_MAX))<0)
        {
            n = HOST_NAME_MAX;
        }
        if((host = malloc(n)) == NULL)
        {
            printf("malloc error\n");
            exit(1);
        }
        if (gethostname(host, n)<0)
        {
            printf("gethostname error\n");
            exit(1);
        }
        printf("host: %s\n", host);
        printf("Daemonizing\n");
        int res = daemonize("ruptimed");
        printf("%d\n", res);
        printf("Daemonized\n");
        memset(&hint, 0, sizeof(hint)); //set to 0 all bytes
        printf("hint initialized\n");
        hint.ai_flags = AI_CANONNAME;
        hint.ai_socktype = SOCK_STREAM;
        hint.ai_canonname = NULL;
        hint.ai_addr = NULL;
        hint.ai_next = NULL;
        printf("getting addresses\n");
        if((err = getaddrinfo(host, "ruptime", &hint, &ailist))!=0)
        {
            printf("error %s\n", gai_strerror(err));
            syslog(LOG_ERR, "ruptimed: getaddrinfo error %s", gai_strerror(err));
            exit(1);
        }
        printf("Got addresses\n");
        for (aip = ailist; aip!=NULL; aip = aip->ai_next)
        {
            if ((sockfd = initserver(SOCK_STREAM, aip->ai_addr, aip->ai_addrlen, QLEN))>=0)
            {
                printf("starting to serve\n");
                serve(sockfd);
                exit(0);
            }
        }
        exit(1);
    }
    
    void serve(int sockfd)
    {
        int clfd;
        FILE *fp;
        char buf[BUFLEN];
        set_cloexec(sockfd);
        for(;;)
        {
            /*After listen, the socket can receive connect requests. accept
            retrieves a connect request and converts it into a connection.
            The file returned by accept is a socket descriptor connected to the client that
            called connect, haing the same coket type and family type. The original
            soket remains available to receive otherconneion requests. If we don't care
            about client's identity we can set the second (struct sockaddr *addr)
            and third parameter (socklen_t *len) to NULL*/
            if((clfd = accept(sockfd, NULL, NULL))<0)
            {
                /*This generates a log mesage.
                syslog(int priority, const char *fformat,...)
                priority is a combination of facility and level. Levels are ordered from highest to lowest:
                LOG_EMERG: emergency system unusable
                LOG_ALERT: condiotin that must be fied immediately
                LOG_CRIT: critical condition
                LOG_ERR: error condition
                LOG_WARNING
                LOG_NOTICE
                LOG_INFO
                LOG_DEBUG
                format and other arguements are passed to vsprintf function forf formatting.*/
                syslog(LOG_ERR, "ruptimed: accept error: %s", strerror(errno));
                exit(1);
            }
            /* set the FD_CLOEXEC file descriptor flag */
            /*it causes the file descriptor to be automatically and atomically closed
             when any of the exec family function is called*/
            set_cloexec(clfd);
            /**pg. 542 Since a common operation is to create a pipe to another process
            to either read its output or write its input Stdio has provided popen and
            pclose: popen creates pipe, close the unused ends of the pipe,
            forks a child and call exec to execute cmdstr and
            returns a file pointer (connected to std output if "r", to stdin if "w").
            pclose closes the stream, waits for the command to terminate*/
            if ((fp = popen("/usr/bin/uptime", "r")) == NULL)
            {
                /*sprintf copy the string passed as second parameter inside buf*/
                sprintf(buf, "error: %s\n", strerror(errno));
                /*pag 610. send is similar to write. send(int sockfd, const void *buf, size_t nbytes, it flags)*/
                send(clfd, buf, strlen(buf),0);
            }
            else
            {
                /*get data from the pipe that reads created to exec /usr/bin/uptime */
                while(fgets(buf, BUFLEN, fp)!=NULL)
                {
                    /* clfd is returned by accept and it is a socket descriptor
                    connected to the client that called connect*/
                    send(clfd, buf, strlen(buf), 0);
                }
                /*see popen pag. 542*/
                pclose(fp);
            }
            close(clfd);
        }
    }
    
    
    int initserver(int type, const struct sockaddr *addr, socklen_t alen, int qlen)
    {
        int fd, err;
        int reuse = 1;
        if ((fd = socket(addr->sa_family, type, 0))<0)
        {
            return (-1);
        }
        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))<0)
        {
            goto errout;
        }
        if(bind(fd, addr, alen)<0)
        {
            goto errout;
        }
        if (type == SOCK_STREAM || type == SOCK_SEQPACKET)
        {
            if(listen(fd, qlen)<0)
            {
                goto errout;
            }
        }
        return fd;
        errout:
            err = errno;
            close (fd);
            errno = err;
            return(-1);
    }
    

    utilities.c demonize setcloexec daemonize

    #include "utilities.h"
    #include <stdlib.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <stdio.h>
    #include <syslog.h>
    #include <sys/time.h>//getrlimit
    #include <sys/resource.h>//getrlimit
    #include <signal.h> //sigempyset , asigcation (umask?)
    #include <sys/resource.h>
    #include <fcntl.h> //O_RDWR
    #include <stdarg.h>
    
    #include "error.h"
    int daemonize(const char *cmd)
    {
        int fd0, fd1, fd2;
        unsigned int i;
        pid_t pid;
        struct rlimit       rl;
        struct sigaction    sa;
        /* *Clear file creation mask.*/
        umask(0);
        /* *Get maximum number of file descriptors. */
        if (getrlimit(RLIMIT_NOFILE, &rl) < 0)
        {
            err_quit("%s: can’t get file limit", cmd);
        }
        /* *Become a session leader to lose controlling TTY. */
        if ((pid = fork()) < 0)
        {
            err_quit("%s: can’t fork", cmd);
        }
        else if (pid != 0) /* parent */
        {
            exit(0); //the parent will exit
        }
        setsid();
        /* *Ensure future opens won’t allocate controlling TTYs. */
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0)
        {
            err_quit("%s: can’t ignore SIGHUP", cmd);
        }
        if ((pid = fork()) < 0)
        {
            err_quit("%s: can’t fork", cmd);
        }
        else if (pid != 0) /* parent */
        {
            exit(0);
        }
        /*
        *Change the current working directory to the root so
        * we won’t prevent file systems from being unmounted.
        */
        if (chdir("/") < 0)
        {
            err_quit("%s: can’t change directory to /", cmd);
        }
        /* Close all open file descriptors. */
        if (rl.rlim_max == RLIM_INFINITY)
        {
            rl.rlim_max = 1024;
        }
        printf("closing file descriptors\n");
        /*for (i = 0; i < rl.rlim_max; i++)
        {
            close(i);
        }*/
        /* *Attach file descriptors 0, 1, and 2 to /dev/null.*/
        //printf not working
        /*printf("closed all file descriptors for daemonizing\n");*/
        /*fd0 = open("/dev/null", O_RDWR);
        fd1 = dup(0);
        fd2 = dup(0);*/
        /* *Initialize the log file. Daemons do not have a controlling terminal so
        they can't write to stderror. We don't want them to write to the console device
        because on many workstations the control device runs a windowing system. They can't
        write on separate files either. A central daemon error-logging facility is required.
        This is the BSD. 3 ways to generate log messages:
        1) kernel routines call the log function. These messages can be read from /dev/klog
        2) Most user processes (daemons) call syslog to generate log messages. This causes
        messages to be sent to the UNIX domain datagram socket /dev/log
        3) A user process on this host or on other host connected to this with TCP/ID
        can send log messages to UDP port 514. Explicit network programmin is required
        (it is not managed by syslog.
        The syslogd daemon reads al three of log messages.
    
        openlog is optional since if not called, syslog calls it. Also closelog is optional
        openlog(const char *ident, int option, int facility)
        It lets us specify ident that is added to each logmessage. option is a bitmask:
            LOG_CONS tells that if the log message can't be sent to syslogd via UNIX
            domain datagram, the message is written to the console instead.
        facility lets the configuration file specify that messages from different
        facilities are to be handled differently. It can be specified also in the 'priority'
        argument of syslog. LOG_DAEMON is for system deamons
        */
        /*
        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if (fd0 != 0 || fd1 != 1 || fd2 != 2)
        {*/
            /*This generates a log mesage.
            syslog(int priority, const char *fformat,...)
            priority is a combination of facility and level. Levels are ordered from highest to lowest:
            LOG_EMERG: emergency system unusable
            LOG_ALERT: condiotin that must be fied immediately
            LOG_CRIT: critical condition
            LOG_ERR: error condition
            LOG_WARNING
            LOG_NOTICE
            LOG_INFO
            LOG_DEBUG
    
            format and other arguements are passed to vsprintf function forf formatting.*/
            /*syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
            exit(1);
        }*/
        return 0;
    }
    
    /*The function set the FD_CLOEXEC flag of the file descriptor already open that
    is passed to as parameter. FD_CLOEXEC causes the file descriptor to be
    automatically and atomically closed when any of the exec family function is
    called*/
    int set_cloexec(int fd)
    {
        int val;
        /* retrieve the flags of the file descriptor */
        if((val = fcntl(fd, F_GETFD, 0))<0)
        {
            return -1;
        }
        /* set the FD_CLOEXEC file descriptor flag */
        /*it causes the file descriptor to be automatically and atomically closed
         when any of the exec family function is called*/
        val |= FD_CLOEXEC;
        return (fcntl(fd, F_SETFD, val));
    }
    

    /* Fatal error unrelated to a system call.
    * Print a message and terminate*/
    void err_quit (const char *fmt, ...)
    {
        va_list ap;
        va_start (ap, fmt);
        err_doit (0, 0, fmt, ap);
        va_end (ap);
        exit(1);
    }
    
    /*Print a message and return to caller.
    *Caller specifies "errnoflag"*/
    static void err_doit(int errnoflag, int error, const char *fmt, va_list ap)
    {
        char buf [MAXLINE];
        vsnprintf (buf, MAXLINE-1, fmt, ap);
        if (errnoflag)
        {
            snprintf (buf+strlen(buf), MAXLINE-strlen(buf)-1, ": %s",
                strerror (error));
        }
        strcat(buf, "\n");
        fflush(stdout); /*in case stdout and stderr are the same*/
        fputs (buf, stderr);
        fflush(NULL); /* flushes all stdio output streams*/
    }
    
    1 回复  |  直到 6 年前
        1
  •  1
  •   Nominal Animal    6 年前

    getaddrinfo() initserver() freeaddrinfo() )循环之后。这使得代码更易于维护;您希望将紧密耦合的实现保持在一起。

    NULL AI_PASSIVE )表示套接字作为一个集合绑定到所有网络接口,而不是特定的网络接口。绑定到特定节点名称时,将绑定到特定网络接口。

    在实践中,这意味着如果在运行时有额外的网络接口可用,内核将在将数据包路由到/从绑定到通配符地址的套接字时考虑这些接口。

    它确实应该是每个系统管理员所做的选择,因为在某些用例中,服务(您的应用程序)应该监听所有网络接口上的传入连接,而在其他用例中,服务应该只监听特定接口或某些特定接口上的传入连接。典型的情况是当一台机器连接到多个网络。对于服务器来说,这是非常常见的。有关实际情况,请参见例如如何 the Apache web server

    就我个人而言,我会重写OP的 initServer()

    enum {
        /* TCP=1, UDP=2, IPv4=4, IPv6=8 */
        SERVER_TCPv4 = 5,   /* IPv4 | TCP */
        SERVER_UDPv4 = 6,   /* IPv4 | UDP */
        SERVER_TCPv6 = 9,   /* IPv6 | TCP */
        SERVER_UDPv6 = 10,  /* IPv6 | UDP */
        SERVER_TCP   = 13,  /* Any  | TCP */
        SERVER_UDP   = 14   /* Any  | UDP */
    };
    
    int initServer(const char *host, const char *port,
                   const int type, const int backlog)
    {
        struct addrinfo  hints, *list, *curr;
        const char      *node;
        int              family, socktype, result, fd;
    
        if (!host || !*host || !strcmp(host, "*"))
            node = NULL;
        else
            node = host;
    
        switch (type) {
        case SERVER_TCPv4: family = AF_INET;   socktype = SOCK_STREAM; break;
        case SERVER_TCPv6: family = AF_INET6;  socktype = SOCK_STREAM; break;
        case SERVER_TCP:   family = AF_UNSPEC; socktype = SOCK_STREAM; break;
        case SERVER_UDPv4: family = AF_INET;   socktype = SOCK_DGRAM;  break;
        case SERVER_UDPv6: family = AF_INET6;  socktype = SOCK_DGRAM;  break;
        case SERVER_UDP:   family = AF_UNSPEC; socktype = SOCK_DGRAM;  break;
        default:
            fprintf(stderr, "initServer(): Invalid server type.\n");
            return -1;
        }
        memset(&hints, 0, sizeof hints);
        hints.ai_flags = AI_PASSIVE;
        hints.ai_family = family;
        hints.ai_socktype = socktype;
        hints.ai_protocol = 0;
        hints.ai_canonname = NULL;
        hints.ai_addr = NULL;
        hints.ai_next = NULL;
        result = getaddrinfo(node, port, &hints, &list);
        if (result) {
            /* Fail. Output error message to standard error. */
            fprintf(stderr, "initServer(): %s.\n", gai_strerror(result));
            return -1;
        }
    
        fd = -1;
        for (curr = list; curr != NULL; curr = curr->ai_next) {
            int  reuse = 1;
    
            fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
            if (fd == -1)
                continue;
    
            if (bind(fd, curr->ai_addr, curr->ai_addrlen) == -1) {
                close(fd);
                fd = -1;
                continue;
            }
    
            if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
                            &reuse, sizeof (int)) == -1) {
                close(fd);
                fd = -1;
                continue;
            }
    
            if (listen(fd, backlog) == -1) {
                close(fd);
                fd = -1;
                continue;
            }
    
            break;
        }
        freeaddrinfo(list);
        if (fd == -1) {
            fprintf(stderr, "initServer(): Cannot bind to a valid socket.\n");
            return -1;
        }
    
        return fd;
    }
    

    (注意:代码没有经过测试,甚至没有编译;但是底层逻辑是健全的。如果您发现任何问题或错误,请在评论中告诉我,以便我可以查看、检查并在必要时进行修复。)

    host port "*" 无效的 ,函数将尝试绑定到通配符地址。(顺便说一下,这应该是默认值;如果服务器管理员希望限制到特定的接口,那么他们可以提供IP地址或与该接口对应的主机名。)

    港口 services getent services "49152" "ruptime"

    由于我在代码中指定了端口,它是否允许避免在/etc/services/中添加新条目?

    services 数据库(运行) 获得服务 在您的计算机上查看)只包含TCP的服务名和端口号之间的映射( SOCK_STREAM )和/或UDP( SOCK_DGRAM 协议。

    ruptime 49152/tcp . 这同时影响服务器和客户机。(也就是说,即使您的服务器知道ruptime是TCP套接字的端口49152,客户机也不会知道,除非他们在自己的服务数据库中拥有它。)

    通常,大多数管理员不需要编辑服务数据库,而是使用显式端口号。当您安装了防火墙(以及相关的实用程序,如fail2ban,我甚至建议在工作站和笔记本电脑上安装)时,如果在服务配置文件中清楚地显示端口号,则更容易维护规则。

    对于在同一台计算机上运行的客户机,我必须传递主机名。我以为回送地址可以用。什么定义了可以使用主机名而不是环回地址创建连接的事实?我是否正在将主机作为第一个参数传递给服务器中的getaddrinfo?

    对。如果将服务绑定到通配符地址,它将响应所有网络接口上的请求,包括环回地址。

    如果绑定到特定主机名,它将只响应对该特定网络接口的请求。

    (这是由操作系统内核完成的,也是网络数据包如何路由到用户空间应用程序的一部分。)

    这也意味着,绑定到特定主机名(而不是通配符地址)的“适当的”启用Internet的服务应该真正能够侦听几个套接字上的传入连接,而不是只侦听一个套接字。它可能不是绝对必要的,甚至不需要 用例,但是我可以告诉你,当服务在跨越几个不同网络的机器上运行,并且你只想向其中的一些网络提供服务时,它确实非常有用。幸运的是,您可以使监听套接字不阻塞(使用 fcntl(fd, F_SETFL, O_NONBLOCK) fcntl(fd, F_SETFD, O_CLOEXEC) O_CLOEXEC ,以便侦听套接字不会意外地传递给执行外部二进制文件的子进程),然后使用 select() poll() accept()