C套接字原子非阻塞读
C socket atomic non-blocking read
我正在实现一个 TCP/IP 服务器应用程序,它在边缘触发模式下使用 epoll 并执行非阻塞套接字操作。客户端正在使用没有 epoll 的简单阻塞操作。
我不知道如何在服务器端实现 "atomic reads"。要解释我对 "atomic read" 的意思,请看这个简单阻塞操作的例子:
- 客户端和服务器都使用 64K 缓冲区。 (在应用程序级别。他们不更改内核级别套接字缓冲区。)
- 客户端单次写入12K数据。
- 服务器读取它。在这种情况下,当缓冲区相同时,它总是读取整个 12K。所以它不能只读它的一半。这就是我所说的 "atomic".
但在 epoll + 非阻塞操作的情况下,可能会发生这种情况:
- 客户端和服务器都使用 64K 缓冲区。 (在应用程序级别。他们不更改内核级别套接字缓冲区。)
- 客户端单次写入12K数据。
- 6K到达服务器
- epoll 告诉应用程序数据已到达套接字
- 应用程序使用非阻塞操作将 6K 读入缓冲区。
- 重复读取时,returns EAGAIN / EWOULDBLOCK。
在这种情况下,读取不是 "atomic"。不能保证当数据是通过一次写入操作写入时,读取将 return 整体,一片。
是否可以知道数据何时是部分数据?我知道一种解决方案是始终将数据大小附加到开头,或者另一种可能是始终关闭并重新打开连接,但我不想这样做:因为我认为内核必须知道不是完整的"package"(那个单元怎么叫 BTW?)到了,因为它保证了阻塞操作的原子性。
非常感谢!
TCP 是基于流的,而不是面向消息的。即使在阻塞套接字的情况下,您也不能保证应用程序发送的内容会一次性完成。 TCP 将决定自己的路线。
因此,"atomic" 读取它的意愿取决于应用程序。例如:
应用程序协议应规定消息应以长度字节作为前缀。长度字节通知对等方感兴趣的应用程序数据的大小。当然,应用程序应该知道两字节长度指示符何时开始。
[2 byte msg length][Data bytes of interest]
基于此信息,正在读取的应用程序应采取行动。它应该轮询套接字,直到它接收到所有由 msg 长度字节指示的字节。然后才处理数据。
如果您需要 "atomic" 读取而不是部分读取,您可以在 recv
中使用 MSG_PEEK 标志。这不应从套接字缓冲区中删除数据。应用程序查看套接字,根据 return 值查看套接字缓冲区中是否包含所需数量的数据。
ret = recv(sd, buf, MAX_CALL_DATA_SIZE, MSG_PEEK);
我正在实现一个 TCP/IP 服务器应用程序,它在边缘触发模式下使用 epoll 并执行非阻塞套接字操作。客户端正在使用没有 epoll 的简单阻塞操作。
我不知道如何在服务器端实现 "atomic reads"。要解释我对 "atomic read" 的意思,请看这个简单阻塞操作的例子:
- 客户端和服务器都使用 64K 缓冲区。 (在应用程序级别。他们不更改内核级别套接字缓冲区。)
- 客户端单次写入12K数据。
- 服务器读取它。在这种情况下,当缓冲区相同时,它总是读取整个 12K。所以它不能只读它的一半。这就是我所说的 "atomic".
但在 epoll + 非阻塞操作的情况下,可能会发生这种情况:
- 客户端和服务器都使用 64K 缓冲区。 (在应用程序级别。他们不更改内核级别套接字缓冲区。)
- 客户端单次写入12K数据。
- 6K到达服务器
- epoll 告诉应用程序数据已到达套接字
- 应用程序使用非阻塞操作将 6K 读入缓冲区。
- 重复读取时,returns EAGAIN / EWOULDBLOCK。
在这种情况下,读取不是 "atomic"。不能保证当数据是通过一次写入操作写入时,读取将 return 整体,一片。
是否可以知道数据何时是部分数据?我知道一种解决方案是始终将数据大小附加到开头,或者另一种可能是始终关闭并重新打开连接,但我不想这样做:因为我认为内核必须知道不是完整的"package"(那个单元怎么叫 BTW?)到了,因为它保证了阻塞操作的原子性。
非常感谢!
TCP 是基于流的,而不是面向消息的。即使在阻塞套接字的情况下,您也不能保证应用程序发送的内容会一次性完成。 TCP 将决定自己的路线。
因此,"atomic" 读取它的意愿取决于应用程序。例如:
应用程序协议应规定消息应以长度字节作为前缀。长度字节通知对等方感兴趣的应用程序数据的大小。当然,应用程序应该知道两字节长度指示符何时开始。
[2 byte msg length][Data bytes of interest]
基于此信息,正在读取的应用程序应采取行动。它应该轮询套接字,直到它接收到所有由 msg 长度字节指示的字节。然后才处理数据。
如果您需要 "atomic" 读取而不是部分读取,您可以在 recv
中使用 MSG_PEEK 标志。这不应从套接字缓冲区中删除数据。应用程序查看套接字,根据 return 值查看套接字缓冲区中是否包含所需数量的数据。
ret = recv(sd, buf, MAX_CALL_DATA_SIZE, MSG_PEEK);