简体   繁体   English

getaddrinfo,AI_PASSIVE-不同行为的Windows <-> linux

[英]getaddrinfo, AI_PASSIVE - different behaviour windows <-> linux

I have adapted the code from http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html (selectserver.c -- a cheezy multiperson chat server) to compile on Windows. 我改编了http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html(selectserver.c-活泼的多人聊天服务器)中的代码,以在Windows上进行编译。 The complete code follows below. 完整的代码如下。 I compile using gcc version 6.1.0 (x86_64-posix-seh, Built by MinGW-W64 project). 我使用gcc版本6.1.0(x86_64-posix-seh,由MinGW-W64项目构建)进行编译。 I compile using gcc6.1.0 on Linux, too. 我也在Linux上使用gcc6.1.0进行编译。

Basically, you run it, telnet 2 or more times to port 9034, and whatever you type in one telnet session gets echoed to the other telnet sessions (depending on the system, one has to type Enter before it gets echoed - on Windows it echoes every character typed). 基本上,你运行它,远程登录2次或更多次到端口9034,不管你在一个telnet会话中输入被回显到其他的telnet会话(根据系统的不同,人们必须键入回车它得到回应之前-在Windows呼应每个字符)。

Now the problem : 现在的问题是:

On Linux AMD64 or ARM, I can connect to it from localhost and from another system, be that Windoes or Linux. 在Linux AMD64或ARM上,我可以从localhost和另一个系统(即Windoes或Linux)连接到它。 On Windows, it only works on localhost, and I fail to understand why. 在Windows上,它只能在localhost上运行,而我不明白为什么。 The fact that hints.ai_flags = AI_PASSIVE; hints.ai_flags = AI_PASSIVE;的事实hints.ai_flags = AI_PASSIVE; is specified makes it listen on all interfaces, if I understand things correctly. 如果我理解正确的话,指定它会使它在所有接口上侦听。

The MSDN doc states: MSDN文档指出:

Setting the AI_PASSIVE flag indicates the caller intends to use the returned socket address structure in a call to the bind function. 设置AI_PASSIVE标志指示调用者打算在对bind函数的调用中使用返回的套接字地址结构。

When the AI_PASSIVE flag is set and pNodeName is a NULL pointer, the IP address portion of the socket address structure is set to INADDR_ANY for IPv4 addresses and IN6ADDR_ANY_INIT for IPv6 addresses. 当设置AI_PASSIVE标志并且pNodeName是NULL指针时,套接字地址结构的IP地址部分对于IPv4地址设置为INADDR_ANY,对于IPv6地址设置为IN6ADDR_ANY_INIT。

The code reads : 该代码显示为:

hints.ai_flags = AI_PASSIVE;
if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)

How do I make this behave correctly on Windows? 如何使它在Windows上正确运行?

It is compiled with : 它与编译:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -o "src\\chatserver.o" "..\\src\\chatserver.cpp" g ++ -O0 -g3 -Wall -c -fmessage-length = 0 -o“ src \\ chatserver.o”“ .. \\ src \\ chatserver.cpp”

and linked with 并与

g++ -mwindows -o chatserver.exe "src\\chatserver.o" -lws2_32 g ++ -mwindows -o chatserver.exe“ src \\ chatserver.o” -lws2_32

What do I need to change in the code please? 我需要更改代码中的什么?

This is the complete code: 这是完整的代码:

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

#ifdef __linux__
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
#endif

#ifdef _WIN32
#include <ws2tcpip.h>

#endif

