Может ли сервер использовать один и тот же сокет для отправки ответа клиенту? как? - PullRequest
12 голосов
/ 22 января 2009

Я использую сокеты Беркли (оба: интернет-домен и домен Unix), и мне было интересно, может ли сервер использовать одни и те же сокеты для чтения запроса и записи ответа клиенту. Или же клиент должен создать другой сокет для ожидания воспроизведения, а сервер подключиться к нему после обработки полученного сообщения.

Кстати, я говорю о сокетах, ориентированных на соединение (потоковые сокеты, TCP, ...).

Это упрощенный серверный код (здесь я просто опускаю проверку на ошибки при системных вызовах):

int main() {

    int server_socket, connected_socket;
    struct sockaddr_in server_addr;
    char buf[1024];
    char aux[256];
    int bytes_read;

    server_socket = socket(AF_INET, SOCK_STREAM, 0);    

    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(1234);
    bind(server_socket, &server_addr, sizeof(server_addr))

    listen(server_socket, 5)

    connected_sodket = accept(server_socket, 0, 0);
    do {
        bzero(buf, sizeof(buf));
        bytes_read = read(connected_socket, buf, sizeof(buf));        
    } while (bytes_read > 0);          

    /* Here I want to use connected_socket to write the reply, can I? */

    close(connected_socket);       

    close(server_socket);

    return (EXIT_SUCCESS);
}

А это упрощенный клиентский код (здесь я просто опускаю проверку на ошибки при системных вызовах):

int main() {

    int client_socket;
    struct sockaddr_in server_addr;

    client_socket = socket(AF_INET, SOCK_STREAM, 0);

    hp = gethostbyname("myhost");
    server_addr.sin_family = AF_INET;
    memcpy(&server_addr.sin_addr, hp->h_addr_list[0], hp->h_length);
    server_addr.sin_port = htons(1234);

    connect(client_socket, &server_addr, sizeof(server_addr));

    write(client_socket, MSG, sizeof(MSG));

    /* Here I want to wait for a response from the server using client_socket, can I? */

    close(client_socket);

    return (EXIT_SUCCESS);
}

Могу ли я использовать connected_socket на сервере и client_socket на клиенте для передачи ответного сообщения обратно? Или я должен использовать адрес клиента, который я получаю на сервере, когда в «принять» для подключения к сокету в клиенте?

Я пытался использовать read / wrint на клиенте / сервере, где отображается комментарий, но таким образом обе программы остаются заблокированными, похоже, это тупик.

Спасибо заранее! Привет.

Ответы [ 9 ]

10 голосов
/ 22 января 2009

Вы должны использовать тот же сокет!

Ваш прикладной протокол однозначно определяет, когда клиент и сервер должны ждать данных или отправлять сообщения друг другу; при условии, что протокол содержит только один запрос от клиента и один ответ от сервера, должно выполняться следующее:


  • Клиент устанавливает соединение с сервером;
  • клиент отправляет свой запрос (с send ());
  • клиент знает по протоколу , что сервер ответит; поэтому он ожидает данные в том же сокете (recv ());
  • после проверки ответа клиент может закрыть сокет.

  • Сервер принимает соединение от клиента;
  • сервер знает , что первый шаг зависит от клиента, поэтому он ожидает данных (recv ());
  • сервер подтверждает запрос;
  • сервер теперь знает по протоколу , что клиент ожидает данных; следовательно, он отправляет свой ответ с помощью send ();
  • сервер знает из протокола , что дальнейших шагов нет; следовательно, он может закрыть сокет.
8 голосов
/ 22 января 2009

Вы можете использовать тот же сокет, НО ваша программа настроена так, чтобы сервер считывал ВСЕ, что клиент отправляет перед попыткой ответа. Таким образом, цикл на сервере не завершится, пока клиент не закроет сторону записи своего сокета, поэтому сервер получит EOF (чтение 0 байт), и, следовательно, сервер никогда не отправит свой ответ.

Есть несколько способов справиться с этим.

  1. Вы можете разорвать цикл на сервере после просмотра всего запроса, а не читать до EOF. Для этого необходимо, чтобы данные, отправляемые клиентом, каким-то образом самостоятельно разграничивались, чтобы сервер мог знать, когда все это прочитает.
  2. Вы можете использовать второе соединение для ответа. Наверное, не самый лучший.
  3. Вы можете использовать асимметричное отключение розетки. Попросите клиента сделать shutdown(client_socket, SHUT_WR), чтобы наполовину закрыть сокет. Сервер увидит EOF (и цикл завершится), но другое направление сокета все еще будет открыто для ответа.
3 голосов
/ 22 января 2009

Да, это возможно. Посмотрите на на этой странице пример простого сервера (и простого клиента). Обратите внимание, что сервер обычно передает дескриптор ed файла «accept» в новый процесс, чтобы он мог продолжать прослушивать больше входящих соединений

2 голосов
/ 22 января 2009

Мало того, что вы должны использовать один и тот же сокет (как говорит Федерико), вам на самом деле необходимо , чтобы получить ваши возвратные пакеты через брандмауэры.

Брандмауэры знают о TCP-соединениях и автоматически пропускают возвращаемые данные, если машина внутри брандмауэра инициировала соединение. Если вместо этого вы попытаетесь создать новый сокет TCP извне, брандмауэр заблокирует его, если только этот трафик не был специально разрешен.

1 голос
/ 22 января 2009

В вашей текущей настройке сервер пытается прочитать, пока клиент не закроет сокет, в то время как клиент не закроет сокет, пока сервер не ответит. Поэтому у вас есть своего рода «тупик». Сервер не останавливается для чтения в надежде получить еще какие-то данные, и клиент не может сообщить серверу, что это сделано.

Вам нужно, чтобы сервер мог распознать, что запрос завершен, пока соединение еще открыто. Если вы, например, завершите запрос с помощью новой строки, сервер может проверить, получил ли он полную строку, а затем прекратить чтение и отправить ответ.

1 голос
/ 22 января 2009

К сожалению, Я этого не говорил, но попробовал вот так

Этот код на сервере, где комментарий:

write(connected_socket, "Ok, I got it", sizeof("Ok, I got it"));

и этот код в клиенте, где комментарий:

read(client_socket, buf, sizeof(buf));

Обе программы остаются заблокированными, и когда я убиваю клиента, сервер показывает полученные сообщения (у меня есть printf сразу после того, как сервер вызывает read ).

Я пробую это с send и recv (оба с 0 флагами) вместо read и write, и это не изменилось.

1 голос
/ 22 января 2009

Да, вы можете. TCP-сокеты являются двунаправленными. Просто используйте те же функции read () и write (). Также не забудьте проверить наличие ошибок во всех вызовах connect (), read (), write (), ... поскольку вы не можете контролировать, что происходит в сети.

1 голос
/ 22 января 2009

Да, сокеты SOCK_STREAM являются двусторонними. Вы должны иметь возможность читать и записывать в / из одного и того же сокета на каждой стороне соединения. Страница man для socket(2) содержит более подробную информацию об этом.

0 голосов
/ 22 января 2009

Вы пробовали это? Похоже, что он должен работать, когда вы на самом деле поместите код там, где указано

...