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

GJS中Gio套接字类实现Unix域套接字监听的问题求助

GJS中Unix域套接字监听与TlsConnection实现

问题分析

  1. Gio.Socket创建的连接自动关闭:核心原因是未在主循环中保留连接对象的引用,或未正确处理IO流的生命周期。手动用UnixInputStream可行,但可通过Gio的流封装简化处理。
  2. Gio.SocketListener返回null:大概率是地址格式错误、权限问题,或是使用同步accept()未适配GJS主循环——同步方法无连接时会阻塞,更适合用异步API处理。

正确实现示例

方式1:基于Gio.Socket的Unix域套接字监听

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

function createUnixSocketListener(socketPath) {
    // 删除已有套接字文件
    try {
        Gio.File.new_for_path(socketPath).delete(null);
    } catch (e) {}

    // 创建套接字
    const socket = new Gio.Socket({
        family: Gio.SocketFamily.UNIX,
        type: Gio.SocketType.STREAM,
        protocol: Gio.SocketProtocol.DEFAULT,
    });

    // 绑定地址并监听
    const address = Gio.UnixSocketAddress.new(socketPath);
    socket.bind(address, true);
    socket.listen(10);

    // 异步接受连接
    const acceptCallback = (socket, res) => {
        try {
            const clientSocket = socket.accept_finish(res);
            if (!clientSocket) return;

            // 封装为IO流
            const inputStream = clientSocket.get_input_stream();
            const outputStream = clientSocket.get_output_stream();

            // 监听输入流数据
            const readCallback = (stream, res) => {
                try {
                    const [bytesRead, buffer] = stream.read_finish(res);
                    if (bytesRead <= 0) {
                        // 连接关闭,释放资源
                        inputStream.close(null);
                        outputStream.close(null);
                        clientSocket.close(null);
                        return;
                    }

                    const message = new TextDecoder().decode(buffer);
                    console.log(`Received: ${message}`);

                    // 回复客户端
                    const reply = new TextEncoder().encode(`Echo: ${message}`);
                    outputStream.write_all(reply, null);

                    // 继续监听下一次数据
                    inputStream.read_async(new GLib.Bytes(1024), GLib.PRIORITY_DEFAULT, null, readCallback);
                } catch (e) {
                    console.error(`Read error: ${e.message}`);
                }
            };

            inputStream.read_async(new GLib.Bytes(1024), GLib.PRIORITY_DEFAULT, null, readCallback);

            // 继续接受下一个连接
            socket.accept_async(GLib.PRIORITY_DEFAULT, null, acceptCallback);
        } catch (e) {
            console.error(`Accept error: ${e.message}`);
        }
    };

    socket.accept_async(GLib.PRIORITY_DEFAULT, null, acceptCallback);

    // 保留套接字引用,防止被GC回收
    return socket;
}

// 启动监听
const socket = createUnixSocketListener('/tmp/test-unix-socket');

// 运行主循环
GLib.MainLoop.new(null, false).run();

关键注意点:

  • 必须保留clientSocket、流对象的引用,否则会被GC回收导致连接自动关闭
  • 使用异步API适配GJS主循环,避免阻塞
  • 连接关闭时要正确释放所有资源

方式2:基于Gio.SocketListener的Unix域套接字监听

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

function createSocketListener(socketPath) {
    // 删除已有套接字文件
    try {
        Gio.File.new_for_path(socketPath).delete(null);
    } catch (e) {}

    const listener = new Gio.SocketListener();
    const address = Gio.UnixSocketAddress.new(socketPath);
    listener.add_address(
        address,
        Gio.SocketType.STREAM,
        Gio.SocketProtocol.DEFAULT,
        null
    );

    // 异步接受连接
    const acceptCallback = (listener, res) => {
        try {
            const [clientSocket, clientAddress] = listener.accept_finish(res);
            if (!clientSocket) return;

            console.log(`New connection from ${clientAddress.get_path()}`);

            // 处理流逻辑(同方式1)
            const inputStream = clientSocket.get_input_stream();
            const outputStream = clientSocket.get_output_stream();

            const readCallback = (stream, res) => {
                try {
                    const [bytesRead, buffer] = stream.read_finish(res);
                    if (bytesRead <= 0) {
                        inputStream.close(null);
                        outputStream.close(null);
                        clientSocket.close(null);
                        return;
                    }

                    const message = new TextDecoder().decode(buffer);
                    console.log(`Received: ${message}`);
                    const reply = new TextEncoder().encode(`Echo: ${message}`);
                    outputStream.write_all(reply, null);

                    inputStream.read_async(new GLib.Bytes(1024), GLib.PRIORITY_DEFAULT, null, readCallback);
                } catch (e) {
                    console.error(`Read error: ${e.message}`);
                }
            };

            inputStream.read_async(new GLib.Bytes(1024), GLib.PRIORITY_DEFAULT, null, readCallback);

            // 继续接受下一个连接
            listener.accept_async(null, acceptCallback);
        } catch (e) {
            console.error(`Accept error: ${e.message}`);
        }
    };

    listener.accept_async(null, acceptCallback);

    return listener;
}