#define PORT "9034" // port we're listening on
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) { return &(((struct sockaddr_in*)sa)->sin_addr); }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
int main(void)
{
    #ifdef _WIN32
        WSADATA wsaData;                                                        // Initialize Winsock
        int nResult = WSAStartup(MAKEWORD(2,2), &wsaData);
        if (NO_ERROR != nResult) {
             printf ("Error occurred while executing WSAStartup().");
        }
    #endif

    fd_set master; // master file descriptor list
    fd_set read_fds; // temp file descriptor list for select()
    int fdmax; // maximum file descriptor number
    int listener; // listening socket descriptor
    int newfd; // newly accept()ed socket descriptor
    struct sockaddr_storage remoteaddr; // client address
    socklen_t addrlen;
    char buf[256]; // buffer for client data
    int nbytes;

    char remoteIP[INET6_ADDRSTRLEN];
    int yes=1; // for setsockopt() SO_REUSEADDR, below
    int i, j, rv;
    struct addrinfo hints, *ai, *p;
    FD_ZERO(&master); // clear the master and temp sets
    FD_ZERO(&read_fds);
    // get us a socket and bind it
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0) {
        fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
        exit(1);
    }
    for(p = ai; p != NULL; p = p->ai_next) {
        listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (listener < 0) { continue; }
        // lose the pesky "address already in use" error message
        setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(int));
        //setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, "1", sizeof(int));
        if (bind(listener, p->ai_addr, p->ai_addrlen) < 0) {
        close(listener);
        continue;
        }
        break;
    }
    // if we got here, it means we didn't get bound
    if (p == NULL) {
        fprintf(stderr, "selectserver: failed to bind\n");
        exit(2);
    }
    freeaddrinfo(ai); // all done with this
    // listen
    if (listen(listener, 10) == -1) {
        perror("listen");
        exit(3);
    }
    // add the listener to the master set
    FD_SET(listener, &master);
    // keep track of the biggest file descriptor
    fdmax = listener; // so far, it's this one
    // main loop
    for(;;) {
    read_fds = master; // copy it
    if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
    perror("select");
    exit(4);
    }
    // run through the existing connections looking for data to read
    for(i = 0; i <= fdmax; i++) {
        if (FD_ISSET(i, &read_fds)) { // we got one!!
        if (i == listener) {
            // handle new connections
            addrlen = sizeof remoteaddr;
            newfd = accept(listener,
            (struct sockaddr *)&remoteaddr,
            &addrlen);
            if (newfd == -1) {
            perror("accept");
            }
            else {
                FD_SET(newfd, &master); // add to master set
                if (newfd > fdmax) { // keep track of the max
                    fdmax = newfd;
                }
                std::cout << "selectserver: new connection on socket " << newfd;
                /*
                printf("selectserver: new connection from %s on "
                "socket %d\n",
                inet_ntop(remoteaddr.ss_family,get_in_addr((struct sockaddr*)&remoteaddr),remoteIP, INET6_ADDRSTRLEN),newfd);
                */
            }
        }
        else {
            // handle data from a client
            if ((nbytes = recv(i, buf, sizeof buf, 0)) <= 0) {
                // got error or connection closed by client
                if (nbytes == 0) {
                    // connection closed
                    std::cout << "selectserver: socket " << i << " hung up";
                }
                else {
                    perror("recv");
                }
                close(i); // bye!
                FD_CLR(i, &master); // remove from master set
            }
            else {
            // we got some data from a client
            for(j = 0; j <= fdmax; j++) {
                // send to everyone!
                if (FD_ISSET(j, &master)) {
                    // except the listener and ourselves
                    if (j != listener && j != i) {
                    if (send(j, buf, nbytes, 0) == -1) {
                        perror("send");
                    }
                    }
                }
            }
            }
        } // END handle data from client
        } // END got new incoming connection
        } // END looping through file descriptors
    } // END for(;;)--and you thought it would never end!
    return 0;
}

getaddrinfo() can return multiple IP addresses. getaddrinfo()可以返回多个IP地址。 You are correctly looping through all of the returned addresses, but you are breaking the loop after the first successful bind() , and then you are calling listen() on that one single socket, regardless of its socket family. 您正确地循环了所有返回的地址,但是在第一个成功的bind()之后中断了循环,然后在那个单个套接字上调用listen() ,而不管其套接字家族如何。 Since you are using AF_UNSPEC when calling getaddrinfo() , it is possible that it is returning BOTH INADDR_ANY for IPv4 AND IN6ADDR_ANY_INIT for IPv6. 由于您使用AF_UNSPEC时调用getaddrinfo()它是可能的它返回BOTH INADDR_ANY IPv4 IN6ADDR_ANY_INIT使用IPv6。

Change your code to listen on every IP address that getaddrinfo() returns, and to keep track of those sockets so you can use all of them in your select() loop. 更改代码以侦听getaddrinfo()返回的每个IP地址,并跟踪这些套接字,以便可以在select()循环中使用所有套接字。 If you just wanted to listen on either INADDR_ANY or IN6ADDR_ANY_INIT , there would be no point in using getaddrinfo() at all, as you could just hard-code the socket() / bind() calls for those two addresses and get rid of the loop altogether. 如果您只想监听INADDR_ANYIN6ADDR_ANY_INIT ,那么根本就没有使用getaddrinfo()的意义,因为您可以硬编码这两个地址的socket() / bind()调用并摆脱掉完全循环。 The purpose of using getaddrinfo() in this manner is to let it decide what you should be listening on, given the AI_PASSIVE hint you provided. 以这种方式使用getaddrinfo()的目的是,根据提供的AI_PASSIVE提示,让它决定您应该听的内容。 Don't make assumptions about its output. 不要对它的输出做任何假设。

