接收未知大小数据的最佳方式?

时间:2014-05-06 02:51:22

标签: c sockets

我正在制作一个IRC机器人,以便了解插座,但我遇到了一些麻烦。 我写了一个函数,它发送启动与RFC1459中指定的IRC服务器连接所需的命令。为了接收,我在这里有这个功能:

int receiveData(int socketDescriptor)
{
    char receiveBuffer[512];
    receiveBuffer[512] = '\0';
    recv(socketDescriptor, receiveBuffer, 511, 0);
    printf("%s\n", receiveBuffer);
    return 0;
}

(到目前为止整个程序:http://lpaste.net/103646

正如您所知,在初始交换之后,IRC服务器转发给您的第一件事是MoTD,但是,当天的消息通常很长,并且在这方面也可能有所不同,所以我想我可能需要使用动态数组的链接列表来存储它,但我以前从未实现过类似的东西,所以这是我的问题。

我是否需要动态数组的链接列表,如果是,那么我该如何在这里实现呢?

1 个答案:

答案 0 :(得分:5)

原谅我这种轻微的转移,但你问这个问题的方式让我相信这是有道理的。

首先处理基于TCP的协议可能具有挑战性,因为有时很难理解从套接字获得的内容与更高级别协议的结构无关。 TCP是基于流的,旨在确保接收方获取发送方按照最初传输的顺序发送的数据。它绝对不能保证在从套接字读取任何给定的数据时会收到多少数据,更不用说如何映射到更高级别的消息。

例如,考虑希望通过TCP套接字背靠背传输两个消息的发送方。假设消息A是200字节而消息B是500字节。从套接字读取时,可能会得到100个字节,200个字节,400个字节或700个字节(或其间的任何变化)。您根本无法知道在任何给定的套接字读取时将检索多少数据。更糟糕的是,你不能指望从套接字返回单个读取。完整的信息。您可能会同时收到部分消息,完整消息或多条消息。

那么如何应对呢?好吧,链接列表可以工作,但我认为这使得事情变得比他们需要的更复杂。出于我的目的,我使用两个缓冲区 - 接收缓冲区和工作缓冲区。我的接收缓冲区往往是静态大小的,并且等于套接字接收缓冲区的大小(我相信在Windows上默认为8KB ...不确定你正在使用什么操作系统)。从套接字读取的每一次都将数据从字节0开始写入接收缓冲区。读取完这个数据后,我将它从接收缓冲区复制到工作缓冲区的尾端,这对于你的目的来说可能很大静态大小的数组。这允许我将数据流拼凑回一个非常容易解析的连续数据数组。我使用索引变量来跟踪我在工作缓冲区中的位置,例如startPoslastPos等。

回到这个例子,让我们说第一次读取得到250个字节。这是消息A的完整200字节和消息B的前50个字节。我将其复制到工作缓冲区的尾端并更新索引变量。当然,自从我们刚刚开始,这恰好是工作缓冲区的开始。所以现在我检查工作缓冲区,看看我是否有完整的消息。好吧,消息A已经完成,但它是什么类型的消息?检查消息表明它是Foo消息。我实例化一个Foo对象,然后用消息中的数据填充字段。这里int shortdouble那边Foo。处理完Bar消息后,我将返回工作缓冲区。此时,我不再需要前200个字节,因此我将最后50个字节复制到工作缓冲区的开头并更新索引变量。那么我检查50个字节是否代表完整的消息,当然在这一点上,答案是否定的。

回到套接字以获取更多数据。下一次读取给了我另外250个字节到接收缓冲区。我将其复制到工作缓冲区的尾端,以便工作缓冲区现在总共有300个字节,并更新索引变量。我检查300字节是否代表完整的消息,同样,答案是否定的。

再次回到套接字。下一次读取给了我另外200个字节到接收缓冲区。另一个副本到工作缓冲区的尾端并更新到索引变量。我现在在工作缓冲区中有500个字节。我查看它是否是一个完整的消息,它是。什么样的消息?它是Bar消息。我实例化一个{{1}}对象,然后用消息中的数据填充字段。此时,工作缓冲区已经完全处理,所以我只是更新索引变量(因为没有数据移位)。

这就是我如何做到的。读 - 复制 - 处理 - 更新 - 重复。

要注意的一些问题......

我所描述的是一种处理基于TCP的数据的单线程方法。在实践中,我使用至少两个线程来执行此操作,一个用于服务套接字,另一个用于处理数据。单线程方法的危险在于您花费了大量时间处理数据,而这些数据并未充分地为套接字提供服务。我怀疑你不必为IRC担心这一点,我当然明白你正在学习。请注意,这将是未来的考虑因素。

另一个需要注意的是工作缓冲区的大小。您不想遇到的情况是您已经从套接字读取数据到接收缓冲区,但是您没有足够的空间来处理工作缓冲区来复制数据。解决这个问题的方法是使用一个静态大小的数组作为接收缓冲区,但是一个动态大小的数组用于工作缓冲区,它可以增长以适应不断增加的流量负载。同样,对于IRC,一个大的静态大小的工作缓冲区(比如8MB)可能会有点过分。但是,将我所描述的解决方案转移到处理基于XML的流或非常高的流量负载是不够的。

我希望你觉得这很有用。