GJS中Gio套接字类实现Unix域套接字监听的问题求助
GJS中Unix域套接字监听与TlsConnection实现
问题分析
- Gio.Socket创建的连接自动关闭:核心原因是未在主循环中保留连接对象的引用,或未正确处理IO流的生命周期。手动用UnixInputStream可行,但可通过Gio的流封装简化处理。
- 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
相关产品推荐
相关产品推荐

