Сокет Linux: как заставить send() ждать recv()

Я делаю простое клиент-серверное приложение, использующее протокол TCP.

Я знаю, что по умолчанию. recv() будет блокироваться до тех пор, пока другая сторона не вызовет send() для этого сокета. Но возможно ли, что send() блокирует себя до тех пор, пока другая сторона не recv() отправляет сообщение, вместо того, чтобы сохранять send() в исходящей очереди, а затем, чтобы найти другую сторону, recv() получает целую кучу сообщений, отправленных несколькими send()

Другими словами. Можно ли позволить каждому send() ждать recv() другой стороны, прежде чем он сможет вызвать другой send()?

Чтобы проиллюстрировать мой вопрос. Я опубликую простой код здесь:

клиент.c

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <poll.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h> 

int main(int argc, char *argv[])
{
    int sockfd = 0;
    char sendBuff[1024];
    struct sockaddr_in serv_addr;
    int i;

    if(argc != 2)
    {
        printf("\n Usage: %s <ip of server> \n",argv[0]);
        return 1;
    } 

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    } 

    memset(&serv_addr, '0', sizeof(serv_addr)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000); 

    if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
    {
        printf("\n inet_pton error occured\n");
        return 1;
    } 

    if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
        printf("\n Error : Connect Failed \n");
        return 1;
    }

    do{
        memset(sendBuff, '\0', sizeof(sendBuff));
        sprintf(sendBuff, "This is line %d", i);
        send(sockfd, sendBuff, strlen(sendBuff), 0);
        //sleep(1);
    }while(++i<100);

    return 0;
}

server.c

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h> 

int main(int argc, char *argv[])
{
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr; 

    char sendBuff[1025];
    char recvBuff[100];

    int i = 0;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff)); 

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000); 

    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 

    listen(listenfd, 10); 

    connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 

    do{  
        memset(recvBuff, '\0', sizeof(recvBuff));
        recv(connfd, recvBuff, sizeof(recvBuff),0);
        printf( "%s\n", recvBuff);
    }while(++i<100); 

    return 0;
} 

Что я ожидаю от результата на стороне сервера, так это печать:

This is line 0
This is line 1
This is line 2
This is line 3
...

Однако фактический результат таков:

This is line 0
This is line 1This is line 2This is line3This is line 4
This is line 5This is line 6This is line 7This is line 8This is line 9This is line 10
This is line 11This is line 12...

Однако это легко объяснить: когда клиентская сторона выдала send(), она не дождалась завершения recv() серверной стороны, и по какой-то причине цикл recv() на стороне сервера медленнее, чем send() на стороне клиента. Таким образом, несколько send() на стороне клиента могут быть сложены вместе и получены сервером как единое целое. (правильно ли мое объяснение?)

На самом деле, кажется, очень глупое и свободное решение. просто добавьте sleep(1) (как я прокомментировал) после каждого send() в цикле. Я знаю, что это сделает код очень неэффективным, и если циклу recv() потребуется больше времени для реализации некоторых других сложных действий (это, очевидно, непредсказуемо, когда программа становится большой), это займет больше 1 секунды. Это решение не работает!

Итак, есть ли более надежный способ позволить двум сторонам общаться друг с другом, чтобы гарантировать, что сообщение, отправленное одним единственным send(), будет получено одним recv()?


person user2151995    schedule 05.11.2013    source источник


Ответы (3)


client.c

while(condition)
{
  send() from client;
  recv() from server;
}

server.c

recv() from client;
while(condition)
{
  send() from server; //acknowledge to client
  recv() from client;
}
person ravi    schedule 05.11.2013
comment
Я никогда не ожидал, что решение может быть настолько простым. Спасибо чувак! - person user2151995; 07.11.2013
comment
Пожалуйста!! Однажды я столкнулся с похожей ситуацией. Я думал, что это решение может помочь :-) - person ravi; 07.11.2013

Единственный способ для отправителя узнать, что получатель получил сообщение, — это ответ получателя.

Другими словами, если вы хотите синхронизировать отправителя и получателя, вам необходимо реализовать собственное явное управление потоком.

Это правда, что протокол TCP требует, чтобы получатели подтверждали полученные пакеты, поэтому вы можете подумать, что получатель может просто запросить реализацию TCP, были ли подтверждены данные. Как правило, это возможно, хотя я не верю, что существует кросс-платформенный способ сделать это. В Linux, например, вы можете использовать ioctl код SIOCOUTQ для проверки размера исходящего буфера. Но, насколько я знаю, нет механизма блокировки, пока исходящий буфер не станет пустым, поэтому лучшее, что вы можете сделать, это периодически опрашивать. Это неудовлетворительно.

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

Кроме того, ожидание прибытия сообщения подтверждения на отправляющую сторону обычно излишне замедляет обмен данными, поскольку добавляет задержку передачи (получатель-отправитель) к каждой транзакции. Это не так драматично, как ваше решение sleep, но в чем-то похоже.

Если ваши сообщения очень короткие, возможно, вы столкнулись с алгоритмом Nagle, который заставляет небольшие пакеты данных сохраняться. в течение небольшого промежутка времени отправителем в надежде, что несколько пакетов могут быть объединены. Вы можете отключить алгоритм Nagle для TCP-соединения, используя setsockopt с опцией TCP_NODELAY. Но не делайте этого, если не уверены, что это будет полезно. (Каноническое использование TCP_NODELAY — это соединение типа telnet, где данные состоят из отдельных нажатий клавиш.)

person rici    schedule 05.11.2013

Если ваша проблема заключается в обработке только одного сообщения за раз, как насчет включения поля длины? Отправитель

int sendLen = strlen(sendBuff);
send(sockfd, &sendLen, sizeof(sendLen), 0);
send(sockfd, sendBuff, sendLen, 0);

Получатель

int len = 0;
recv(connfd, &len, sizeof(len), 0);
recv(connfd, recvBuff, len, 0);

Это позволит вам читать по одному сообщению за раз, а остальные оставлять в tcp-буфере.

Имейте в виду, что приведенный выше код делает много предположений о том, что ваши отправляющие и принимающие программы имеют одинаковый порядок следования байтов и размер целых чисел. (Если все это работает на одной машине, все в порядке). Он также не проверяет возвраты ошибок, и send(), и recv() могут иметь ошибки.

person Macattack    schedule 05.11.2013