You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go向C发送文件描述符异常:抽象UNIX域套接字客户端挂起

抽象UNIX域套接字发送文件描述符时C客户端挂起问题排查与解决

问题场景

尝试通过抽象UNIX域套接字从Go程序向C程序发送文件描述符,但C客户端始终在recvmsg调用处挂起,无法获取目标描述符。Go代码使用标准库实现,C代码直接调用系统接口。

核心问题分析

1. 抽象套接字地址不匹配

Go的net.ResolveUnixAddr处理抽象UNIX套接字时,会自动将路径开头的@替换为null字节(\0),因此Go端实际使用的抽象套接字名称是\0myabstractsocket。而原C代码直接将包含@的字符串复制到sockaddr_un.sun_path,再手动将第一个字节设为\0,导致实际套接字名称变为\0@myabstractsocket,两边地址不匹配,无法建立有效通信。

2. UDP模式通信逻辑错误

Unixgram(数据报模式,UDP)是无连接协议,服务器需要先接收客户端的消息(哪怕是空消息)来获取客户端地址,之后才能向客户端发送数据或文件描述符。原Go代码启动后直接向自身监听地址发送消息,没有目标客户端地址,消息根本无法被C客户端接收。

修复后的代码

Go端代码

package main

import (
    "fmt"
    "net"
    "os"
    "syscall"
    "os/signal"
)

func main() {
    socketAddr := "@myabstractsocket"

    // 创建抽象UNIX域套接字
    addr, err := net.ResolveUnixAddr("unixgram", socketAddr)
    if err != nil {
        fmt.Println("解析地址失败:", err)
        return
    }

    conn, err := net.ListenUnixgram("unixgram", addr)
    if err != nil {
        fmt.Println("监听套接字失败:", err)
        return
    }
    defer conn.Close()

    fmt.Println("服务器监听中:", socketAddr)

    // 接收客户端消息,获取客户端地址
    buf := make([]byte, 1)
    clientAddr, err := conn.ReadFromUnix(buf)
    if err != nil {
        fmt.Println("读取客户端消息失败:", err)
        return
    }
    fmt.Println("已获取客户端地址")

    // 打开要发送的文件
    file, err := os.Open("file.txt")
    if err != nil {
        fmt.Println("打开文件失败:", err)
        return
    }
    defer file.Close()

    // 获取文件描述符
    fileDescriptor := file.Fd()

    // 向客户端发送文件描述符
    fdMsg := syscall.UnixRights(int(fileDescriptor))
    _, _, err = conn.WriteMsgUnix(nil, fdMsg, clientAddr)
    if err != nil {
        fmt.Println("发送文件描述符失败:", err)
        return
    }

    fmt.Println("文件描述符发送成功")

    done := make(chan os.Signal, 1)
    signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)
    fmt.Println("按ctrl+c退出")
    <-done
}

C端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>

#define BUFFER_SIZE 256

int main()
{
    // 对应Go端的@myabstractsocket(Go会自动将@转为\0)
    const char *socketName = "myabstractsocket";

    // 创建套接字
    int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址
    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    // 抽象套接字标识:第一个字节设为0
    addr.sun_path[0] = 0;
    // 复制实际套接字名称到sun_path[1]开始的位置
    strncpy(&addr.sun_path[1], socketName, sizeof(addr.sun_path) - 2);

    // 计算抽象套接字地址长度
    socklen_t addrLen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(socketName);
    if (connect(sockfd, (struct sockaddr *)&addr, addrLen) == -1)
    {
        perror("connect");
        exit(EXIT_FAILURE);
    }

    // 向服务器发送空消息,触发服务器获取客户端地址
    if (send(sockfd, "", 0, 0) == -1)
    {
        perror("send");
        exit(EXIT_FAILURE);
    }

    // 准备接收文件描述符
    char buffer[BUFFER_SIZE];
    struct msghdr msg;
    struct iovec iov;
    union
    {
        struct cmsghdr cm;
        char control[CMSG_SPACE(sizeof(int))];
    } control_un;
    struct cmsghdr *cmptr;

    iov.iov_base = buffer;
    iov.iov_len = sizeof(buffer);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_name = NULL;
    msg.msg_namelen = 0;
    msg.msg_control = control_un.control;
    msg.msg_controllen = sizeof(control_un.control);
    msg.msg_flags = 0;

    fprintf(stderr, "等待文件描述符...\n");

    ssize_t recvLen = recvmsg(sockfd, &msg, 0);
    if (recvLen == -1)
    {
        perror("recvmsg");
        exit(EXIT_FAILURE);
    }

    fprintf(stderr, "收到消息,开始提取文件描述符...\n");

    // 提取文件描述符
    int receivedFd = -1;
    for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL; cmptr = CMSG_NXTHDR(&msg, cmptr))
    {
        if (cmptr->cmsg_level == SOL_SOCKET && cmptr->cmsg_type == SCM_RIGHTS)
        {
            receivedFd = *((int *)CMSG_DATA(cmptr));
            break;
        }
    }

    if (receivedFd == -1)
    {
        fprintf(stderr, "未接收到文件描述符\n");
        exit(EXIT_FAILURE);
    }

    // 测试文件描述符有效性
    char testBuf[1024];
    ssize_t readLen = read(receivedFd, testBuf, sizeof(testBuf) - 1);
    if (readLen == -1)
    {
        perror("读取接收的文件描述符失败");
        close(receivedFd);
        exit(EXIT_FAILURE);
    }
    testBuf[readLen] = '\0';
    fprintf(stderr, "从接收的文件描述符读取内容:\n%s\n", testBuf);

    close(receivedFd);
    close(sockfd);
    return 0;
}

修复说明

  1. 地址匹配修正:Go端保留@前缀让标准库自动处理,C端手动设置syn_path[0]为0并复制不含@的名称,保证两边抽象套接字名称一致。
  2. UDP通信逻辑修正:C端先发送空消息,Go端接收后获取客户端地址,再针对性发送文件描述符,符合无连接协议的通信流程。
  3. 地址长度计算优化:使用offsetof正确计算抽象套接字的地址长度,避免原代码中错误的长度计算方式。

内容的提问来源于stack exchange,提问作者Mitar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 20:17:00