// 启动监听
const listener = createSocketListener('/tmp/test-socket-listener');
GLib.MainLoop.new(null, false).run();

关键注意点:

  • SocketListener的accept_finish返回两个值:客户端套接字和地址,需正确解构
  • 保留listener和客户端资源的引用,防止GC回收

Unix流结合Gio.TlsConnection实现

要在Unix域套接字上使用TLS,需先将套接字封装为Gio.IOStream,再传入Gio.TlsConnection。完整示例如下:

import Gio from 'gi://Gio';
import GLib from 'gi://GLib';

// 加载服务器证书和密钥(需提前生成)
const certFile = Gio.File.new_for_path('/path/to/server.crt');
const keyFile = Gio.File.new_for_path('/path/to/server.key');

const cert = Gio.TlsCertificate.new_from_files(certFile, keyFile);

function createTlsUnixSocketListener(socketPath) {
    try {
        Gio.File.new_for_path(socketPath).delete(null);
    } catch (e) {}

    const socket = new Gio.Socket({
        family: Gio.SocketFamily.UNIX,
        type: Gio.SocketType.STREAM,
        protocol: Gio.SocketProtocol.DEFAULT,
    });

    const address = Gio.UnixSocketAddress.new(socketPath);
    socket.bind(address, true);
    socket.listen(10);

    const acceptCallback = (socket, res) => {
        try {
            const clientSocket = socket.accept_finish(res);
            if (!clientSocket) return;

            // 将套接字封装为IOStream
            const ioStream = new Gio.SocketIOStream({ socket: clientSocket });

            // 创建TLS连接
            const tlsConnection = new Gio.TlsConnection({
                base_io_stream: ioStream,
                server: true, // 作为服务器端
            });
            tlsConnection.set_certificate(cert);

            // 启动TLS握手
            tlsConnection.handshake_async(GLib.PRIORITY_DEFAULT, null, (conn, res) => {
                try {
                    conn.handshake_finish(res);
                    console.log('TLS handshake successful');

                    const inputStream = conn.get_input_stream();
                    const outputStream = conn.get_output_stream();

                    // 处理TLS流数据
                    const readCallback = (stream, res) => {
                        try {
                            const [bytesRead, buffer] = stream.read_finish(res);
                            if (bytesRead <= 0) {
                                inputStream.close(null);
                                outputStream.close(null);
                                conn.close(null);
                                clientSocket.close(null);
                                return;
                            }

                            const message = new TextDecoder().decode(buffer);
                            console.log(`TLS Received: ${message}`);
                            const reply = new TextEncoder().encode(`TLS Echo: ${message}`);
                            outputStream.write_all(reply, null);

                            inputStream.read_async(new GLib.Bytes(1024), GLib.PRIORITY_DEFAULT, null, readCallback);
                        } catch (e) {
                            console.error(`TLS Read error: ${e.message}`);
                        }
                    };

                    inputStream.read_async(new GLib.Bytes(1024), GLib.PRIORITY_DEFAULT, null, readCallback);
                } catch (e) {
                    console.error(`TLS handshake error: ${e.message}`);
                    ioStream.close(null);
                    clientSocket.close(null);
                }
            });

            socket.accept_async(GLib.PRIORITY_DEFAULT, null, acceptCallback);
        } catch (e) {
            console.error(`Accept error: ${e.message}`);
        }
    };

    socket.accept_async(GLib.PRIORITY_DEFAULT, null, acceptCallback);

    return socket;
}

// 启动TLS监听
const tlsSocket = createTlsUnixSocketListener('/tmp/test-tls-unix-socket');
GLib.MainLoop.new(null, false).run();

关键注意点:

  • 提前用openssl生成自签名服务器证书和密钥
  • TlsConnection必须在握手完成后才能进行读写操作
  • 所有资源要在连接关闭时正确释放

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 00:39:53