You also cannot use fdmax on Windows, so you need to re-write your select() loop. 您也不能在Windows上使用fdmax ,因此您需要重新编写select()循环。 Sockets on Windows do not use file descriptors, so you can't simply loop from 0 <= fdmax when calling FD_ISSET() , and the first parameter of select() is ignored as well. Windows Sockets的不使用的文件描述符,所以你不能从简单的循环0 <= fdmax时调用FD_ISSET()和第一个参数select()被忽略为好。 I suggest not storing your active socket descriptors/handles in a master fd_set to begin with. 我建议不要将活动套接字描述符/句柄存储在主fd_set中。 Use a std::list or other suitable container instead, and then dynamically create a new fd_set whenever you need to call select() . 请改用std::list或其他合适的容器,然后在需要调用select()时动态创建一个新的fd_set This would be more portable across different platforms. 这将在不同平台之间更加可移植。

Try something more like this: 尝试更多类似这样的方法:

#include <unistd.h>
#include <sys/types.h>

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

#define SOCKET int
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1

inline int closesocket(int s) { return close(s); }
inline int getLastSocketError() { return errno; }
#endif

#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>

inline int getLastSocketError() { return WSAGetLastError(); }
#endif

#include <iostream>
#include <list>
#include <algorithm> 
#include <utility> 

#define PORT "9034" // port we're listening on

#ifdef _WIN32
#define SELECT_MAXFD 0
#else
#define SELECT_MAXFD fdmax+1
#endif

enum eSocketType { stListener, stClient };

struct SocketInfo
{
    SOCKET sckt;
    eSocketType type;
};

SocketInfo makeSocketInfo(SOCKET sckt, eSocketType type) {
    SocketInfo info;
    info.sckt = sckt;
    info.type = type;
    return info;
}

