1

我已经阅读了这个关于套接字同步的问题,但我仍然不明白。

最近我正在开发一个相对简单的客户端/服务器应用程序,其中通信通过 tcp 套接字进行。客户端是用 PHP 编写的,使用类似 C 的函数(尤其是fsockopenfgetc)PHP 提供与套接字交互,服务器是用 node.js 编写的,Stream用于输出数据。

协议非常简单,消息只是一个以 0 字节字符结尾的字符串。

基本上它是这样工作的:

SERVER: Message 1
CLIENT: Ack 1

SERVER: Message 2
CLIENT: Ack 2

....

这真的很好用,因为我的客户一次处理一条消息,方法是从套接字读取一个字符一个字符,直到遇到一个表示消息结束的 0 字节。然后客户端向服务器写回它已成功接收到消息(这就是Ack <message id>部分)。

现在发生了这样的事情:

SERVER: Message 1
CLIENT: Ack 1

SERVER: Message 2
CLIENT: Ack 2

SERVER: Message 3
        Message 4
        Message 5
        Message 6
CLIENT: <DOH!>
....

这意味着服务器意外地在一个“批次”中向客户端发送了多条消息,尽管每条消息都是服务器上的单个stream.write(...)操作。似乎消息在某处缓冲,然后立即发送给客户端。我的客户端代码无法在没有响应的情况下处理套接字中的多条消息Ack,因此它切断了 id 3 之后的剩余消息。

所以我的问题是:

  • 套接字的读写同步程度如何?从上面的问题我了解到一个套接字基本上是两个单向管道,这意味着它们根本不同步?
  • 一些消息以简单的“一条消息一确认”方式发送到我的客户端,然后突然将多条消息写入流,怎么会发生这种情况?
  • 如果套接字以阻塞/非阻塞方式打开,它实际上会改变图片吗?

我使用 PHP 5.4 和节点 0.6.x 在 Ubuntu VM 上对此进行了测试(因此没有负载或任何可能引起奇怪行为的东西)。

4

2 回答 2

3

TCP 是双向流的抽象,因此没有消息的概念,也不能保留消息边界。无法保证多个 send() 或 recv() 调用将如何映射到 TCP 数据包。您应该将 send() 视为多次调用它等同于调用一次并连接所有数据。更重要的是,在接收时,您应该确保您的代码以完全相同的方式解释传入的数据,无论它是如何在单独的 recv() 调用中拆分的。

要正确接收,您可以使用存储不完整消息的缓冲区。但是请注意,当缓冲区中有不完整的消息时,下一个 recv() 调用可能会完成当前消息,提供零个或多个完整消息,并且可能是另一个不完整消息的一部分。

阻塞或非阻塞模式在这里不会改变任何东西——它只是关于你的应用程序与操作系统接口的方式。

于 2012-05-19T13:27:56.293 回答
0

有两个同步概念需要处理:

  1. send()or的(通常)同步操作recv()
  2. 一个进程发送消息的异步方式以及另一个进程处理消息的方式。

如果可以,请尽量避免使客户端和服务器处于进程同步的“锁定步骤”中的设计。那是自找麻烦。如果其中一个进程意外关闭怎么办?另一个进程/线程可能会挂在一个recv()永远不会到来的地方。对于您的设计而言,期望每条消息最终都得到确认是一回事,但对于您的设计而言,期望只能发送一条消息,然后必须先确认,然后才能发送另一条消息,则完全是另一回事。

考虑一下:

Server: send 1
Client: ack 1
Server: send 2
Server: send 3
Client: ack 2
Server: send 4
Client: ack 3
Client: ack 4

能够适应这种情况的设计比预期的要好:

Server: send 1
Client: ack 1
Server: send 2
Client: ack 2
Server: send 3
Client: ack 3
Server: send 4
Client: ack 4
于 2013-07-09T22:31:09.750 回答