unix域套接字sendto错误码分析
1 参考资料
https://blog.csdn.net/guotianqing/article/details/80808169
https://blog.csdn.net/guotianqing/article/details/80795353
https://www.cnblogs.com/skynet/archive/2010/12/04/1881236.html
2 问题现象
unix域数据报套接字实现线程间通信,在非阻塞模式下出现了send失败的现象
单次发送最大字节数超过一个阈值**(160K?)**后,会报错“sendto error: Message too long”
发送模式处于非阻塞模式下会报错“sendto error: Resource temporarily unavailable”
unix socket单次发送数据包大小影响因素:
- 内核配置的socket最大缓冲区大小:/proc/sys/net/core/wmem_max,我的机器为163840(160k),它决定了setsockopt (SO_SNDBUF)可以设定的最大值
- socket能够保存的未读取的数据包的最大个数:/proc/sys/net/unix/max_dgram_qlen,我的机器为10
- 数据包的发送需要连续的内存块,因此还取决于内核能够分配多大的连续内存块(影响的因素较多,如系统IO负载等)
帖子最后对实际极限值给出了一个估计
wmem_max ~8Mb
unix_dgram_qlen ~32
内核对内存大小的限制可通过以下接口查看:
/proc/slabinfo | grep kmalloc
按照这种方法修改了内核参数,只是缓解了错误出现的时机,并没有真正解决问题。
3 UNIX域套接字特点——AF_LOCAL通信的效率更高
- unix域套接字仅仅是复制数据,不执行协议处理 - 不需要添加或删除网络报头 - 不计算校验和,不产生序列号 - 不需要发送确认报文
4 测试程序
服务端、客户端由父进程生成两个子线程产生,套接字为SOCK_DGRAM类型, socket(AF_LOCAL, SOCK_DGRAM, 0); 服务端在recvfrom后主动休眠3秒 客户端在sendto后休眠1秒
可以发现客户端的发送缓冲区不断积压,直至爆满,最终导致内核返回 EAGIN
附:测试程序
#include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#define MAXLEN (1024 * 1024 * 1)/* 单次最大发送缓冲区为1M */
#define UNIX_DGRAM_PATH "/tmp/unix_test_path"
static int get_file_content_and_len(char *file, char *buf, int *len)
{
int ret;
struct stat st; // 存储文件信息
FILE *fp;
if (stat(file, &st)) { // 使用stat函数获取指定文件的信息
printf("stat error: %s\n", strerror(errno));
return -1; // error: 文件信息获取失败,即文件不存在
}
*len = st.st_size; // 获取文件大小
if (*len >= MAXLEN) {
printf("file too long, len[%d]\n", *len);
return -1; // error: 文件大小超过最大长度
}
printf("file len=%d\n", *len);
fp = fopen(file, "r"); // 只读形式打开文件
if (fp == NULL) {
printf("fopen error: %s\n", strerror(errno));
return -1; // error: 文件打开失败
}
if (fread(buf, 1, *len, fp) != *len) { // 从文件读取数据到缓冲区
printf("fread error: %s\n", strerror(errno));
fclose(fp);
return -1; // error: 文件读取失败
}
fclose(fp);
return 0;
}
static void *recv_test(void *arg)
{
int ret, sockfd;
struct sockaddr_un servaddr;
char buf[MAXLEN];
int len = sizeof(buf);
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (sockfd < 0) {
printf("socket error: %s\n", strerror(errno));
return NULL;
}
unlink(UNIX_DGRAM_PATH); // 删除文件
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strncpy(servaddr.sun_path, UNIX_DGRAM_PATH, sizeof(servaddr.sun_path) - 1);
ret = bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 服务端,绑定socket地址
if (ret) {
printf("bind error: %s\n", strerror(errno));
return NULL;
}
printf("begin recv.\n");
while(1) {
if (recvfrom(sockfd, buf, len, 0, NULL, NULL) < 0) { // 最后两个参数:存储发送方地址信息结构体、信息长度指针,不需要时可传入NULL
printf("recvfrom error: %s\n", strerror(errno));
}
printf("recvfrom len: %d\n", strlen(buf));
sleep(3);
}
}
static void *send_test(char *filename)
{
char buf[MAXLEN] = { 0 };
int len;
struct sockaddr_un servaddr;
socklen_t addrlen = sizeof(servaddr);
int fd;
int ret;
ret = get_file_content_and_len(filename, buf, &len); // 获取文件内容、长度
if (ret) {
printf("get file content and len error[%d]\n", ret);
return NULL;
}
fd = socket(AF_LOCAL, SOCK_DGRAM, 0); // 创建套接字
if (fd < 0) {
printf("socket error: %s\n", strerror(errno));
return NULL;
}
int buffer_size = MAXLEN;
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof(buffer_size)) == -1) {
perror("setsockopt");
return NULL;
}
/* O_NONBLOCK */
if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { // 对文件描述符进行控制操作
printf("fcntl error: %s\n", strerror(errno)); // 将文件描述符设置为非阻塞模式
return NULL;
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strncpy(servaddr.sun_path, UNIX_DGRAM_PATH, sizeof(servaddr.sun_path) - 1); // 目的地址信息
printf("begin send. buf_len=%d, len=%d\n", strlen(buf), len);
while (1) {
ret = sendto(fd, buf, len, 0, (struct sockaddr *)&servaddr, addrlen); // 发送消息
/* 获取发送缓冲区大小 */
int outq_len;
if (ioctl(fd, SIOCOUTQ, &outq_len) < 0) {
perror("ioctl");
return NULL;
}
printf("Send queue length: %d\n", outq_len);
if (ret != len) {
printf("sendto error: %s\n", strerror(errno));
}
/* 增加延时,用于测试 */
sleep(1);
}
}
int main(int argc, char **argv)
{
int ret;
pthread_t ntid;
if (argc != 2) {
printf("Usage: %s filename\n", argv[0]);
return -1;
}
/* 启动recv线程 */
if (pthread_create(&ntid, NULL, (void *)recv_test, NULL)) { // 传入线程标识符指针、线程属性、线程函数和传递给线程函数的参数
printf("pthread_create recv_test error: %s\n", strerror(errno));
return -1;
}
pthread_detach(ntid);
/* 延时保证recv就绪 */
sleep(1);
/* 启动send线程 */
if (pthread_create(&ntid, NULL, (void *)send_test, argv[1])) {
printf("pthread_create sned_test error: %s\n", strerror(errno));
return -1;
}
pthread_detach(ntid);
while (1) {
pause();
}
return 0;
}