В сетях C ++, используя select, мне сначала нужно слушать () и принять ()? - PullRequest
7 голосов
/ 18 февраля 2010

Я пытаюсь разрешить нескольким клиентам подключаться к хосту с помощью select. Должен ли я подключить каждый из них, попросить их перейти на другой порт, а затем повторно подключиться к новому порту? Или разрешите мне подключать несколько клиентов к одному и тому же порту?

Это код клиента:

    int rv;
int sockfd, numbytes;

if ((rv = getaddrinfo(hostName, hostPort, &hints, &servinfo)) != 0) {
    cout << "Could not get server address.\n";
    exit(1);
}

// loop through all the results and connect to the first we can
for(p = servinfo; p != NULL; p = p->ai_next) {
    if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) == -1) {
        perror("Client: no socket");
        continue;
    }

    if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
        close(sockfd);
        perror("Client: connect");
        continue;
    }

    break;
}

if (p == NULL) {
    fprintf(stderr, "Unable to connect to server.\n");
    exit(2);
}

FD_SET(sockfd, &masterSet);

Это код сервера:

        int rv = getaddrinfo(NULL, port, &hints, &res);
    int yes = 1;//Not sure what this is for, found it in Beej's
    if(rv != 0){
            cout<< "Error, nothing matches criteria for file descriptor.\n";
            exit(1);
    }
    int fdInit;
    for(temp = res; temp != NULL; temp = temp->ai_next){
            if((fdInit = socket(temp->ai_family, temp->ai_socktype, temp->ai_protocol)) == -1){
                    cout << "This is not the fd you're looking for.  Move along.\n";
                    continue; //This is not the fd you're looking for, move along.
            }

            if(setsockopt(fdInit, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
                    cout << "Doom has fallen upon this set socket.\n";
                    perror("setsockopt");
                    exit(1); //Unable to set socket, exit program with code 1
            }

            if(bind(fdInit, temp->ai_addr, temp->ai_addrlen) == -1){
                    cout << "Could not bind fd\n";
                    close(fdInit);
                    continue; //Could not bind fd, continue looking for valid fd
            }
            break; //If a valid fd has been found, stop checking the list
    }
    if(temp==NULL){
            cout<<"Server failed to bind a socket\n";
            exit(2);
    }

    cout << fdInit << endl;
    //Setup the file descriptor for initial connections on specified port
    freeaddrinfo(res);
    FD_SET(fdInit, &masterSet);

Любая помощь будет отличной! Спасибо.

Ответы [ 3 ]

13 голосов
/ 18 февраля 2010

TCP-соединения идентифицируются по IP-адресу и номеру порта обоих концов соединения. Поэтому хорошо иметь множество клиентов (которые обычно имеют произвольно назначенные номера портов) для подключения к одному порту сервера.

Вы создаете сокет и bind() его подключаете к порту, к которому нужно listen(), а затем ждете, пока клиенты не постучат в него. Если вы не возражаете против блокирования, вы можете просто позвонить по номеру accept() напрямую, но вам не удастся выполнить цикл тайм-аута или что-то еще. В противном случае вы можете select() на сокете прослушивания, который станет читаемым, когда клиент пытается подключиться, и затем вызвать accept().

accept() вернет вновь созданный сокет, который является фактическим сокетом для общения с клиентом. Исходное гнездо прослушивания продолжает прослушивать, и на него можно принимать больше соединений.

Обычно используется цикл select() для поиска удобочитаемости на слушающем сокете и любом из подключенных сокетов. Затем, когда возвращается select(), вы просто проверяете, является ли сокет прослушивания доступным для чтения, и если да, то accept(); в противном случае ищите читаемый подключенный разъем и обрабатывайте его.

fd_set fds;
int max = 0, reuse = 1;
struct timeval tv;
int server;
std::vector<int> connected;

// create server listening socket
server = socket(PF_INET, SOCK_STREAM, getprotobyname("tcp")->p_proto);
setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int)); // optional, but recommended
if (bind(server, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0) {
    // error, could not bind server socket
}
if (listen(server, 8) < 0) {
    // error, could not listen on server port
}

// loop looking for connections / data to handle
while (running) {
    FD_ZERO(&fds);
    FD_SET(server, &fds);
    if (server >= max) max = server + 1;

    for (std::vector<int>::iterator it = connected.begin(); it != connected.end(); ++it) {
        FD_SET(*it, &fds);
        if (*it >= max) max = *it + 1;
    }

    tv.tv_sec = 2; tv.tv_usec = 0;
    if (select(max, &fds, NULL, NULL, &tv) > 0) {
        // something is readable
        if (FD_ISSET(server, &fds)) {
            // it's the listener
            connected.push_back(accept(server, (struct sockaddr *)&addr));
        }
        for (std::vector<int>::iterator it = connected.begin(); it != connected.end(); ++it) {
            if (FD_ISSET(*it, &fds)) {
                // handle data on this connection
            }
        }
    }
}
3 голосов
/ 18 февраля 2010

многие клиенты могут подключаться к одному и тому же порту

Вы должны сначала прослушать, затем выбрать

когда select сообщает, что у вас новое соединение, затем принимайте. Он сообщает вам, что клиент подключен, отметив чтение на вашем сокете fd.

1 голос
/ 18 февраля 2010

Вы должны пометить серверный сокет как таковой (listen(2)), но вызывать accept(2) следует только по возвращении из select(2), когда он станет читаемым - это означает, что новый запрос на соединение ожидает рассмотрения.

Обратите внимание, что если вы не работаете с неблокирующими сокетами , есть шанс на гонку между select(2) возвращением и вызовом accept(2) - подключающийся клиент может отбросить попытку в течение этого времени - так что вы все еще можете заблокировать.

...