如何通过PuTTY调用运行中Unity Linux无头服务器内部的C#公开函数?
首先明确一点:直接通过Linux命令调用已经运行的Unity服务器进程内部的C#公开函数是做不到的——因为Linux系统命令和Unity的C#运行时是完全独立的执行上下文,没有原生的系统命令能直接“穿透”到进程内部调用函数。你之前设想的./ServerLinux.x86_64 -call command只会启动一个新的服务器进程,而不是给正在运行的进程发指令。
不过我们可以通过「进程间通信(IPC)」的方式,让Unity服务器主动监听外部指令,当你在PuTTY里执行特定命令时,触发服务器内部的MyFunction()。下面给你几个适合Unity Linux无头服务器的可行方案:
方案1:监控本地指令文件(最简单易实现)
这个思路是让Unity服务器定期检查一个指定的本地文件,当你在PuTTY里往这个文件写入命令内容时,Unity检测到后就执行对应的函数。
Unity C#代码修改
在你的Server脚本中添加文件监控逻辑:
using System.IO; using UnityEngine; using System.Collections; public class Server : MonoBehaviour { // 指定要监控的命令文件路径(Linux下建议放在/tmp目录,无需权限) private readonly string _commandFilePath = "/tmp/unity_server_cmd.txt"; void Start() { StartCoroutine(StartChecking()); // 启动命令监控协程 StartCoroutine(CheckForExternalCommands()); } // 你要触发的目标函数 public void MyFunction() { Debug.Log("✅ 成功执行MyFunction!"); // 这里写你的函数逻辑 } // 定期检查命令文件 private IEnumerator CheckForExternalCommands() { while (true) { if (File.Exists(_commandFilePath)) { // 读取命令并去除多余空白符 string command = File.ReadAllText(_commandFilePath).Trim(); if (!string.IsNullOrEmpty(command)) { ProcessExternalCommand(command); // 执行后清空文件,避免重复触发 File.WriteAllText(_commandFilePath, string.Empty); } } // 每3秒检查一次,可根据需求调整间隔 yield return new WaitForSeconds(3f); } } // 根据命令执行对应逻辑 private void ProcessExternalCommand(string command) { switch (command) { case "run_myfunction": MyFunction(); break; case "toggle_boolean": someBoolean = !someBoolean; Debug.Log($"someBoolean已切换为:{someBoolean}"); break; // 可以扩展更多命令 } } // 你的原有代码 public bool someBoolean = false; public IEnumerator StartChecking() { while (true) { // 你的原有轮询逻辑 yield return new WaitForSeconds(60.0f); } } }
PuTTY中执行的命令
当服务器运行后,在PuTTY里执行以下命令即可触发MyFunction():
echo "run_myfunction" > /tmp/unity_server_cmd.txt
方案2:使用本地Unix Socket(更可靠的实时通信)
如果需要更低延迟、更可靠的指令触发,可以用Linux的Unix Domain Socket,让Unity作为Socket服务器监听本地连接,你用PuTTY的nc命令发送指令。
Unity C#代码修改
需要添加Socket监听逻辑,同时要注意Unity API必须在主线程执行,所以需要一个主线程调度器:
1. 先添加主线程调度器脚本
创建一个新的UnityMainThreadDispatcher脚本,用来把Socket回调的逻辑切回主线程:
using System; using System.Collections.Generic; using UnityEngine; public class UnityMainThreadDispatcher : MonoBehaviour { public static UnityMainThreadDispatcher Instance { get; private set; } private readonly Queue<Action> _pendingActions = new Queue<Action>(); private void Awake() { if (Instance == null) { Instance = this; DontDestroyOnLoad(gameObject); } else { Destroy(gameObject); } } private void Update() { lock (_pendingActions) { while (_pendingActions.Count > 0) { _pendingActions.Dequeue()?.Invoke(); } } } public void Enqueue(Action action) { lock (_pendingActions) { _pendingActions.Enqueue(action); } } }
2. 修改Server脚本添加Socket监听
using System; using System.Net.Sockets; using System.Text; using UnityEngine; using System.Collections; public class Server : MonoBehaviour { private Socket _commandSocket; // Socket文件路径,建议放在/tmp目录 private readonly string _socketPath = "/tmp/unity_server_socket"; void Start() { StartCoroutine(StartChecking()); // 初始化命令Socket SetupCommandSocket(); } public void MyFunction() { Debug.Log("✅ 通过Socket触发MyFunction!"); // 你的函数逻辑 } // 初始化Unix Socket服务器 private void SetupCommandSocket() { // 如果Socket文件已存在,先删除避免绑定失败 if (File.Exists(_socketPath)) { File.Delete(_socketPath); } // 创建Unix Domain Socket _commandSocket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.IP); _commandSocket.Bind(new UnixDomainSocketEndPoint(_socketPath)); _commandSocket.Listen(1); // 开始异步监听连接 _commandSocket.BeginAccept(OnSocketConnected, null); } // 当有客户端连接时处理指令 private void OnSocketConnected(IAsyncResult ar) { try { using (Socket clientSocket = _commandSocket.EndAccept(ar)) { byte[] buffer = new byte[1024]; int bytesRead = clientSocket.Receive(buffer); string command = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim(); // 切回主线程执行Unity逻辑 UnityMainThreadDispatcher.Instance.Enqueue(() => ProcessExternalCommand(command)); } } catch (Exception e) { Debug.LogError($"Socket错误:{e.Message}"); } // 继续监听下一个连接 _commandSocket.BeginAccept(OnSocketConnected, null); } private void ProcessExternalCommand(string command) { switch (command) { case "run_myfunction": MyFunction(); break; // 扩展其他命令 } } // 服务器关闭时清理Socket private void OnDestroy() { _commandSocket?.Close(); if (File.Exists(_socketPath)) { File.Delete(_socketPath); } } // 你的原有代码 public bool someBoolean = false; public IEnumerator StartChecking() { while (true) { yield return new WaitForSeconds(60.0f); } } }
PuTTY中执行的命令
确保你的VPS安装了netcat(如果没装,执行sudo apt install netcat-openbsd),然后发送指令:
echo "run_myfunction" | nc -U /tmp/unity_server_socket
方案3:使用Linux自定义信号(轻量级触发)
Linux允许给进程发送自定义信号,我们可以让Unity监听SIGUSR1或SIGUSR2信号,当收到信号时执行目标函数。
Unity C#代码修改
using System; using System.Runtime.InteropServices; using UnityEngine; using System.Collections; public class Server : MonoBehaviour { // 调用Linux的libc库注册信号处理 [DllImport("libc")] private static extern void signal(int sig, Action handler); // Linux下SIGUSR1的信号编号 private const int SIGUSR1 = 10; void Start() { StartCoroutine(StartChecking()); // 注册SIGUSR1信号的处理函数 signal(SIGUSR1, OnReceivedCustomSignal); } public void MyFunction() { Debug.Log("✅ 通过Linux信号触发MyFunction!"); // 你的函数逻辑 } // 信号回调函数(注意:此函数在子线程执行) private void OnReceivedCustomSignal() { // 切回主线程执行Unity逻辑 UnityMainThreadDispatcher.Instance.Enqueue(() => MyFunction()); } // 你的原有代码 public bool someBoolean = false; public IEnumerator StartChecking() { while (true) { yield return new WaitForSeconds(60.0f); } } }
PuTTY中执行的命令
- 先找到Unity服务器的进程ID:
pgrep ServerLinux.x86_64
- 给该进程发送SIGUSR1信号:
kill -SIGUSR1 <进程ID>
比如进程ID是1234,就执行kill -SIGUSR1 1234。
总结
所有方案的核心逻辑都是:让Unity服务器主动监听外部的触发信号/指令,而不是试图用Linux命令直接“调用”内部函数——因为两者属于完全独立的执行环境,没有直接调用的通道。
你可以根据自己的需求选择方案:
- 如果你需要简单易实现,选方案1(文件监控);
- 如果你需要低延迟、实时性高的触发,选方案2(Socket通信);
- 如果你需要轻量级、无文件残留的触发,选方案3(Linux信号)。
内容来源于stack exchange

