【问题标题】:Comparing strings containing IPv4 addresses in C在 C 中比较包含 IPv4 地址的字符串
【发布时间】:2013-08-19 06:23:13
【问题描述】:

我有两个字符串 ip1 = "192.168.145.123"ip2 = "172.167.234.120"

我可以比较这两个字符串是否相等:

strncmp(ip1,ip2) == 0

但是如何我可以找到

if (ip1 > ip2) {
    ...
}

我的尝试

我可以使用 sscanf:

sscanf(ip1,"%d.%d.%d.%d",&s1,&s2,&s3,&s4) 

并存储数字并进行比较。 但是在 32 位中,由于上限,我无法将数字存储为整数。

因此我别无选择,只能将整数作为字符串进行比较。

【问题讨论】:

  • 你指的是什么“上限”? (如果是int 的符号,你有什么理由不使用unsigned int"%u" 来阅读它们)?
  • 一个 IPV4 地址可以完美地存储在一个 4 字节宽的无符号整数中。正如对 your 问题之一的回答 stackoverflow.com/a/18291062/694576 中所示。
  • @JoachimPileborg 非常确定他希望 192.168.1.1 比 10.0.0.1大于。将它们存储在 32 位 int 中不会这样做,但 unsigned int 会。
  • 为什么不按字典顺序将它们作为字符串进行比较呢? IP 地址没有有意义的排序; 172.167.234.120 的时间不早于 192.168.145.123,也不比东边或更快。排序的一种用途是在数据结构中管理它们。为此,任何排序都足够了,因此它也可能是您可以计算得最快的任何排序。只要您知道没有前导零,字符串比较就可以了。如果这不是目的,那为什么 IP 地址需要排序?
  • @EricPostpischil 当然在 IP 地址中有一个顺序,例如通过路由表中的网络部分。您可能想要 qsort() 一个 IP 地址列表以使用 bsearch() 来查看一个是否已经在表中。不要妄下结论,华生 :-)

标签: c ipv4


【解决方案1】:

还有inet_aton值得一提吗?

你可以找到手册页here,下面是一个简短的描述和一个简短的概要。

此解决方案适用于大多数 POSIX 系统,但我确信在 Windows API 中也有一些等价物,甚至还有一些抽象包装器。

inet_ntoa() 在 POSIX.1-2001 中指定。 inet_aton() 未在 POSIX.1-2001 中指定,但在大多数系统上都可用。


Linux 程序员手册

inet_aton() 将 Internet 主机地址 cp 从 IPv4 数字和点表示法转换为二进制形式(按网络字节顺序)并将其存储在 inp 指向的结构中。

概要

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);

示例

inet_aton() 和inet_ntoa() 的使用示例如下所示。以下是一些示例运行:

       $ ./a.out 226.000.000.037      # Last byte is in octal
       226.0.0.31
       $ ./a.out 0x7f.1               # First byte is in hex
       127.0.0.1

节目来源

   #define _BSD_SOURCE
   #include <arpa/inet.h>
   #include <stdio.h>
   #include <stdlib.h>

   int
   main(int argc, char *argv[])
   {
       struct in_addr addr;

       if (argc != 2) {
           fprintf(stderr, "%s <dotted-address>\n", argv[0]);
           exit(EXIT_FAILURE);
       }

       if (inet_aton(argv[1], &addr) == 0) {
           fprintf(stderr, "Invalid address\n");
           exit(EXIT_FAILURE);
       }

       printf("%s\n", inet_ntoa(addr));
       exit(EXIT_SUCCESS);
   }

更多信息

  • 字节排序 (@Jonathan Leffler)

    inet_ntoa() 函数将以网络字节顺序给出的 Internet 主机地址 in 转换为 IPv4 点分十进制表示法的字符串。 inet_aton() 将 Internet 主机地址 cp 从 IPv4 数字和点表示法转换为二进制形式(按网络字节顺序)并将其存储在 inp 指向的结构中。

  • in_addr (@POW) 的结构

    inet_ntoa()、inet_makeaddr()、inet_lnaof() 和 inet_netof() 中使用的结构 in_addr 定义如下:

       typedef uint32_t in_addr_t;
    
       struct in_addr {
           in_addr_t s_addr;
       };
    
  • 比较独立于计算机字节序的地址 in_addr 中的地址是网络字节顺序(大端序),因此正如@glglgl 所指出的,您必须使用ntohl,其手册页位于here

    ntohl() 函数将无符号整数 netlong 从网络字节顺序转换为主机字节顺序。

    uint32_t ntohl(uint32_t netlong);
    

【讨论】:

  • 这是否在 big-endian 和 little-endian 机器上都给出了正确的顺序?网络排序不一定是本地排序,所以我怀疑它可能会混淆。如果它工作正常,那么这是一个很好的答案。
  • @JonathanLeffler 你会得到一个带有inet_aton() 的“内存转储”,它按照网络字节顺序排列。那么ntohl() 会给你正确的顺序。
  • 这是一个很好的答案,但不确定这个答案是否“我怎样才能找到if (ip1 &gt; ip2) { ...}
  • @JonathanLeffler,编辑了我的帖子并添加了一个引用,应该可以回答您的问题。
  • 感谢您的更新。我担心inet_aton() 将字符串转换为网络顺序地址,但网络顺序(大端)在英特尔(小端)机器上无法准确比较。我认为@glglgl 或多或少同意我的看法。要在机器上获得可比较的结果,您必须将网络顺序值转换为主机顺序,例如使用ntohl()。然后你可以与原生值进行比较。
【解决方案2】:

