Python与C#间使用IPC通信的最简单方法是什么?
我有一些C#代码,需要调用一个Python脚本几千次,每次都传一个字符串,然后期待返回一个浮点数。这个Python脚本可以用任何版本的Python运行,所以我不能使用Iron Python。有人建议我使用IPC命名管道。我对这个没有经验,也不知道怎么在C#和Python之间实现这个。这个过程简单吗,还是说需要花费不少功夫?这是解决我问题的最佳方法吗?
3 个回答
根据你所说的,你可以连接到Python程序,并获取它输出的文本。这很简单、快速,而且可靠!
环境初始化
为了实现这个目标,首先你需要在C#可执行文件所在的目录里设置一个Python环境,这样可以让应用程序模块化,并且能够有效地进行进程间通信。通过这种方式,应用程序整体上就不再依赖于操作系统,因为它有一个独立的Python虚拟环境,可以在里面运行Python应用。这意味着应用程序不需要依赖于主机上是否已经安装了Python运行环境。
在Linux或Windows上创建虚拟环境,可以输入:python -m venv [ 创建环境的路径 ]
。
在Linux上,为了创建Python虚拟环境,你需要输入命令sudo apt-get install python[python版本]-venv
,这样可以下载创建Python虚拟环境所需的包,比如:sudo apt-get install python3.11-venv
环境初始化完成后,去Windows上python.exe
所在的目录,或者Linux上python
二进制文件所在的目录,创建或上传你需要运行的Python脚本文件。
进程间通信方法
带参数的脚本初始化
一种优雅且稳定的进程间通信方法是通过在脚本初始化时传递参数来调用脚本。如果你只需要传递一些信息给脚本,而不需要在C#应用和Python应用之间交换数据,这种方法非常合适。
[ C# 代码 ]
static void Main(string[] args)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process(); // <----- Process object
proc.StartInfo.WorkingDirectory = "C:\\Users\\Teodor Mihail\\source\\repos\\Inter_Process_Communication_Example_Python\\Inter_Process_Communication_Example_Python\\bin\\Debug\\Scripts"; // <----- Path where python executable is located
proc.StartInfo.FileName = "python.exe"; // <----- Executable name ( Windows ) or binary (Linux/MacOS)
proc.StartInfo.Arguments = "main.py Param1 Param2"; // // <----- Python file to be executed by the Python executable and the command line arguments passed to the process
proc.Start(); // <---- Start the process
Console.ReadLine();
}
[ Python 代码 ]
import sys
def main(param1, param2):
print("Param1 =", str(param1))
print("Param2=", str(param2))
# Get the first command line argument passed
# to the application and store it in a variable.
received_param1 = sys.argv[1]
# Get the second command line argument passed
# to the application and store it in a variable.
received_param2 = sys.argv[2]
# Call the "main" function and pass the two command
# line arguments to the method as parameters
main(received_param1, received_param2)
input()
使用stdin、stdout和stderr进行实时数据传输
Stdin、Stdout和Stderr是操作系统内核用于接收输入、发送输出和发送错误信息的主要输入输出流。这些主要的输入输出流可以用于进程间通信,方法是将这些输入输出流的流向从子进程重定向到操作系统,再到父进程。
[ C# 代码 ]
static void Main(string[] args)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process(); // <----- Process object
proc.StartInfo.WorkingDirectory = "C:\\Users\\Teodor Mihail\\source\\repos\\Inter_Process_Communication_Example_Python\\Inter_Process_Communication_Example_Python\\bin\\Debug\\Scripts"; // <----- Path where python executable is located
proc.StartInfo.FileName = "python.exe"; // <----- Executable name ( Windows ) or binary (Linux/MacOS)
proc.StartInfo.Arguments = "main.py"; // <----- Python file to be executed by the Python executable
proc.StartInfo.RedirectStandardInput = true; // <----- Redirect the Stdin stream of the Python application through the C# application
proc.StartInfo.RedirectStandardOutput = true; // <----- Redirect the Stdout stream of the Python application through the C# application
proc.StartInfo.RedirectStandardError = true; // <----- Redirect the Stderr stream of the Python application through the C# application
proc.StartInfo.UseShellExecute = false; // <----- Do not use the OS shell to execute the application and use the C# application as the shell
proc.Start(); // <---- Start the process
// Read the output of the Python application on the Stdout stream
char[] buffer = new char[1000];
proc.StandardOutput.Read(buffer, 0, buffer.Length);
Console.WriteLine(buffer);
// Send a message to the Python application through the Stdin stream
proc.StandardInput.WriteLine("Hello from C# application over STDIN");
proc.StandardInput.FlushAsync();
// Read the output of the Python application on the Stdout stream
buffer = new char[1000];
proc.StandardOutput.Read(buffer, 0, buffer.Length);
Console.WriteLine(buffer);
// Read the error message thrown by the Python application on the Stderr stream
buffer = new char[1000];
proc.StandardError.Read(buffer, 0, buffer.Length);
Console.WriteLine(buffer);
Console.ReadLine();
}
[ Python 代码 ]
import sys
def main():
# Send a message to the C# application on the Stdout stream
sys.stdout.write("Hello from Python application over STDOUT")
# Receive a message from the C# application on the
# Stdin stream and store it inside a variable.
received = input()
# Send the message received from the C# application
# back to the C# application through the Stdout stream
sys.stdout.write(received)
# Send an error message through the Stderr stream to the C# application
raise Exception("\n\n\nHello from Python application over STDERR")
main()
从stdout和stderr获取的数据必须逐字符读取,否则流可能会因为等待响应而锁定。这是因为stdout和stderr不是异步输入输出流,可能会导致流在等待缓冲数据时锁定:https://devblogs.microsoft.com/oldnewthing/20110707-00/?p=10223
char[] buffer = new char[1000];
proc.StandardOutput.Read(buffer, 0, buffer.Length);
使用命名管道进行实时数据传输
管道是一种使用操作系统文件系统在连接上发送和接收信息的套接字。这种进程间通信方式非常适合快速数据传输,并且相比于stdin、stdout和stderr,能够同时处理多个连接:https://www.baeldung.com/cs/pipes-vs-sockets。
[ C# 代码 ]
static void Main(string[] args)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process(); // <----- Process object
proc.StartInfo.WorkingDirectory = "C:\\Users\\Teodor Mihail\\source\\repos\\Inter_Process_Communication_Example_Python\\Inter_Process_Communication_Example_Python\\bin\\Debug\\Scripts"; // <----- Path where python executable is located
proc.StartInfo.FileName = "python.exe"; // <----- Executable name ( Windows ) or binary (Linux/MacOS)
proc.StartInfo.Arguments = "main.py"; // <----- Python file to be executed by the Python executable
proc.Start(); // <---- Start the process
// Named pipe server object with an "Out" direction. This means that this pipe can only send messages
System.IO.Pipes.NamedPipeServerStream connection1 = new System.IO.Pipes.NamedPipeServerStream("main_write_pipe", System.IO.Pipes.PipeDirection.Out);
try
{
// Wait for a conection to e established on the pipe. This is a blocking method call, meaning that the thread will wait for this method to finish the execution
connection1.WaitForConnection();
// Byte buffer that stores the UTF8 encoded binary value of the string "Message from C# application over FIFO Pipe"
byte[] buffer = Encoding.UTF8.GetBytes("Message from C# application over FIFO Pipe");
// Write the binary buffer's contents on the pipe's I/O stream
connection1.Write(buffer, 0, buffer.Length);
// Flush the binary buffer's contents on the pipe's I/O stream
connection1.Flush();
}
catch
{
}
finally
{
if (connection1 != null)
{
connection1.Dispose();
}
}
// Named pipe server object with an "In" direction. This means that this pipe can only read messages
System.IO.Pipes.NamedPipeServerStream connection2 = new System.IO.Pipes.NamedPipeServerStream("main_read_pipe", System.IO.Pipes.PipeDirection.In);
try
{
// Wait for a conection to e established on the pipe. This is a blocking method call, meaning that the thread will wait for this method to finish the execution
connection2.WaitForConnection();
// Byte buffer that stores the UTF8 encoded binary value of the string "Message from Python application over FIFO Pipe"
byte[] buffer = new byte[1024];
connection2.Read(buffer, 0, buffer.Length);
// Print the message
Console.WriteLine(Encoding.UTF8.GetString(buffer));
}
catch
{
}
finally
{
if (connection1 != null)
{
connection1.Dispose();
}
}
Console.ReadLine();
}
[ Python 代码 ]
def main():
# On Linux and MacOs the pipes created by C# are located in "/tmp" so you have to enter "/tmp/pipe_name"
# Open the OS's file system pipe FIFO and specify that it has only "read" permissions.
# This must be done because the "main_write_pipe" pipe server in C# is created with
# write permissions and the receiver can only read from the stream.
pipe_fifo1 = open(r"\\.\pipe\main_write_pipe", "r")
# Read the content from the stream and store it in a variable.
# Because the stream is buffered, the data will be received
# after the pipe server instance in the C# application is
# closed.
received = pipe_fifo1.readline()
# Open the OS's file system pipe FIFO and specify that it has only "write" permissions.
# This must be done because the "main_read_pipe" pipe server in C# is created with
# read permissions and the receiver can only write to the stream.
pipe_fifo1 = open(r"\\.\pipe\main_read_pipe", "w")
# Write the content to the pipe stream
pipe_fifo1.write("Message from Python over FIFO Pipe")
# Flush the stream to ensure that all the data within the
# stream is flushed to the receiver.
pipe_fifo1.flush()
input()
main()
使用TCP/UDP套接字进行实时数据传输
套接字是一种二进制接收器/发射器,用于通过IP地址接收或发送二进制数据。在进程间数据通信中,使用的IP地址是回环地址(127.0.0.1),这是计算机自我调用时使用的地址。
[ C# 代码 ]
static void Main(string[] args)
{
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo.WorkingDirectory = "C:\\Users\\Teodor Mihail\\source\\repos\\Inter_Process_Communication_Example_Python\\Inter_Process_Communication_Example_Python\\bin\\Debug\\Scripts";
proc.StartInfo.FileName = "python.exe";
proc.StartInfo.Arguments = "main.py";
proc.Start();
Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
server.Bind(new System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 80));
server.Listen(1);
try
{
Socket client = server.Accept();
try
{
byte[] buffer = Encoding.UTF8.GetBytes("Message from Python application over TCP");
client.Receive(buffer, 0);
Console.WriteLine(Encoding.UTF8.GetString(buffer));
buffer = Encoding.UTF8.GetBytes("Message from C# application over TCP");
client.Send(buffer, 0);
}
catch
{
}
finally
{
if(client != null)
{
client.Dispose();
}
}
}
catch
{
}
finally
{
if(server != null)
{
server.Dispose();
}
}
Console.ReadLine();
}
[ Python 代码 ]
import socket
def main():
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 80))
client.send("Message from Python application over TCP".encode("utf-8"))
info = client.recv(len("Message from C# application over TCP".encode("utf-8")))
print(info.decode("utf-8"))
input()
main()
可以使用zeromq。
- 它可以实现非常轻量级的消息传递,不需要中间的代理。
- 支持很多平台。
- 可以通过TCP套接字、Unix套接字或者在进程内部进行通信。
这是我使用zeromq的回答 https://stackoverflow.com/a/23227631/346478
它可以用于不同Python程序之间的消息传递,但同样的方法也可以用于其他平台之间的通信。只要确保传递的消息是可以互操作的——双方都必须正确理解内容。你可以处理二进制数据,但通常使用json格式会更快更简单。不过还有很多序列化框架,比如结果缓冲区等,可以帮助你编码结果。