解码器在使用过程中,开机容易出现 ztebw 向 vplayer 发送控制命令失败,时常返回 Resource temporarily unavailable 错误,导致一些关键的命令没有办法发送成功,造成字幕叠加不正确或者播放不成功,给使用带来一些不便。在解码器中, ztebw 和 vplayer 分别以进程形式存在,使用高效的 Unix Domain Socket 通信,用非阻塞模式。由于解码器同时支持 8 路视频点播,每一路视频播放都通过同一 Unix Domain Socket 发送,造成网络阻塞发送不成功,也不是没有有可能的。那原因究竟在哪呢?
先看看 Unix Domain Socket 这种 Socket 。按照网上的说法, UNIX Domain Socket 用于 IPC 更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。而且 UNIX Domain Socket 是全双工的, API 接口语义丰富,相比其它 IPC 机制有明显的优越性,目前已成为使用最广泛的 IPC 机制,比如 X Window 服务器和 GUI 程序之间就是通过 UNIX Domain Socket 通讯的。
有了这样的高帽,我们只能先把焦点放在应用上了。仔细分析启动过程中的消息发送过程。发现
这个过程本身是没有错的,但是解码器考虑到 vplayer 可能会中途退出重启,会主动通知 ztebw 进程,告诉 ztebw 自己已经启动了,会不断发送 VOD_INITED 消息,间隔时间非常短(这本身就有问题), ztebw 收到此消息的响应是 STB_CFG 消息。这个消息附带着解码器整个的配置消息。因此它非常大,一个消息附带着 4008Bytes ,这个消息连续发送肯定是不能接受的。用 getsockopt 查看,发送缓冲区默认是 64Kbytes 。如果不再考虑 vplayer 退出重启与 ztebw 握手的过程,不主动发送 VOD_INITED 消息,没有再出现发送不成功的现象。
问题虽然搞定了,但总觉得有些地方没有彻底弄清楚。这种发送失败的情况在平常异常情况中也会偶尔出现,而这些异常情况大都在正常运行过程中,是不会发送 STB_CFG 消息。
难道另有隐情。
1. 参数 max_dgram_qlen 的大小
[wlh@localhost ~]$ cat /proc/sys/net/unix/max_dgram_qlen
10
max_dgram_qlen limits how many datagrams can be queued on a unix domain socket's (SOCK_DGRAM) receive buffer. If a sender tries to send more datagrams, it blocks (in a blocking sendto) or returns error (in a non-blocking sendto). The default value is 10.
上面的解释说明这个参数是域通信 Socket 在数据报( UDP )方式下,队列里最大数据报个数。我们使用的正是这个方式。
// testUnixDomain.c 检测 /proc/sys/net/unix/max_dgram_qlen 的参数,
// 使用 echo “50” >/proc/sys/net/unix/max_dgram_qlen
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
unlink("toto");
int sock = socket(AF_UNIX, SOCK_DGRAM, 0);
if (sock < 0)
{
perror("socket");
exit(1);
}
struct sockaddr_un dest ;
dest.sun_family = AF_UNIX;
strcpy(dest.sun_path, "toto");
int ret = bind(sock, (struct sockaddr *)&dest, sizeof(dest));
if( ret == -1)
{
perror("bind error");
exit(1);
}
int bufsize = atoi(argv[1]);
char *buf = malloc(bufsize);
int count = 0;
while ( 1 )
{
int res = sendto(sock, buf, bufsize, MSG_DONTWAIT,
(struct sockaddr*)&dest, sizeof dest);
if (res < 0)
{
perror("sendto");
break;
}
++count;
}
printf("COUNT=%d/n", count);
return 0;
}
2. 缓冲区的迷惑
有了上面的参数,那缓冲区如何设置呢。缓冲区的设置跟普通 Socket 一样,通过 setsockopt 设置发送和接收缓冲区。
使用上面的程序分别运行。
[root@localhost TestProg]# ./testUnixDomain 4
sendto: Resource temporarily unavailable
COUNT=11
[root@localhost TestProg]# ./testUnixDomain 4000
sendto: Resource temporarily unavailable
COUNT=11
[root@localhost TestProg]# echo "50" >/proc/sys/net/unix/max_dgram_qlen
[root@localhost TestProg]# ./testUnixDomain 4
sendto: Resource temporarily unavailable
COUNT=51
[root@localhost TestProg]# ./testUnixDomain 4000
sendto: Resource temporarily unavailable
COUNT=26
从上面的测试可以看到,其实 Unix Domain Socket 的数据报方式的发送与两个因素有关,一是 /proc/sys/net/unix/max_dgram_qlen 中允许的最大数据报个数。
另一个是缓冲区的最大值,超过其中一个限制,都会出现 Resource temporarily unavailable 的错误。