PHP websocket using socket_recv() - will I ever receive a partial frame?
我正在用PHP(使用
我对如何传递Websocket信息的理解如下:
我想了解的是使用
具体来说,我需要在多大程度上担心碎片化?
为了帮助解释我的问题,以下是上述过程的图解形式:
1 2 3 4 5 | 1. Web app (messages): [Message_1][Message_2] 2. Browser (frames) : [Messag][e_1][Messag][e_2] 3. TCP send (packets) : [Mess][ag][e_1][Mess][ag][e_2] 4. TCP recv (packets) : [ag][Mess][e_2][ag][Mess][e-1] 5. socket_recv : ??? |
如果我在循环中调用
1 2 | socketrecv: [Message_1] socketrecv: [Message_2] |
还是一个完整的
1 2 3 4 | socketrecv: [Messag] socketrecv: [e_1] socketrecv: [Messag] socketrecv: [e_2] |
还是实际上是一个任意的
1 2 3 4 5 | socketrecv: [Messag socketrecv: e_1][Mess socketrecv: socketrecv: ag socketrecv: e_2] |
或者是其他东西?
我很高兴将各种
哦,好吧,我以前在C ++代码中使用过websockets,是的,由于TCP协议的工作原理,它可能会碎片化。
Websocket有两种类型的数据流:Hixie(旧),Hybi(新),也有版本,例如hybi-13 .. hybi-17。
但这没关系,因为您的问题是socket_recv()仅从缓冲区中检索数据,缓冲区取决于您的网络设置(MTU)以及操作系统和硬件...如此复杂。.甚至可以读取1字节也是16MB。
因此,如果您想在PHP中实现websocket,则必须阅读并解析框架并获取其大小,如果有可用的大小,则可以对其进行剪切和处理,如果没有更多内容,请继续。
接收不完整的一帧或一帧以及不完整的下一帧是很有可能的。
因此,如果您必须将剩余数据保留在缓冲区(又称为变量)中,则必须跨步查找帧的开头并计算其大小,然后向前迈进,并且必须在其后追加下一个读数。
但是首先,您必须至少读取4个字节。 (标题大小)
众所周知,hybi协议使用"压缩",因此对于有效负载而言,帧字节可以根据其整数类型而有所不同。
请参见下面的C代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 | payload_length = frame[1] & 0x7f; if (payload_length < 126) { hdr_length = 2; payload_length = payload_length; // FYI / DUMMY } else if (payload_length == 126) { payload_length = (frame[2] << 8) + frame[3]; hdr_length = 4; } else .... |
我非常擅长联网,并且我当天写了很多Twisted联网代码(Python中的网络套接字库)
我在家中有一本书" Unix网络编程第三版",我偷看了一下……几年前我从图书馆购买了这本书,因为据说这是TCP上的"权威" / IP堆栈及其规范。
摘自第2章"传输层"
两个主机之间的路径中最小的
...
当要从接口发送IP数据报时,如果数据报的大小超过链接MTU,则由IPV4 / IPV6堆栈执行分段。碎片通常不会重新组装,直到它们到达最终目的地。在IPv4上,主机和路由器都可以执行分段。在IPv6上,仅主机可以执行分段。
...
IPv4和IPv6定义了最小的重组缓冲区大小,这是我们保证任何实现都必须支持的最小数据报大小。对于IPv4,这是576个字节。
应用程序
任何应用程序或IPv4主机堆栈都保证在应用程序级别始终接收
您的应用程序可能会收到较少的数据,因为可能发送的数据较少,这就是为什么套接字服务器可以知道消息何时结束以及新消息何时开始的原因。
典型的套接字服务器
1 2 3 | ssize_t numBytesRcvd = recv(clntSocket, buffer, BUFSIZE, 0) if (numBytesRcvd < 0 ) // 0 indicates end of stream exit(1); |
在上面的代码段中,该进程从操作系统接收了MOST
实际上,在堆栈的较低级别发生什么的整个讨论对您的目的实际上是没有意义的。
当您在PHP中调用
1 2 3 4 5 6 7 8 | if ((retval = recv(php_sock->bsd_socket, ZSTR_VAL(recv_buf), len, flags)) < 1) { zend_string_efree(recv_buf); ZEND_TRY_ASSIGN_REF_NULL(buf); } else { ZSTR_LEN(recv_buf) = retval; ZSTR_VAL(recv_buf)[ZSTR_LEN(recv_buf)] = '\\0'; ZEND_TRY_ASSIGN_REF_NEW_STR(buf, recv_buf); } |
您也可以看到它也尝试接收
然后使用功能
真正的答案
任何套接字应用程序都需要一种方法来区分消息的长度和结构。
根据您的要求,消息的大小可以是任意的,消息本身可以是任意的。
这就是
在您的情况下,您想从客户端发送一条消息并在服务器上接收它,并知道消息何时结束,然后可能无限期地重复该循环。
您实际上要问的是:
我该如何为数据报的结构构建规范并知道数据报何时结束-您需要协议!
这是最简单的协议如何工作的基础:
重要的部分是标头的
假设您已将
将标头
此设计假定CLIENT根据我们的规范构造了一个至少5个字节的适当组成的标头。
...
如果没有,我们还有另一堆问题要处理。即,丢弃格式错误的消息并查找下一个格式正确的消息。
不幸的是,这篇文章的纠错对话会花很长时间。
如何在现实生活中做到这一点
上面是一个有趣的学术练习,可以帮助我们了解TCP套接字客户端和服务器的工作方式-但在其之上构建自己的应用程序并非最简单。
幸运的是,其他人已经为我们完成了工作。
Wamp是为WebSocket设计的协议,可轻松定义消息格式并确保可靠地发送/接收消息。
Wamp的PHP实现称为Ratchet
与滚动自己的协议相比,这些工具将是更可取的,因为它们可以自行处理格式错误的消息和错误恢复。
祝好运!
好。
它在Internet上有完整的文档说明。TCP是可靠的且面向连接。
您会收到完整且正确顺序的消息-否则永远不会。消息的每个段都必须由接收方确认,如果未完成,则再次发送该段(几次)。消息的重新组装由TCP堆栈完成,因此您不必担心数据包顺序或应用程序中的数据包丢失……您将获得完整的消息或错误。
不要误解缓冲区...在调用socket_recv()时,您将提供一个缓冲区,但这与基础TCP堆栈使用的缓冲区不同。
UDP是计数器的一部分,您必须在其中注意所有详细信息。您可能会以错误的顺序,多次,损坏/不完整或其他有缺陷的方式获取数据报,甚至永远不会!意思是:您可能最终会得到一个包含缺口的序列,并且必须忍受它。