Чтение более одного "сообщения" из recv () - PullRequest
2 голосов
/ 15 марта 2019

Вполне возможно, что более одного отдельного "сообщения" (например, 2 send ()) могут быть прочитаны в буфер с помощью вызова recv ().

В таком случае, как бы вы поместили второе сообщение обратно в буфер recv (), как только поняли, что в вашем буфере больше данных, чем нужно?

Например,,

Все сообщения начинаются с байта, определяющего их длину.Мне нужно получать до тех пор, пока правильное количество байтов не будет считано в буфер, но не продолжать после этого.

Одна идея - сделать один recv (), чтобы установить длину сообщения, а затем создать буфер.с этим размером.Я не знаю, что произойдет с данными, которые не помещаются в буфер.

Ответы [ 3 ]

2 голосов
/ 15 марта 2019

Если у вас есть фиксированный размер, который вы хотите получить, вы можете сделать что-то вроде этого:

ssize_t recv_all(int socket, char *buffer_ptr, size_t bytes_to_recv)
{
    size_t original_bytes_to_recv = bytes_to_recv;

    // Continue looping while there are still bytes to receive
    while (bytes_to_recv > 0)
    {
        ssize_t ret = recv(socket, buffer_ptr, bytes_to_recv, 0);
        if (ret <= 0)
        {
            // Error or connection closed
            return ret;
        }

        // We have received ret bytes
        bytes_to_recv -= ret;  // Decrease size to receive for next iteration
        buffer_ptr += ret;     // Increase pointer to point to the next part of the buffer
    }

    return original_bytes_to_recv;  // Now all data have been received
}

Просто используйте как

// Somewhere above we have received the size of the data to receive...

// Our data buffer
char buffer[the_full_size_of_data];

// Receive all data
recv_all(socket, buffer, sizeof buffer);  // TODO: Add error checking

[Обратите внимание, что я использую POSIXтипы как ssize_t и int для сокетов.Измените, чтобы соответствовать вашей системе (например, SOCKET для сокета в Windows).]

0 голосов
/ 15 марта 2019

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

Например, приведенный ниже класс будет вызывать recv до байта сторожа (в данном случае, находится символ новой строки), затем возвращается только сообщение (строка в кодировке UTF-8) минус сторож.Все оставшиеся в буфере данные сохраняются и обрабатываются при следующем get_msg вызове:

from socket import *

class SocketBuffer:
    def __init__(self,sock):
        self.sock = sock
        self.buffer = b''

    def get_msg(self):
        # Buffer data until a newline is found.
        while b'\n' not in self.buffer:
            data = self.sock.recv(1024)
            if not data:
                return b'' # drops partial messages...should check and raise error instead
            self.buffer += data
        # split off the message bytes from the buffer.
        msg,_,self.buffer = self.buffer.partition(b'\n')
        return msg.decode()
0 голосов
/ 15 марта 2019

как бы вы поместили второе сообщение обратно в буфер recv (), как только вы поняли, что в вашем буфере больше данных, чем нужно?

Просто не кладитевторое сообщение из буфера recv (), я вижу для этого два пути:

1) сделать первое

ssize_t size = recv(sockfd, buf, len, MSG_PEEK | MSG_TRUNC);
  • MSG_TRUNC (только AF_PACKET) дает вам реальный размериз доступных данных, длина которых не может быть усечена до len
  • с MSG_PEEK, полученные данные не удаляются из очереди.

Это позволяет вам анализировать данные просмотра и

  • , если оно является частью первого сообщения, но не его концом, вы читаете (не просматриваете) его recv(sockfd, buf, size); затем вы переделываете предыдущее recv и т. д.
  • , если у вас есть (конец) первого сообщения и вы можете быть частью второго сообщения, которое вы знаете subSize , которое нужно прочитать и сделатьrecv(sockfd, buf, subSize);, и ваше второе сообщение все еще доступно для следующего revc

Конечно, каждый раз, когда вы читаете часть первого сообщения, указатель buf прогресс, чтобы не переписывать уже прочитанную часть.

Используйте malloc затем realloc, чтобы увеличить размер буфера, получающего первое сообщение

2), очень распространенный способ егоотправьте размер сообщения перед самим сообщением, что позволит получателю сначала прочитать размер, а затем прочитать данные в цикле, пока все сообщение не будет прочитано.Для совместимости с прямым / младшим порядковым номером, если сообщение больше 255 байтов, используйте htons / htonl / ntohs / ntohl для размера

Я не знаю, что произойдетхотя к данным, которые не помещаются в буфер.

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


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

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

Клиент получает один аргумент размером, который нужно (попытаться) прочитатькаждый раз он печатает каждый буфер 'peek' (для отладки) и каждый буфер.

server.c

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

int main(int argc, char ** argv)
{
  errno = 0;

  int ssock = socket(AF_INET, SOCK_STREAM, 0);

  if (ssock == -1) {
    perror("socket()");
    return -1;
  }

  int reuse = 1;
  if (setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) == -1) {
    perror("setsockopt() SO_REUSEADDR)");
    return -1;
  }

