服务器套接字的IP和端口错误

时间:2015-08-21 17:15:04

标签: c++ c linux sockets gcc

当我编写如下代码时,报告的对等IP和端口是正确的:

int main(){
    int server = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in addr;    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(server,(struct sockaddr *)&addr,sizeof addr);
    listen(server,5);
    struct sockaddr_storage inc_adrs;
    socklen_t inc_len;
    int client = accept(server, (struct sockaddr *) &inc_adrs,&inc_len);
    struct sockaddr_in *inc_adr = (struct sockaddr_in *) &inc_adrs;    
    char ip [INET_ADDRSTRLEN];
    inet_ntop(AF_INET,&(inc_adr->sin_addr),ip,INET_ADDRSTRLEN);
    printf("Connection from %s port %d\n",ip,ntohs(inc_adr->sin_port));
    return 0;
}

例如,我在本地计算机上使用nc连接到它:

#nc 127.0.0.1 8080

输出是:

Connection from 127.0.0.1 port 55112

但是,如果我只是将它们放在另一个块中,如“if”或“while”程序报告错误的ip和端口:

int main(){
    if (1){
        int server = socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in addr;    
        addr.sin_family = AF_INET;
        addr.sin_port = htons(8080);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        bind(server,(struct sockaddr *)&addr,sizeof addr);
        listen(server,5);
        struct sockaddr_storage inc_adrs;
        socklen_t inc_len;
        int client = accept(server, (struct sockaddr *) &inc_adrs,&inc_len);
        struct sockaddr_in *inc_adr = (struct sockaddr_in *) &inc_adrs;    
        char ip [INET_ADDRSTRLEN];
        inet_ntop(AF_INET,&(inc_adr->sin_addr),ip,INET_ADDRSTRLEN);
        printf("Connection from %s port %d\n",ip,ntohs(inc_adr->sin_port));
        return 0;
    }
}

示例输出:

Connection from 253.127.0.0 port 32323

我在linux x64上用“gcc code.c -o code”编译它。 这很奇怪,我不知道它是怎么可能的。有什么想法吗?

1 个答案:

答案 0 :(得分:2)

好的,看起来问题在于你如何使用man 2 accept

  

addrlen参数是一个value-result参数:调用者必须初始化它以包含addr指向的结构的大小(以字节为单位);返回时它将包含对等地址的实际大小。

     

如果提供的缓冲区太小,则会截断返回的地址;在这种情况下,addrlen将返回一个大于提供给调用的值。

通过以下修复 SO_REUSEADDR,您的代码可以正常运行:

    struct sockaddr_storage inc_adrs;
    socklen_t inc_len = sizeof( inc_adrs );

此外,请不要忘记检查socketbindlistenaccept的结果值。

./a.out
Connection from 127.0.0.1 port 11111
---
nc 127.0.0.1 8080 -p 11111

Connection from 192.168.0.1 port 11111
---
nc 192.168.0.1 8080 -p 11111

您可能struct sockaddr_storage inc_adrs;之后使用accept(取决于inc_len中的随机值):

  • 将完整值复制到inc_adrs
  • 将不完整的值复制到inc_adrs
  • 如果inc_len碰巧为零,
  • 根本不会触及inc_adrs。

例如,在我的情况下,if inc_len为零。 inc_adrs仍未受影响并且包含一些垃圾:

inc_adrs = {ss_family = 0, __ss_align = 234832925920,
            __ss_padding = "@\300\377\377\377\177\000\000G\004I\255\066\000\000\000X...

这种垃圾取决于编译器如何在堆栈上分配值以及之前的代码(包括glibc)将对堆栈执行的操作。

例如,没有if的相同值:

1: inc_len = 54 <<--- this one is unitialized!!!!
2: inc_adrs = {ss_family = 49240, __ss_align = 4294967296,
             __ss_padding = "\000\000\000\000\000\000\000\000\034\00

你可以看到inc_len恰好是54,这足以让accept保存sockaddr_in,因此这种情况好像一切都好。

ifnot-if个案之间的差异:

-       leaq    -52(%rbp), %rdx
-       leaq    -208(%rbp), %rcx
+       leaq    -180(%rbp), %rdx
+       leaq    -176(%rbp), %rcx
        movl    -4(%rbp), %eax
        movq    %rcx, %rsi
        movl    %eax, %edi
        call    accept

所以它只是偶然地正常工作。您可以使用random模拟相同的内容:

#include <stdlib.h>

srand(time(NULL));
...
socklen_t inc_len = random() % (sizeof(struct sockaddr_in)*2)

这将填补inc_adrs 50%的时间,5%不会触及它。

相关问题