Sirius
Sirius

目录

unix域套接字sendto错误码分析

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

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

按照这种方法修改了内核参数,只是缓解了错误出现的时机,并没有真正解决问题。

- unix域套接字仅仅是复制数据,不执行协议处理 - 不需要添加或删除网络报头 - 不计算校验和,不产生序列号 - 不需要发送确认报文

服务端、客户端由父进程生成两个子线程产生,套接字为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;
}