#ifdef SO_REUSEPORT
  if (setsockopt(ssock, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) == -1) {
    perror("setsockopt() SO_REUSEPORT)");
    return -1;
  }
#endif

  struct sockaddr_in ssin = { 0 };

  ssin.sin_addr.s_addr = htonl(INADDR_ANY);
  ssin.sin_port = htons(1024);
  ssin.sin_family = AF_INET;

  if(bind (ssock, (struct sockaddr*) &ssin, sizeof(ssin)) == -1)
  {
    perror("bind()");
    return -1;
  }

  if(listen(ssock, 1) == -1)
  {
    perror("listen()");
    return -1;
  }

  struct sockaddr_in csin = { 0 };
  socklen_t csz = sizeof(csin);
  int csock = accept(ssock, (struct sockaddr*) &csin, &csz);

  if (csock == -1) {
    perror("accept()");
    return -1;
  }

  for (int i = 1; i < argc; ++i) {
    if (send(csock, argv[i], strlen(argv[i]), 0) == -1) {
      char s[32];

      sprintf(s, "send %i", i);
      perror(s);
    }
  }

  close(csock);
  close(ssock);
}

client.c

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

int main(int argc, char ** argv)
{
  if (argc != 2) {
    printf("Usage : %s <length>\n", *argv);
    return 0;
  }

  int len;
  char c;

  if ((sscanf(argv[1], "%d%c", &len, &c) != 1) && (len < 1)) {
    fprintf(stderr, "invalid length\n");
    return -1;
  }

  errno = 0;

  int sock = socket(AF_INET, SOCK_STREAM, 0);

  if (sock == -1) {
    perror("socket()");
    return -1;
  }

  struct sockaddr_in sin = { 0 };

  sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */
  sin.sin_port = htons(1024);
  sin.sin_family = AF_INET;

  if(connect (sock, (struct sockaddr*) &sin, sizeof(sin)) == -1)
  {
    perror("connect()");
    return -1;
  }

  for (;;) {
    size_t ln = len;
    char * buf = malloc(ln + 1);

    if (buf == NULL) {
      fprintf(stderr, "cannot malloc");
      break;
    }

    size_t off = 0;

    for (;;) {
      ssize_t sz = recv(sock, buf + off, len, MSG_PEEK); /* no MSG_TRUNC : AF_INET */

      if (sz <= 0) {
        free(buf);
        close(sock);
        return -1;
      }

      buf[off + sz] = 0;

      /* debug */
      printf("peek '%s'\n", buf + off);

      char * p = strchr(buf + off, ' ');

      if (p != NULL) {
        recv(sock, buf + off, p - buf - off + 1, 0);
        *p = 0;
        printf("full buff is '%s'\n", buf);
        free(buf);
        break;
      }

      recv(sock, buf + off, sz, 0);
      off += sz;
      ln += sz;
      buf = realloc(buf, ln + 1);

      if (buf == NULL) {
        fprintf(stderr, "cannot malloc");
        break;
      }
    }
  }

  close(sock);
}

Компиляция и выполнение:

pi@raspberrypi:~ $ gcc -pedantic -Wextra server.c -o se
pi@raspberrypi:~ $ gcc -g -pedantic -Wextra client.c -o cl
pi@raspberrypi:~ $ ./se "123 456 78901234567" "8 1 " &
[1] 11551
pi@raspberrypi:~ $ ./cl 5
peek '123 4'
full buff is '123'
peek '456 7'
full buff is '456'
peek '78901'
peek '23456'
peek '78 1 '
full buff is '789012345678'
peek '1 '
full buff is '1'
[1]+  Fini                    ./se "123 456 78901234567" "8 1 "
pi@raspberrypi:~ $ 

Выполнения под valgrind (в отдельных терминалах):

pi@raspberrypi:~ $ valgrind ./se "123 456 78901234567" "8 1 " 
==11602== Memcheck, a memory error detector
==11602== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==11602== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==11602== Command: ./se 123\ 456\ 78901234567 8\ 1\ 
==11602== 
==11602== 
==11602== HEAP SUMMARY:
==11602==     in use at exit: 0 bytes in 0 blocks
==11602==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==11602== 
==11602== All heap blocks were freed -- no leaks are possible
==11602== 
==11602== For counts of detected and suppressed errors, rerun with: -v
==11602== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 3)

pi@raspberrypi:~ $ valgrind ./cl 5
==11604== Memcheck, a memory error detector
==11604== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==11604== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==11604== Command: ./cl 5
==11604== 
peek '123 4'
full buff is '123'
peek '456 7'
full buff is '456'
peek '78901'
peek '23456'
peek '78 1 '
full buff is '789012345678'
peek '1 '
full buff is '1'
==11604== 
==11604== HEAP SUMMARY:
==11604==     in use at exit: 0 bytes in 0 blocks
==11604==   total heap usage: 8 allocs, 8 frees, 1,081 bytes allocated
==11604== 
==11604== All heap blocks were freed -- no leaks are possible
==11604== 
==11604== For counts of detected and suppressed errors, rerun with: -v
==11604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 6 from 3)
...