// get sockaddr, IPv4 or IPv6:
void* get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) {
        return &(((struct sockaddr_in*)sa)->sin_addr);
    }
    return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
    std::list<SocketInfo> master; // socket descriptors
    std::list<SocketInfo>::iterator i, j;
    SOCKET sckt, newsckt; // socket descriptors

    fd_set read_fds; // temp file descriptor list for select()
    #ifndef _WIN32
    int fdmax; // maximum file descriptor number
    #endif

    struct sockaddr_storage remoteaddr; // client address
    socklen_t addrlen;
    char buf[256]; // buffer for client data
    int nbytes;

    char ipAddr[INET6_ADDRSTRLEN];
    int yes = 1; // for setsockopt() SO_REUSEADDR, below
    int rv;
    struct addrinfo hints, *ai, *p;

    #ifdef _WIN32
    WSADATA wsaData; // Initialize Winsock
    rv = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (NO_ERROR != rv) {
        std::cerr << "WSA startup failed, error: " << rv << std::endl;
        return 1;
    }
    #endif

    // get us the listening sockets and bind them

    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    rv = getaddrinfo(NULL, PORT, &hints, &ai);
    if (rv != 0) {
        std::cerr << "selectserver: getaddrinfo failed, error: " << gai_strerror(rv) << std::endl;
        return 2;
    }

    for(p = ai; p != NULL; p = p->ai_next) {
        sckt = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
        if (INVALID_SOCKET == sckt) {
            std::cerr << "selectserver: socket failed, error: " << getLastSocketError() << std::endl;
            continue;
        }

        // lose the pesky "address already in use" error message
        setsockopt(sckt, SOL_SOCKET, SO_REUSEADDR, (const char *)&yes, sizeof(int));
        //setsockopt(sckt, SOL_SOCKET, SO_REUSEADDR, "1", sizeof(int));
        if (bind(sckt, p->ai_addr, p->ai_addrlen) < 0) {
            std::cerr << "selectserver: bind failed, error: " << getLastSocketError() << std::endl;
            closesocket(sckt);
            continue;
        }

        // listen
        if (listen(sckt, 10) < 0) {
            std::cerr << "selectserver: listen failed, error: " << getLastSocketError() << std::endl;
            closesocket(sckt);
            continue;
        }

        /*
        std::cout << "selectserver: listening on IP " << inet_ntop(p->ai_family, get_in_addr(p->ai_addr), ipAddr, sizeof(ipAddr)) << ", socket " << sckt << std::endl,
        */

        // add the listener to the master list
        master.push_back(makeSocketInfo(sckt, stListener));
    }

    freeaddrinfo(ai); // all done with this

    // if we got here, it means we didn't get bound
    if (master.empty()) {
        std::cerr << "selectserver: failed to bind" << std::endl;
        return 3;
    }

    // main loop
    while (1) {
        #ifndef _WIN32
        fdmax = 0;
        #endif

        FD_ZERO(&read_fds);
        for (i = master.begin(); i != master.end(); ++i) {
            sckt = i->sckt;
            FD_SET(sckt, &read_fds);
            #ifndef _WIN32
            fdmax = std::max(fdmax, sckt);
            #endif
        }

        if (select(SELECT_MAXFD, &read_fds, NULL, NULL, NULL) < 0) {
            std::cerr << "select failed, error: " << getLastSocketError() << std::endl;
            return 4;
        }

        // run through the existing connections looking for data to read

        for(i = master.begin(); i != master.end(); ) {
            sckt = i->sckt;

            if (!FD_ISSET(sckt, &read_fds)) {
                ++i;
                continue;
            }

            // we got one!!
            if (stListener == i->type) {
                // handle a new connection
                addrlen = sizeof(remoteaddr);
                newsckt = accept(sckt, (struct sockaddr *)&remoteaddr, &addrlen);
                if (INVALID_SOCKET == newsckt) {
                    std::cerr << "accept failed on socket " << sckt << ", error: " << getLastSocketError() << std::endl;
                }
                else {
                    master.push_back(makeSocketInfo(newsckt, stClient)); // add to master list

                    std::cout << "selectserver: new connection, socket " << newsckt << std::endl;
                    /*
                    std::cout << "selectserver: new connection from " << inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), ipAddr, sizeof(ipAddr)) << ", socket " << newsckt << std::endl,
                    */
                }
            }
            else {
                // handle data from a client
                nbytes = recv(sckt, buf, sizeof(buf), 0);
                if (nbytes <= 0) {
                    // got error or connection closed by client
                    if (nbytes == 0) {
                        // connection closed
                        std::cout << "selectserver: socket " << sckt << " disconnected" << std::endl;
                    }
                    else {
                        std::cerr << "selectserver: recv failed on socket " << sckt << ", error: " << getLastSocketError() << std::endl;
                    }
                    closesocket(sckt); // bye!
                    i = master.erase(i); // remove from master list
                    continue;
                }

                // send to everyone!
                // except a listener and ourselves

                for(j = master.begin(); j != master.end(); ) {
                    if ((j->sckt != sckt) && (stClient == j->type)) {
                        if (send(j->sckt, buf, nbytes, 0) < 0) {
                            std::cerr << "selectserver: send failed on socket " << j->sckt << ", error: " << getLastSocketError() << std::endl;
                            closesocket(j->sckt); // bye!
                            j = master.erase(j); // remove from master list
                            continue;
                        }
                    }
                    ++j;
                }
            }
            ++i;
        }
    }

    for(i = master.begin(); i != master.end(); ++i) {
        closesocket(i->sckt);
    }

    #ifdef _WIN32
    WSACleanup();
    #endif

    return 0;
}

If you are running the code on a system that supports dual-stack sockets (like Windows), you can change AF_UNSPEC to AF_INET6 (or just hard-code socket() / bind() without using getaddrinfo() ) to create only IPv6 listener(s) on IN6ADDR_ANY_INIT , and then disable the IPV6_V6ONLY socket option on them. 如果在支持双栈套接字的系统(例如Windows)上运行代码,则可以将AF_UNSPEC更改为AF_INET6 (或仅将硬代码socket() / bind()更改为不使用getaddrinfo() )以仅创建IPv6侦听器(s)放在IN6ADDR_ANY_INIT ,然后在它们上禁用IPV6_V6ONLY套接字选项。 This will allow IPv6 listen sockets to accept both IPv4 and IPv6 clients, reducing the number of listen sockets you need to create. 这将允许IPv6侦听套接字接受IPv4和IPv6客户端,从而减少了需要创建的侦听套接字的数量。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM