recv()直到收到NUL字节?

时间:2011-02-19 15:41:57

标签: c sockets winsockets

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

3 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分: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到请求的字节数。收到的缓冲区可能包含多个甚至部分消息。

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

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

希望这有帮助。

答案 2 :(得分:0)

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

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

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