2017-01-12 9 views
0

私は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を使ってコンパイルします。getaddrinfo、AI_PASSIVE - さまざまな動作ウィンドウ<-> linux

基本的に、あなたはシステムに応じて、1つは、それを取得する前にを入力します入力する必要があります(、ポート9034へのtelnet 2回以上、それを実行し、何でもあなたが1つのTelnetセッションを入力すると、他のTelnetセッションにエコーされますエコー - Windowsでは、入力されたすべての文字をエコーし​​ます)。今

問題:

のLinux AMD64またはARM上で、私はWindoesやLinuxということは、localhostと別のシステムからからそれに接続することができます。 Windowsでは、それはlocalhost上でのみ動作し、なぜ私は理解していません。 hints.ai_flags = AI_PASSIVE;が指定されているという事実は、私が正しく理解していれば、すべてのインターフェースでそれを聞きます。

MSDN doc状態:

AI_PASSIVEフラグを設定するには、発信者がbind関数の呼び出しで返されたソケットアドレス構造体を使用する予定を示しています。

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上で正しく動作するのですか?

それがコンパイルされています

グラム++ -O0 -g3 -Wall -c -fmessage長= 0 "SRC \ chatserver.o" ".. \ SRC \ chatserver.cpp" -o

ととリンク

グラム++ -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; 
} 
+0

ファイアウォールでポートを開くのを忘れましたか? –

+0

私ははいでした。 firewall.cplのすべてを無効にしました – ZoOl007

答えて

2

getaddrinfo()は、複数のIPアドレスを返すことができます。返されたすべてのアドレスを正しくループしていますが、最初に成功した後にループを破棄している場合は、bind()を呼び出し、ソケットファミリにかかわらずその単一ソケットでlisten()を呼び出しています。 getaddrinfo()を呼び出すときにAF_UNSPECを使用しているので、BOTH IPv4のためINADDR_ANYおよびIPv6のIN6ADDR_ANY_INITを返している可能性があります。

getaddrinfo()が返すすべてのIPアドレスで待機するようにコードを変更し、それらのソケットを追跡して、すべてselect()ループで使用できるようにします。 INADDR_ANYまたはIN6ADDR_ANY_INITを聞きたければ、socket()/bind()が2つのアドレスを呼び出すだけで、ループを完全に取り除くことができるので、getaddrinfo()の使用は全く意味がありません。このようにgetaddrinfo()を使用する目的は、入力したAI_PASSIVEのヒントを考慮して、何を聞くべきかを決定させることです。その出力については仮定しないでください。

Windowsでもfdmaxを使用することはできませんので、select()ループを書き直す必要があります。 Windowsのソケットはファイル記述子を使用しないので、FD_ISSET()を呼び出すときに0 <= fdmaxからループすることはできません。また、最初のパラメータselect()も無視されます。私はあなたのアクティブなソケット記述子/ハンドルをマスターに格納しないことをお勧めします。fd_setまずは。 std::listまたはその他の適切なコンテナを代わりに使用し、select()に電話する必要があるときはいつでも、新しいfd_setを動的に作成します。これは、異なるプラットフォーム間で移植性が向上します。

より、このような何か試してください:あなたが(Windowsのような)デュアルスタックソケットをサポートするシステム上でコードを実行している場合、あなたはAF_INET6AF_UNSPECを変更することができます(あるいは単にハードコードsocket()

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

を/ bind()getaddrinfo()を使用しないで)IN6ADDR_ANY_INITにIPv6リスナーのみを作成してから、IPV6_V6ONLYソケットオプションを無効にします。これにより、IPv6リッスンソケットがIPv4クライアントとIPv6クライアントの両方を受け入れて、作成する必要があるリッスンソケットの数を減らすことができます。

+0

答える時間をとっていただきありがとうございます - それは動作します - 私は数日以内にそれを吸収し、拡張しようとします:)。リストには3つの小さなタイプミスがあります。構造体aの後、選択呼び出しがウィンドウ間で分割されていて0の後にstd :: cerrがありません:それを修正できません。これらを修正すると、私のシステム上でコンパイルされます。まだLinuxを試していない。 – ZoOl007

+0

@ ZoO007私はタイプミスを修正しました。 –

関連する問題