IP地址的可移植紧凑表示

Portable compact representation of IP address

本文关键字:表示 可移植 地址 IP      更新时间:2023-10-16

我有一个在Linux上使用Berkley sockets API的C++程序。我有一个连接的一端发送两个IP地址到客户端。我可以使用inet_ntop()inet_pton()来表示这些,但这将使消息长度为2*INET6_ADDRSTRLEN,即92字节。对于两个IP地址来说,这似乎有点过分。是否有可移植的、紧凑的IP地址二进制表示(它必须同时适用于IPv4和IPv6)。

如果有地址信息,则发送.ai_addr.ai_addrlen

试试这两个程序:

send_sockaddr.cc:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <cstdio>
#include <cerrno>
#include <cstdlib>
int main (int ac, char **av) {
  if(ac != 3) {
    fprintf(stderr, "Usage: %s hostname portnumbern", *av);
    return 1;
  }
  struct addrinfo *res0;
  struct addrinfo hints = { AI_CANONNAME, 0, SOCK_DGRAM };
  int rc = getaddrinfo(av[1], av[2], &hints, &res0);
  if(rc) {
    fprintf(stderr, "%s/%s: %sn", av[1], av[2], gai_strerror(rc));
    return 1;
  }
  char *name = res0->ai_canonname;
  for(struct addrinfo *res = res0; res; res=res->ai_next) {
    fprintf(stderr, "%s: %04X/%04X/%04X ", name, res->ai_family, res->ai_socktype, res->ai_protocol);
    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if(fd < 0) {
      perror("socket");
      continue;
    }
    rc = connect(fd, res->ai_addr, res->ai_addrlen);
    if(rc < 0) {
      perror("connect");
      continue;
    }
    fprintf(stderr, "Connected (%d)n", fd);
    *(unsigned short*)res->ai_addr = htons(*(unsigned short*)res->ai_addr);
    rc = send(fd, res->ai_addr, res->ai_addrlen, 0);
    *(unsigned short*)res->ai_addr = ntohs(*(unsigned short*)res->ai_addr);
    if(rc < 0) {
      perror("send");
    }
    close(fd);
  }
  freeaddrinfo(res0);
}

listen_sockaddr.cc:

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <cstdio>
#include <cerrno>
#include <cstdlib>
#include <poll.h>
#include <vector>
#include <arpa/inet.h>
int main (int ac, char **av) {
  if(ac != 2) {
    fprintf(stderr, "Usage: %s portnumbern", *av);
    return 1;
  }
  struct addrinfo *res0;
  struct addrinfo hints = { 0, 0, SOCK_DGRAM };
  int rc = getaddrinfo(0, av[1], &hints, &res0);
  if(rc) {
    fprintf(stderr, "%s/%s: %sn", av[1], av[2], gai_strerror(rc));
    return 1;
  }
  char *name = res0->ai_canonname;
  std::vector<pollfd> fds;
  for(struct addrinfo *res = res0; res; res=res->ai_next) {
    fprintf(stderr, "%s: ", name);
    int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
    if(fd < 0) {
      perror("socket");
      continue;
    }
    rc = bind(fd, res->ai_addr, res->ai_addrlen);
    if(rc < 0) {
      perror("bind");
      continue;
    }
    fprintf(stderr, "Bound (%d)n", fd);
    fds.push_back(pollfd({fd, POLLIN}));
  }
  freeaddrinfo(res0);
  while( (rc = poll( &fds[0], fds.size(), -1)) > 0 ) {
    for(size_t i = 0; i < fds.size(); ++i) {
      pollfd& pfd = fds[i];
      if(!pfd.revents)
        continue;
      pfd.revents = 0;
      union {
        sockaddr s;
        sockaddr_in sin;
        sockaddr_in6 sin6;
      } u;
      rc = recv(pfd.fd, &u, sizeof u, 0);
      if(rc < 0) {
        perror("recv");
        continue;
      }
      fprintf(stderr, "Received %d bytesn", rc);
      char str[256];
      switch(ntohs(u.s.sa_family)) {
      case AF_INET:
        if(inet_ntop(AF_INET, &u.sin.sin_addr, str, sizeof str)) {
          fprintf(stderr, "AF_INET %sn", str);
        } else {
          fprintf(stderr, "AF_INET unknownn");
        }
        break;
      case AF_INET6:
        if(inet_ntop(AF_INET6, &u.sin6.sin6_addr, str, sizeof str)) {
          fprintf(stderr, "AF_INET6 %sn", str);
        } else {
          fprintf(stderr, "AF_INET6 unknownn");
        }
        break;
      default:
        fprintf(stderr, "UNKNOWNn");
        break;
      }
    }
  }
}

实际上,IP地址本身并不是数字,所以字节表示总是跟随Big Endian。至少我不知道有哪个平台有什么不同。它不是作为一个数字来处理的,而是作为4个字节来处理的。