21

我对 C 中的套接字编程有点困惑。

您创建一个套接字,将它绑定到一个接口和一个 IP 地址并让它监听。我在上面找到了一些网络资源,并且理解得很好。特别是,我发现一篇文章Unix 系统下的网络编程非常有用。

让我感到困惑的是数据到达套接字的时间。

您如何知道数据包何时到达以及数据包有多大,您是否必须自己完成所有繁重的工作?

我在这里的基本假设是数据包可以是可变长度的,所以一旦二进制数据开始出现在套接字中,你如何开始从中构造数据包?

4

4 回答 4

17

简短的回答是您必须自己完成所有繁重的工作。可以通知您有数据可供读取,但您不知道有多少字节可用。在大多数使用可变长度数据包的 IP 协议中,数据包前面都会有一个已知固定长度的标头。此标头将包含数据包的长度。您读取标头,获取数据包的长度,然后读取数据包。您重复此模式(读取标头,然后读取数据包),直到通信完成。

从套接字读取数据时,您请求一定数量的字节。read 调用可能会阻塞,直到读取了请求的字节数,但它返回的字节数可能少于请求的字节数。发生这种情况时,您只需重试读取,请求剩余的字节。

这是一个典型的 C 函数,用于从套接字读取一定数量的字节:

/* buffer points to memory block that is bigger than the number of bytes to be read */
/* socket is open socket that is connected to a sender */
/* bytesToRead is the number of bytes expected from the sender */
/* bytesRead is a pointer to a integer variable that will hold the number of bytes */
/*           actually received from the sender. */
/* The function returns either the number of bytes read, */
/*                             0 if the socket was closed by the sender, and */
/*                            -1 if an error occurred while reading from the socket */
int readBytes(int socket, char *buffer, int bytesToRead, int *bytesRead)
{
    *bytesRead = 0;
    while(*bytesRead < bytesToRead)
    {
        int ret = read(socket, buffer + *bytesRead, bytesToRead - *bytesRead);
        if(ret <= 0)
        {
           /* either connection was closed or an error occurred */
           return ret;
        }
        else
        {
           *bytesRead += ret;
        }
    }
    return *bytesRead;
}
于 2008-09-08T00:27:17.370 回答
13

因此,您的问题的答案在很大程度上取决于您是使用 UDP 还是 TCP 作为传输。

对于 UDP,生活变得简单得多,因为您可以使用所需的数据包大小调用 recv/recvfrom/recvmsg(无论如何,您可能会从源发送固定长度的数据包),并假设数据是否可用,它以数据包长度大小的倍数存在。(即您使用发送端数据包的大小调用 recv* ,然后您就设置好了。)

对于 TCP,生活变得更有趣了——为了解释的目的,我假设你已经知道如何使用 socket()、bind()、listen() 和 accept()——后者是你如何获得新建立连接的文件描述符 (FD)。

有两种方法可以为套接字执行 I/O - 阻塞,在其中您调用 read(fd, buf, N) 并且读取坐在那里并等待直到您将 N 个字节读入 buf - 或非阻塞,您必须在其中检查(使用 select() 或 poll())FD 是否可读,然后执行 read()。

在处理基于 TCP 的连接时,操作系统不会关注数据包的大小,因为它被认为是连续的数据流,而不是单独的数据包大小的块。

如果您的应用程序使用“数据包”(您传递的打包或解包数据结构),您应该能够使用适当的大小参数调用 read(),并一次从套接字读取整个数据结构。您必须处理的唯一警告是记住对您发送的任何数据进行正确的字节排序,以防源系统和目标系统具有不同的字节字节序。这适用于 UDP 和 TCP。

就 *NIX 套接字编程而言,我强烈推荐 W. Richard Stevens 的“Unix Network Programming, Vol. 1” (UNPv1) 和“Advanced Programming in an Unix Env​​ironment” (APUE)。第一本是关于基于网络的编程的书籍,与传输无关,后者是一本很好的全方位编程书籍,因为它适用于基于 *NIX 的编程。另外,请查找“TCP/IP 图解”,第 1 卷和第 2 卷。

于 2008-09-08T03:45:57.347 回答
3

当您对套接字进行读取时,您会告诉它要读取的最大字节数,但如果它没有那么多,它就会给您提供多少字节。由您来设计协议,以便您知道是否有部分数据包。例如,过去在发送可变长度二进制数据时,我会在开头放一个 int 表示期望的字节数。我会进行读取,请求比我的协议中可能的最大数据包更大的字节数,然后我会将第一个 int 与我收到的字节数进行比较,然后处理它或尝试更多读取,直到我' d 得到了完整的数据包,具体取决于。

于 2008-09-08T00:05:22.073 回答
1

套接字在比原始数据包更高的级别上运行 - 它就像您可以读取/写入的文件。此外,当您尝试从套接字读取时,操作系统将阻止(暂停)您的进程,直到它有数据来满足请求。

于 2008-09-08T00:06:01.133 回答