getaddrinfo,AI_PASSIVE - 不同的行为窗口< - >的Linux

时间:2017-01-12 09:34:13

标签: c++ linux windows sockets

我已经调整了来自http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html(selectserver.c - 一个cheezy multiperson聊天服务器)的代码,以便在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和其他系统连接到它,无论是Windoes还是Linux。在Windows上,它只适用于localhost,我不明白为什么。如果我正确理解了事情,那么指定hints.ai_flags = AI_PASSIVE;的事实使得它可以监听所有接口。

MSDN doc州:

  

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

     

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

代码为:

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

1 个答案:

答案 0 :(得分:3)

getaddrinfo()可以返回多个IP地址。您正在循环遍历所有返回的地址,但是您在第一次成功bind()之后断开循环,然后在该单个套接字上调用listen(),而不管其套接字系列如何。由于您在调用AF_UNSPEC时使用的是getaddrinfo(),因此可能会为IPv4 AND {{1}返回 BOTH INADDR_ANY对于IPv6。

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

您也无法在Windows上使用getaddrinfo(),因此您需要重新编写AI_PASSIVE循环。 Windows上的套接字不使用文件描述符,因此您无法在调用fdmax时从select()循环,并且也会忽略0 <= fdmax的第一个参数。我建议不要在主FD_ISSET()中存储您的活动套接字描述符/句柄。请改用select()或其他合适的容器,然后在需要致电fd_set时动态创建新的std::list。这将在不同平台上更加便携。

尝试更像这样的事情:

fd_set

如果您在支持双栈套接字的系统(如Windows)上运行代码,则可以将select()更改为#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; } (或者只需硬编码AF_UNSPEC / { {1}}不使用AF_INET6)在socket()上仅创建IPv6侦听器,然后在其上禁用bind()套接字选项。这将允许IPv6侦听套接字接受IPv4和IPv6客户端,从而减少您需要创建的侦听套接字的数量。