【问题标题】:How Do Sockets Work in C?套接字如何在 C 中工作?
【发布时间】:2010-09-08 02:43:59
【问题描述】:

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

您创建一个套接字,将它绑定到一个接口和一个 IP 地址并让它监听。我在上面找到了一些网络资源,并且理解得很好。特别是,我发现一篇文章 Network programming under Unix systems 信息量很大。

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

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

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

【问题讨论】:

    标签: c sockets network-programming


    【解决方案1】:

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 只有在套接字未设置为非阻塞时才如此。
      【解决方案3】:

      简短的回答是您必须自己完成所有繁重的工作。可以通知您有数据可供读取,但您不知道有多少字节可用。在大多数使用可变长度数据包的 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;
      }
      

      【讨论】:

      • 关闭连接后的提前返回不应该是"return bytesRead;"而不是“return ret;”?我认为 readBytes 函数旨在返回实际读取的字节数。我想您可以将其定义为在出现错误时返回非正整数,但您也可以通过检查 readBytes 返回的字节数是否与请求的不同来检测错误。
      • 使用负值来标记错误状态是 C 编程中的常见做法,应尽可能遵守以防止 API 用户混淆。
      • 我修改了我的示例函数以返回读取的字节数,即使在读取过程中发生错误也是如此。使用套接字要记住的一点是,返回零的读取表示连接已关闭,并且不一定是错误。
      【解决方案4】:

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

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

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

      有两种方法可以为套接字执行 I/O - 阻塞,在其中您调用 read(fd, buf, N) 并且读取坐在那里并等待直到您将 N 个字节读入 buf - 或非-blocking,您必须检查(使用 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 卷。

      【讨论】:

        猜你喜欢
        • 2018-11-14
        • 1970-01-01
        • 1970-01-01
        • 2020-11-02
        • 2016-11-10
        • 1970-01-01
        • 1970-01-01
        • 2021-05-11
        • 1970-01-01
        相关资源
        最近更新 更多