你可以试试性感的方式,将所有值存储在一个无符号整数中并进行比较。

  const char* ip1 = "192.168.145.123";
  const char* ip2 = "172.167.234.120";

  unsigned char s1, s2, s3, s4;
  unsigned int uip1, uip2;

  sscanf(ip1,"%hhu.%hhu.%hhu.%hhu",&s1,&s2,&s3,&s4);
  uip1 = (s1<<24) | (s2<<16) | (s3<<8) | s4; //store all values in 32bits unsigned int

  sscanf(ip2,"%hhu.%hhu.%hhu.%hhu",&s1,&s2,&s3,&s4);
  uip2 = (s1<<24) | (s2<<16) | (s3<<8) | s4;

  if (uip1 > uip2)
  {
    printf("ip1 greater !");   
  }
  else
  {
    printf("ip2 greater or equal !");     
  }

【讨论】:

  • 不应该调用sscanf() 使用%hhu吗?
  • 不要sscanf()改用strtoul()
  • @H2CO3:为什么不使用sscanf()?与sscanf() 相比,使用strtoul() 解决了哪些问题?测试strtoul() 的结果是一项非常微妙的工作,尽管由于该数据的值应该在 0..255 范围内,因此它应该比一般的“任何值都有效”的情况更容易验证。
  • @H2CO3 为什么?使用 sscanf 是完全可以的(但应该测试返回值)。 sscanf 四个数字比使用 strtok 或其他查找点要容易得多。
  • 所以这是教育正确使用sscanf()的原因;这不是要求使用strtoul() 的理由,它必须非常小心地处理(比sscanf() 更小心)才能正确检测所有错误情况。诚然,strtoul() 将检测到 sscanf() 没有检测到的错误(如溢出)——但“不要使用”的笼统说法太笼统了;它需要更细致入微的回应。
【解决方案3】:

这个怎么样:-

#include<stdio.h>
#include<conio.h>

unsigned int convIP(const char ip[]) {
    unsigned char s1, s2, s3, s4;

    if (sscanf(ip, "%hhu.%hhu.%hhu.%hhu", &s1, &s2, &s3, &s4) != 4)
        return 0;

    /* Create a 32 bit Integer using left shift & bitwise OR
            MSB                                            LSB
            +-----8----+-----8------+-----8-----+----8-----+
            |    s1    |     s2     |    s3     |    s4    |   
            +----------+------------+-----------+----------+
     */
    return  (s1 << 24) | (s2 << 16) | (s3 << 8) | (s4 << 0);

}

int ipComp(const char ip1[], const char ip2[]) {
    unsigned int ip_addr1 = convIP(ip1);
    unsigned int ip_addr2 = convIP(ip2);

    return (ip_addr1 >= ip_addr2);

}


int main()
{

    printf("%d\n",ipComp("192.168.145.123","172.167.234.120") ); //1

    printf("%d\n", ipComp("10.0.0.1","192.168.1.1") );  //0

    printf("%d\n",ipComp("192.168.145.123","192.168.145.123")); //1
}

编辑:按照 H2CO3 的建议:

您通常应该避免使用sscanf,而是可以使用strtol(),如下所示:

unsigned long ip2int(const char *ip)
{
    const char *end = ip + strlen(ip);
    unsigned long n = 0;
    while (ip < end) {
        n <<= 8;
        n |= strtoul(ip, (char **)&ip, 10);
        ip++;
    }

    return n;
}

【讨论】:

  • 不应该调用sscanf() 使用%hhu吗?
  • @alk 同意,我正在使用 MINGW,%hhu 使用-Wall 发出警告。与this 相同。我还是更新了
  • @DeepakTivari:关于&lt;&lt;:阅读“左移”运算符;关于|,请阅读“按位或”运算符。
  • @DeepakTivari - 该操作将四个独立的整数打包成一个无符号整数。其中三个值必须移动到更高位,这就是移位运算符 (
  • sscanf(ip, "%hhu.%hhu.%hhu.%hhu" 不正确 for s1 是类型 int,而不是类型 unsigned char。建议改用sscanf(ip, "%hhu.%hhu.%hhu.%hhu" 并输入unsigned char 来表示s1 或使用sscanf(ip, "%u.%u.%u.%u" 并输入unsigned 来表示s1
【解决方案4】:

一个迂腐的“在接受的答案之后”的答案。 错误检查

#include <inttypes.h>
int IPstringToUns32(const char *IPString, uint32_t *IPNumber) {
  uint8_t c[4];  // LSByte in c[0]
  char ch;
  const char * format = "%" SCNu8 ".%" SCNu8 ".%" SCNu8 ".%" SCNu8 "%c";
  if (4 != sscanf(IPString, format, &c[3], &c[2], &c[1], &c[0], &ch)) {
    return 1; // parse error
  }
  *IPNumber = (((uint32_t) c[3]) << 24) | (((uint32_t) c[2]) << 16)
      | (((uint32_t) c[1]) << 8) | ((uint32_t) c[0]);
  return 0;
}

假设可以使用uint_fast32_t。此解决方案允许在数字前使用前导空格。

[编辑] 在格式末尾添加经典 %c。感谢@glglgl。

【讨论】:

  • 这里使用数组没有意义。当然可以,但它没有e的好处。 G。 uint8_t c1, c2, c3, c4.
  • glglgl 扫描计数 != 4 未显示。索引数组允许在那里循环处理未设置的值。从 1 而不是惯用的 0 开始变量 ID 很有趣。我发现在 C 语言中从 0 开始比 1 更一致。我将 "((uint32_t) c[3])
  • 只是一个建议。最后,我认为这是个人风格的问题。
  • @glglgl 同意风格。顺便说一句:在断言不存在的确定性“使用......没有意义”时,我经常有点过于频繁。 ;-)
猜你喜欢
  • 2016-04-22
  • 1970-01-01
  • 2021-08-30
  • 2020-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-29
相关资源
最近更新 更多