最近在使用unix套接字做IPC,发送者和接收者用户态的缓存大小都是128K。
发现,在发送端有很大数据量的情况下,接收端接收128K数据,但是只返回了22K数据。
ret = recv(fd, buf, 128*1024, 0);
开始以为是内核没有把全部数据返回。于是写个程序测试:
1 发送端全速发送,有个可选的参数siz,指定单次send的大小。
static void tx(int fd, int siz) { int v = 0; socklen_t opt_len = sizeof(v); getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &v, &opt_len); printf("send buffer size %d\n", v); while (1) { int ret; ret = send(fd, buf, siz, MSG_NOSIGNAL); if (ret < 0) { fprintf(stderr, "send() failed: %m\n"); break; } else if (ret == 0) { fprintf(stderr, "send() return 0\n"); break; } } }
2 接收端1秒接收一次,一次接收128K,接收前打印下缓存里的大小:
static void rx(int fd) { int cli; int v = 0; socklen_t len = sizeof(v); cli = accept(fd, NULL, NULL); if (fd < 0) { fprintf(stderr, "accept fd failed: %m\n"); return; } getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &v, &len); printf("recv buffer size %d\n", v); while (1) { int ret; sleep(1); ioctl(cli, SIOCINQ, &v); printf("not read %d bytes\n", v); ret = recv(cli, buf, bufsiz, 0); if (ret < 0) { fprintf(stderr, "recv() failed: %m\n"); break; } else if (ret == 0) { fprintf(stderr, "recv() return 0\n"); break; } printf("rx %d bytes\n", ret); } }
发现,当包大小比较小时,比如100个字节,收不满128K。
$ ./a.out 100 msg siz 100 recv buffer size 212992 send buffer size 212992 not read 27800 bytes rx 27800 bytes not read 27800 bytes rx 27800 bytes not read 27800 bytes rx 42300 bytes not read 27800 bytes rx 27800 bytes not read 27800 bytes rx 31700 bytes not read 27800 bytes rx 30200 bytes not read 27800 bytes rx 27800 bytes ^C
unix套接字的默认缓存是208KiB。发送端不限制速度,且和接收端再同一台主机。按理说,接收缓存很快就会填满208KiB。但是为啥一次只能接收到20多K的数据呢?
经过思考加阅读内核源码。终于理解了。
unix套接字在内核是用skb存储一个个包的。它并不是一个线性缓存。它计算缓存大小时,包含了skb的头部等额外负载。
因此实际的用户数据是小于缓存大小的。包越小,有效数据越少。比如包大小为1时。
$ ./a.out 1 msg siz 1 recv buffer size 212992 send buffer size 212992 not read 278 bytes rx 278 bytes not read 278 bytes rx 322 bytes not read 278 bytes rx 278 bytes
一次只能收278个字节。这也意味着一个skb的额外负载大概是:766个字节。很大很大了。