Python与C#间使用IPC通信的最简单方法是什么?

8 投票
3 回答
11344 浏览
提问于 2025-04-18 04:48

我有一些C#代码,需要调用一个Python脚本几千次,每次都传一个字符串,然后期待返回一个浮点数。这个Python脚本可以用任何版本的Python运行,所以我不能使用Iron Python。有人建议我使用IPC命名管道。我对这个没有经验,也不知道怎么在C#和Python之间实现这个。这个过程简单吗,还是说需要花费不少功夫?这是解决我问题的最佳方法吗?

3 个回答

0

根据你所说的,你可以连接到Python程序,并获取它输出的文本。这很简单、快速,而且可靠!

4

环境初始化


为了实现这个目标,首先你需要在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()


使用stdinstdoutstderr进行实时数据传输


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()
3

可以使用zeromq。

  • 它可以实现非常轻量级的消息传递,不需要中间的代理。
  • 支持很多平台。
  • 可以通过TCP套接字、Unix套接字或者在进程内部进行通信。

这是我使用zeromq的回答 https://stackoverflow.com/a/23227631/346478

它可以用于不同Python程序之间的消息传递,但同样的方法也可以用于其他平台之间的通信。只要确保传递的消息是可以互操作的——双方都必须正确理解内容。你可以处理二进制数据,但通常使用json格式会更快更简单。不过还有很多序列化框架,比如结果缓冲区等,可以帮助你编码结果。

撰写回答