【问题标题】:getaddrinfo, AI_PASSIVE - different behaviour windows <-> linuxgetaddrinfo, AI_PASSIVE - 不同的行为窗口 <-> linux
【发布时间】:2017-01-12 09:34:13
【问题描述】:

我已经修改了来自http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html(selectserver.c - 一个俗气的多人聊天服务器)的代码,以便在 Windows 上编译。完整的代码如下。我使用 gcc 6.1.0 版(x86_64-posix-seh,由 MinGW-W64 项目构建)进行编译。我也在 Linux 上使用 gcc6.1.0 进行编译。

基本上,您运行它,telnet 2 次或更多次到端口 9034,然后您在一个 telnet 会话中输入的任何内容都会回显到其他 telnet 会话(取决于系统,必须键入 Enter 在回显之前 - 在 Windows 上,它会回显输入的每个字符)。

现在的问题:

在 Linux AMD64 或 ARM 上,我可以从 localhost 和另一个系统(无论是 Windows 还是 Linux)连接到它。在 Windows 上,它只适用于 localhost,我不明白为什么。如果我理解正确的话,指定hints.ai_flags = AI_PASSIVE; 的事实使它可以监听所有接口。

MSDN doc 声明:

设置 AI_PASSIVE 标志表明调用者打算在调用绑定函数时使用返回的套接字地址结构。

当设置 AI_PASSIVE 标志且 pNodeName 为 NULL 指针时,套接字地址结构的 IP 地址部分设置为 IPv4 地址的 INADDR_ANY 和 IPv6 地址的 IN6ADDR_ANY_INIT。

代码如下:

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

如何使它在 Windows 上正常运行?

它是用 : 编译的:

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

并与

链接

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

请问我需要在代码中更改什么?

这是完整的代码:

#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;
}

【问题讨论】:

  • 你记得打开防火墙上的端口吗?
  • 我做到了。也禁用了 firewall.cpl 中的所有内容

标签: c++ linux windows sockets


【解决方案1】:

getaddrinfo() 可以返回多个 IP 地址。您正确地循环了所有返回的地址,但是在第一个成功的bind() 之后打破了循环,然后您在该单个套接字上调用listen(),无论其套接字系列如何。由于您在调用 getaddrinfo() 时使用的是 AF_UNSPEC,因此它可能会返回 BOTH INADDR_ANY 用于 IPv4 AND IN6ADDR_ANY_INIT 用于 IPv6。

更改您的代码以侦听 getaddrinfo() 返回的每个 IP 地址,并跟踪这些套接字,以便您可以在 select() 循环中使用所有这些套接字。如果您只想收听INADDR_ANYIN6ADDR_ANY_INIT,则根本没有使用getaddrinfo() 的意义,因为您可以硬编码这两个地址的socket()/bind() 调用和完全摆脱循环。鉴于您提供的AI_PASSIVE 提示,以这种方式使用getaddrinfo() 的目的是让它决定您应该听什么。不要对其输出做出假设。

您也不能在 Windows 上使用 fdmax,因此您需要重新编写您的 select() 循环。 Windows 上的套接字不使用文件描述符,因此在调用FD_ISSET() 时不能简单地从0 &lt;= fdmax 循环,并且select() 的第一个参数也会被忽略。我建议不要将您的活动套接字描述符/句柄存储在主 fd_set 中。改用std::list 或其他合适的容器,然后在需要调用select() 时动态创建一个新的fd_set。这将更便于跨平台移植。

试试这样的:

#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;
}

如果您在支持双栈套接字的系统(如 Windows)上运行代码,您可以将 AF_UNSPEC 更改为 AF_INET6(或直接硬编码 socket()/bind() 而不使用 @987654351 @) 在IN6ADDR_ANY_INIT 上仅创建 IPv6 侦听器,然后禁用它们上的IPV6_V6ONLY 套接字选项。这将允许 IPv6 侦听套接字同时接受 IPv4 和 IPv6 客户端,从而减少您需要创建的侦听套接字的数量。

【讨论】:

  • 感谢您抽出宝贵的时间来回答-它有效-我会在几天内尝试吸收并扩展它:)。列表中有 3 个小错字 - a ;在 struct a 之后,在选择调用在 windows 和 else 之间拆分的 0 之后,以及缺少的 std::cerr :我无法纠正。通过这些修复,它可以在我的系统上编译。还没试过linux。
猜你喜欢
  • 2011-12-28
  • 1970-01-01
  • 1970-01-01
  • 2014-05-10
  • 2015-09-30
  • 2018-08-14
  • 2011-03-29
  • 2015-03-05
  • 1970-01-01
相关资源
最近更新 更多