recv () до получения байта NUL? - PullRequest
4 голосов
/ 19 февраля 2011

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

    int recvLen = 0;
    char TB;
    char recvBuffer[1024];
    while (recv(Socket, &TB, 1, 0) > 0 && TB != 0 && recvLen < 1024)
    {
        recvBuffer[recvLen] = TB;
        recvLen++;
    }

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

Есть ли какой-либо другой метод для recv (), пока не будет получен символ NULL, или какой-то лучший метод, чем этот, который я использую?


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

#define UPLOAD_LEN 2755
int PacketSize, recvLen;
char Size[4];
char recvBuffer[UPLOAD_LEN+1];
while(1)
{
    if(recv(Socket,Size,4,0)>0)
    {
        Size[4] = '\0';
        PacketSize = atoi(Size);
        if (PacketSize > UPLOAD_LEN || PacketSize <= 0) continue;
        recvLen = recv(Socket, recvBuffer, PacketSize, 0);
    } else recvLen = -1;
    if (recvLen > 0)
    {
        recvBuffer[recvLen] = '\0';
        ProcessData(recvBuffer);
    }
    else
    {
        closesocket(Socket);
    }
}

Ответы [ 3 ]

4 голосов
/ 19 февраля 2011

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

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

2 голосов
/ 19 февраля 2011

Создайте буфер и извлеките из него ваши протокольные сообщения.Если буфер не содержит полное сообщение, то recv (), пока он не будет.Вот простая реализация C для буферизации сокета (слегка протестированная, компилируется на MS VS2008):

#include <winsock2.h>
#include <string.h>

typedef struct buffsock {
    SOCKET s;
    char* buf;
    size_t maxlen;
    size_t curlen;
} buffsock_t;

void buffsock_init(buffsock_t* bs,SOCKET s,size_t maxlen)
{
    bs->s = s;
    bs->buf = malloc(maxlen);
    bs->maxlen = maxlen;
    bs->curlen = 0;
}

void buffsock_free(buffsock_t* bs)
{
    free(bs->buf);
    bs->buf = NULL;
    bs->maxlen = 0;
    bs->curlen = 0;
    bs->s = INVALID_SOCKET;
}

/* Attempt to fill internal buffer.
 * Returns 0 if socket closed.
 * Returns number of additional bytes in buffer otherwise.
 */
int buffsock_fill(buffsock_t* bs)
{
    int bytes;
    bytes = recv(bs->s,bs->buf + bs->curlen,bs->maxlen - bs->curlen,0);
    if(bytes == SOCKET_ERROR)
        return -1;
    bs->curlen += bytes;
    return bytes;
}

/* Return up to <bytes> from buffered socket.
 * If return value 0 socket was closed.
 * If return value >0 and <bytes socket received partial message.
 */
int buffsock_bytes(buffsock_t* bs,size_t bytes,void* msg)
{
    while(bs->curlen < bytes)
    {
        int result;
        result = buffsock_fill(bs);
        if(result == -1)
            return -1; /* error on socket */
        if(result == 0)
            break;
    }
    if(bytes > bs->curlen)
        bytes = bs->curlen;
    memcpy(msg,bs->buf,bytes);
    bs->curlen -= bytes;
    memmove(bs->buf,bs->buf + bytes,bs->curlen);
    return bytes;
}

/* Implmementation of a protocol with two big-endian bytes indicating
 * msg size followed by <size> bytes of message.
 * Returns -1 if error on socket.
 * Returns -2 if partial message recv'd (shouldn't happen as long as
 * internal buffer is bigger than max message size).
 * Returns -3 if user buffer not big enough to hold message.
 * Returns size of message otherwise.
 */
int get_protocol_message(buffsock_t* bs,void* msg,size_t maxlen)
{
    int bytes;
    u_short len;
    bytes = buffsock_bytes(bs,sizeof(u_short),&len);
    if(bytes == 0)
        return 0;  /* socket closed, no more messages */
    if(bytes == -1)
        return -1; /* error on socket */
    if(bytes < sizeof(u_short))
        return -2; /* partial message */
    len = ntohs(len);
    if(len > maxlen)
        return -3; /* message exceeds user buffer */
    bytes = buffsock_bytes(bs,len,msg);
    if(bytes < len)
        return -2; /* partial message */
    return bytes;
}

Используйте это так:

int len;
char msg[256];
buffsock_t bs;
/* open a socket */
buffsock_init(&bs,sock,1024);
len = get_protocol_message(&bs,msg,sizeof(msg));

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

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

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

Надеюсь, это поможет.

0 голосов
/ 19 февраля 2011

Есть несколько способов сделать это.

Вариант № 1: Перед отправкой любой информации отправьте int в начале вашего пакета, который содержит размер пакета.Прочитайте это int, а затем выделите буфер, который является длиной int, который вы только что получили.Затем вы можете выполнить recv () всего пакета за один раз.

Вариант № 2: чтение 1024 байта за раз.recv () вернет вам количество прочитанных байтов.Затем вы можете использовать strlen (), чтобы выяснить, есть ли у вас более одного пакета в буфере.Вероятно, было бы разумнее сделать это рекурсивным (при условии, что у вас может быть несколько пакетов по 1024 байта);так что вы разделяете пакеты на основе пустых байтов.

...