【问题标题】:How to add a default port number defined in program to getaddrinfo()如何将程序中定义的默认端口号添加到 getaddrinfo()
【发布时间】:2018-10-19 02:30:35
【问题描述】:

我想在我的标头中定义一个端口号(如#define port 9191)并用它来调用getaddrinfo()。但是我收到一个错误,因为参数服务是char const*。如何在对getaddrinfo() 的调用中使用#defined 端口号?

下面我将#defined 端口作为9191 并尝试使用sprintf() 将其转换为char*。然后我尝试在调用getaddrinfo()时使用它:

#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>

#define PORT 9191 // here the define port number
#define LEN 500

int
main(int argc, char *argv[])
{
    struct addrinfo hints;
    struct addrinfo *result, *rp;
    struct sockaddr_in cliAddr;  // the from address of a client
    int sock, s, clilen;
    char *service; // declared variable name
    struct sockaddr_in storage;

    ssize_t n;
    char receive[LEN], message[LEN];
    sprintf(service, "%d", PORT); // i have tried convert it to char

    if (argc != 1) {
        fprintf(stderr, "Usage: %s \n", argv[0]);
        exit(EXIT_FAILURE);
    }

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */
    hints.ai_flags = 0;    /* For wildcard IP address */
    hints.ai_protocol = 0;          /* Any protocol */
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    s = getaddrinfo(NULL, &service, &hints, &result);
    if (s != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        exit(EXIT_FAILURE);
    }

    /* getaddrinfo() returns a list of address structures.
       Try each address until we successfully bind(2).
       If socket(2) (or bind(2)) fails, we (close the socket
       and) try the next address. */

    for (rp = result; rp != NULL; rp = rp->ai_next) {
        sock = socket(rp->ai_family, rp->ai_socktype,
            rp->ai_protocol);
        if (sock == -1)
            continue;

        if (bind(sock, rp->ai_addr, rp->ai_addrlen) == 0)
            break;                  /* Success */

        close(sock);
    }

    if (rp == NULL) {               /* No address succeeded */
        fprintf(stderr, "Could not bind\n");
        exit(EXIT_FAILURE);
    }

    freeaddrinfo(result);           /* No longer needed */

    /* Read datagrams and echo them back to sender */

    while (1)
    {
        //bzero(receive, LEN);
        clilen = sizeof(cliAddr);
        n = recvfrom(sock, receive, strlen(receive), 0, (struct sockaddr*)&cliAddr, &clilen);
        if (n < 0)
        {
            fprintf(stderr, "error in reading\n");
            exit(EXIT_FAILURE);
        }
        printf("The client message: %s\n ", receive);
        bzero(message, LEN);

        fgets(message, sizeof(message), stdin);

        message[LEN] = '\0';
        printf("%s\n", message);
        n = sendto(sock, message, strlen(message), 0, (struct sockaddr*)&cliAddr, sizeof(cliAddr));
        printf("%ld\n", n);
        if (n < 0)
        {
            fprintf(stderr, "error in replying\n");
            exit(EXIT_FAILURE);
        }
        int i = strncmp("Exit", receive, 4);
        if (i == 0)
            break;

        printf("The server message: %s\n", message);

    }

    return 0;
}

【问题讨论】:

    标签: c sockets networking


    【解决方案1】:
    char *service; // declared variable name
    // ...
    sprintf(service, "%d", PORT); // i have tried convert it to char
    

    由于service 未初始化并且很可能包含无效的指针值,因此对sprintf() 的调用会导致未定义的行为。

    要么在空闲存储上分配内存并让service 指向它,要么使用足够大小的数组。

    【讨论】:

      【解决方案2】:

      由于Swordfish 已经回答,您正在尝试将sprintf() 指向未定义的指针。使用示例

      #define  STRINGIFY_(x)  #x
      #define  STRINGIFY(x)   STRINGIFY_(x)
      
      service = STRINGIFY(PORT);
      

      改为。


      考虑以下辅助函数:

      #define  DEFAULT_HOST "*"
      #define  DEFAULT_PORT "9191"
      
      /* Return a server-side UDP/IP(v4/v6) socket bound
         to the specified address. If addr is non-NULL,
         and *addrlen is initialized to its length and
         is large enough, the bound-to address will be
         stored there.  Returns -1 with errno set if
         an error occurs. */       
      int server_socket(struct sockaddr *addr, socklen_t *addrlen,
                        const char *host, const char *port)
      {
          struct addrinfo  hints, *list, *curr;
          int              err, fd;
      
          /* Set defaults, if NULL or empty. */
          if (!host || !*host)
              host = DEFAULT_HOST;
          if (!port || !*port)
              port = DEFAULT_PORT;
      
          /* "" or "*" host refer to wildcard address. */
          if (!host[0] || !strcmp(host, "*")
              host = NULL;
      
          memset(&hints, 0, sizeof hints);
          hints.ai_family = AF_INET;       /* Allow IPv4 or IPv6 */
          hints.ai_socktype = SOCK_DGRAM;  /* Datagram socket */
          hints.ai_flags = 0;              /* For wildcard IP address */
          hints.ai_protocol = 0;           /* Any protocol */
          hints.ai_canonname = NULL;
          hints.ai_addr = NULL;
          hints.ai_next = NULL;
      
          err = getaddrinfo(host, port, &hints, &list);
          if (err)
              switch (err) {
              case EAI_ADDRFAMILY:
              case EAI_FAMILY:
                  errno = EAFNOSUPPORT;
                  return -1;
              case EAI_AGAIN:
                  errno = EAGAIN;
                  return -1;
              case EAI_FAIL:
              case EAI_NODATA:
              case EAI_NONAME:
              case EAI_SERVICE:
                  errno = EADDRNOTAVAIL;
                  return -1;
              case EAI_MEMORY:
                  errno = ENOMEM;
                  return -1;
              case EAI_SOCKTYPE:
                  errno = ESOCKTNOSUPPORT;
                  return -1;
              case EAI_SYSTEM:
                  /* errno already set */
                  return -1;
              default:
                  errno = EINVAL;
                  return -1;
              }
      
          /* Find first valid socket we can construct and bind to */
          for (curr = list; curr != NULL; curr = curr->ai_next) {
              fd = socket(curr->ai_family, curr->ai_socktype, curr->ai_protocol);
              if (fd == -1)
                  continue;
      
              if (bind(fd, curr->ai_addr, curr->ai_addrlen) == 0)
                  break; /* This one works. */
      
              /* Cannot bind. Clean it up, and try the next one. */
              close(fd);
          }
      
          if (!curr) {
              /* No suitable socket found. */
              errno = EADDRNOTAVAIL;
              return -1;
          }       
      
          /* Copy bound-to address, if enough room */
          if (addrlen) {
              if (!addr || *addrlen < curr->ai_addrlen) {
                  *addrlen = 0;
              } else {
                  memcpy(addr, curr->ai_addr, curr->ai_addrlen);
                  *addrlen = curr->ai_addrlen;
              }
          }
      
          /* Discard the linked list getaddrinfo() provided. */
          freeaddrinfo(list);
      
          return fd;
      }
      

      数字端口和命名服务(如getent services 中所列)均可用于默认端口和端口参数。

      典型的服务会从一个(或多个)配置文件中读取这些内容。

      如果上述server_socket() 失败,它将返回-1,errno 近似于错误消息。 (如果getaddrinfo() 失败,它会尽可能地将错误映射到errno 常量,而不是使用gai_strerror()。) 如果成功,则返回绑定套接字的描述符号。

      您希望将这些编写为单独的函数的原因主要是懒惰和易于维护。你看,如果你把上面的(用必要的#includes等)写到一个单独的测试程序中,你可以单独测试这部分,作为一个单元:因此,unit testing它带有几个不同的参数,包括NULL指针和无效值。使用例如

      struct sockaddr_storage  addr;
      socklen_t                addrlen;
      const char              *node, *service;
      int                      fd;
      
      addrlen = sizeof addr;
      fd = server_socket(&addr, &addrlen, node, service);
      if (fd == -1) {
          fprintf(stderr, "Cannot create server socket: %s.\n", strerror(errno));
          exit(EXIT_FAILURE);
      }
      

      报告错误。请记住,测试不仅仅是检查函数在给定合理参数时是否有效;它还在测试如果参数本身是错误/错误/矛盾等会发生什么,并验证 that 也是可以接受的。

      这种分段测试节省时间和精力的原因是,错误发生时——无论你是多么优秀的程序员——它们都会发生——,只有一小部分代码(添加的部分还没有进行单元测试)和它们的整体交互逻辑(“更高级别”的逻辑),你需要检查和考虑找出错误。另外,您不需要浪费脑力来尝试同时记住代码的所有部分;你可以忘记单元测试的部分,只记住它们的一般用途。

      这也意味着您需要学习编写 cmet 来描述 您作为程序员的意图 是代码应该做什么,而不是代码做什么。后者从代码中很明显,但只有程序员知道前者;为了有效地发现错误,我们需要知道两者。 (经过二十多年的付费编程工作,我自己仍在学习这部分。)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-01-12
        • 2012-05-07
        • 1970-01-01
        • 2015-10-14
        • 2018-06-02
        • 1970-01-01
        • 2013-10-26
        • 1970-01-01
        相关资源
        最近更新 更多