4

我试图一次从服务器接收一个数据包,因为数据包速度太快,并且每个数据包的大小未定义,调用 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 次。

在收到 NULL char 之前是否有任何其他方法来 recv() ,或者比我正在使用的更好的方法?


编辑:我在从服务器发送的数据前面添加了数据包大小,但是现在,如果一个错误的数据包,甚至有时无缘无故,数据包就会被弄乱并且没有收到正确的数据。这是我的代码

#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);
    }
}
4

3 回答 3

5

我从来不明白为什么通信协议从不支持程序员期望能够做到的一个用例:用发送交换任意大小的 blob,recv 在边界上对齐。

所以这里没有真正的捷径。您需要保留一个持久缓冲区来保存上一次调用recv 留下的任何数据。在收到数据时继续将数据添加到末尾,并在每次找到一个时返回到终止零。您可能至少有一个部分后续数据包,因此将其移至缓冲区的开头以用作下一次调用的初始状态。

于 2011-02-19T15:58:17.080 回答
2

创建一个缓冲区并从中提取您的协议消息。如果缓冲区不包含完整的消息,则 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 到请求的字节数。接收到的缓冲区可能包含多个甚至部分消息。

此代码只是将接收到的数据附加到缓冲区中。协议从缓冲区请求字节,然后从套接字填充缓冲区。随着字节被删除,剩余的缓冲数据被移到缓冲区的开头。

在这种情况下,请求两个字节,转换为长度,然后请求剩余的字节。如果无法满足请求,则会接收更多数据。

希望这可以帮助。

于 2011-02-19T18:54:41.763 回答
0

有几种方法可以做到这一点。

选项 #1:在发送任何信息之前,在数据包的前面发送一个包含数据包大小的 int。读取此 int,然后分配一个缓冲区,该缓冲区是您刚刚收到的 int 的长度。然后您可以一次recv() 整个数据包。

选项 #2:一次读取 1024 个字节。recv() 将返回读取的字节数。然后,您可以使用 strlen() 来确定缓冲区中是否有多个数据包。进行递归可能最有意义(假设您可以在 1024 字节中包含多个数据包);这样您就可以根据 NULL 字节拆分数据包。

于 2011-02-19T16:04:19.507 回答