使用IPC在Python和C#之间进行通信的最简单方法是什么?

8

我有一些C#代码,需要调用一个Python脚本数千次,每次传递一个字符串,并期望返回一个浮点数。这个Python脚本可以使用任何版本的Python运行,因此我不能使用Iron Python。有人建议我使用IPC命名管道。我没有经验,在C#和Python之间如何实现这个功能还无法解决。这是一个简单的过程,还是需要付出相当多的工作?这是否是解决我的问题的最佳方式?


1
STDIN/STDOUT足够快吗?我认为这是一个不错的方法。 - Tim S.
这是我现在正在使用的。我需要提高速度。 - user3490130
3个回答

3

使用zeromq。

  • 允许轻量级消息传递,无需代理
  • 适用于许多平台
  • 可以在TCP套接字、Unix套接字或进程内通信

以下是我使用zeromq的答案https://dev59.com/2mAg5IYBdhLWcg3w5ucd#23227631

它用于不同Python程序之间的消息传递,但是同样的通信方法也可以用于其他平台。只需使通过传递的消息具有互操作性-双方都必须正确理解内容。您可以使用二进制数据进行实验,但是很多时候,json表示法也非常快速和容易。但是还有许多序列化框架,如结果缓冲区等,可以帮助您对结果进行编码。


这可能是一个很好的解决方案,但可能会有安全隐患,因为您正在绑定到一个TCP端口。至少,请限制为本地主机,除非您真的希望通过网络进行实时操作。 - tdelaney
@user3490130 关于速度,准备好迎接真正快速的传输吧。每秒数万条消息,对于较小的消息,它通常比TCP更高效,因为它可以将多个消息合并到一个TCP数据包中(这取决于实际使用场景)。 - Jan Vlcinsky
完全是编程领域的新手。你能简要地解释一下在 C# 方面需要什么才能使其工作吗? - user3490130
@user3490130 如果需要C#绑定,请参考:https://github.com/zeromq/clrzmq。此外,http://zeromq.org/也是ZeroMQ的一个很好的起点 - 它写得非常好 - 你会经常笑出声。 - Jan Vlcinsky
如果您有一些 Python 经验,我建议从 Python 开始尝试实验,它应该更具交互性和易用性。除了一般的 ZMQ 指南外,您还可以在 https://github.com/zeromq/pyzmq/tree/master/examples 找到很棒(功能齐全且简短)的示例。 - Jan Vlcinsky

3

环境初始化


为了实现这一点,首先,您需要在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

enter image description here

enter image description here



环境初始化之后,进入 Windows 上的 python.exe 所在目录,或者 Linux 上的 python 二进制文件所在目录,并创建或上传您需要运行的 Python 脚本文件。

enter image description here

enter image description here

enter image description here




进程间通信方法


带参数的脚本初始化

一种优雅而稳定的进程间通信方法是通过在脚本初始化时传递参数来调用脚本。如果您只需要向脚本传递一些信息,并且不需要在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 进行实时数据传输


标准输入、标准输出和标准错误是应用程序的主要I/O流,由操作系统内核用于接收输入、发送输出以及发送与函数相关的错误消息。这些主要的I/O流可以用于进程间通信,通过将这些I/O流的流向从子进程重定向到操作系统,再到父进程来实现。

[ 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不是异步I/O流,这可能会导致流在等待缓冲数据时被锁定:https://devblogs.microsoft.com/oldnewthing/20110707-00/?p=10223

char[] buffer = new char[1000];
proc.StandardOutput.Read(buffer, 0, buffer.Length);


使用命名管道进行实时数据传输

管道是一种套接字类型,它使用操作系统文件系统在连接上发送和接收信息。这种IPC类型非常适合快速数据传输,并且相对于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()

1
请问您能否裁剪一下截图,去掉所有不相关的巨大空白区域? - starball
1
太棒了,[使用命名管道进行实时数据传输] 对我很有用,但需要做一些调整:我必须在前面声明两个 new System.IO.Pipes.NamedPipeServerStream() 管道(否则,Python脚本找不到 main_read_pipe);我还必须将 connection1.Write() 改为 connection1.WriteAsync(),将 connection2.Read() 改为 connection2.ReadAsync() - gl3yn
1
@gl3yn,太棒了。请点赞该帖子以增加曝光度并帮助其他人。 - teodor mihail
1
我已经做了(帖子和评论)!做得很好! - gl3yn
@gl3yn 谢谢你!!! - teodor mihail

0

根据您所说的,您可以连接到Python进程并捕获标准输出文本。简单、快速和可靠!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接