【问题标题】:Python3 Windows multiprocessing passing socket to processPython3 Windows 多处理将套接字传递给进程
【发布时间】:2017-02-21 12:48:04
【问题描述】:

我正在尝试使多处理 ServerApp 在 Windows 上工作。我猜这个问题缺少os.fork() 功能,所以我必须以某种方式通过socket,这是不可腌制的(?!)。

我已经看到使用multiprocessing.reduction 中的reduce_handlerebuild_handle 可能会实现这一点,如here 所示,但这些方法在Python 3 中不可用(?!)。虽然我有可用的 duplicatesteal_handle 可用,但我找不到如何使用它们或我是否需要它们的示例。

另外,我想知道logging 在创建新进程时是否会成为问题?

这是我的 ServerApp 示例:

import logging
import socket

from select import select
from threading import Thread
from multiprocessing import Queue
from multiprocessing import Process
from sys import stdout
from time import sleep


class ServerApp(object):

    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler(stdout)
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)


    def conn_handler(self, connection, address, buffer):

        self.logger.info("[%d] - Connection from %s:%d", self.id, address[0], address[1])

        try:
            while True:

                command = None
                received_data = b''
                readable, writable, exceptional = select([connection], [], [], 0)  # Check for client commands

                if readable:
                    # Get Command  ... There is more code here
                    command = 'Something'


                if command == 'Something':
                    connection.sendall(command_response)
                else:
                    print(':(')

        except Exception as e:
            print(e)
        finally:
            connection.close()
            self.client_buffers.remove(buffer)
            self.logger.info("[%d] - Connection from %s:%d has been closed.", self.id, address[0], address[1])


    def join(self):

        while self.listener.is_alive():
            self.listener.join(0.5)


    def acceptor(self):

        while True:
            self.logger.info("[%d] - Waiting for connection on %s:%d", self.id, self.ip, self.port)

            # Accept a connection on the bound socket and fork a child process to handle it.
            conn, address = self.socket.accept()

            # Create Queue which will represent buffer for specific client and add it o list of all client buffers
            buffer = Queue()
            self.client_buffers.append(buffer)

            process = Process(target=self.conn_handler, args=(conn, address, buffer))
            process.daemon = True
            process.start()
            self.clients.append(process)

            # Close the connection fd in the parent, since the child process has its own reference.
            conn.close()


    def __init__(self, id, port=4545, ip='127.0.0.1', method='tcp', buffer_size=2048):

        self.id = id
        self.port = port
        self.ip = ip

        self.socket = None
        self.listener = None
        self.buffer_size = buffer_size

        # Additional attributes here....

        self.clients = []
        self.client_buffers = []


    def run(self):

        # Create TCP socket, bind port and listen for incoming connections
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.bind((self.ip, self.port))
        self.socket.listen(5)

        self.listener = Thread(target=self.acceptor)  # Run acceptor thread to handle new connection
        self.listener.daemon = True
        self.listener.start()

【问题讨论】:

  • 您编写了一些代码,但看不到任何protocol 定义。如果已经接受,则无法定义任何接受规则(什么是过滤器?)。
  • @dsgdfg 不确定我是否正确,但每个连接都应由单独的进程接受和处理。

标签: python windows multithreading sockets multiprocessing


【解决方案1】:

要允许 python3 的连接酸洗(包括套接字),您应该使用mulitprocessing.allow_connection_pickling。它为ForkingPickler 中的套接字注册reducer。例如:

import socket
import multiprocessing as mp
mp.allow_connection_pickling()


def _test_connection(conn):
    msg = conn.recv(2)
    conn.send(msg)
    conn.close()
    print("ok")

if __name__ == '__main__':
    server, client = socket.socketpair()

    p = mp.Process(target=_test_connection, args=(server,))
    p.start()

    client.settimeout(5)

    msg = b'42'
    client.send(msg)
    assert client.recv(2) == msg

    p.join()
    assert p.exitcode == 0

    client.close()
    server.close()

我还注意到您还有一些其他问题与socket 的酸洗无关。

  • 当使用self.conn_handler 作为目标时,多处理将尝试腌制整个对象self。这是一个问题,因为您的对象包含一些无法腌制的 Thread。因此,您应该从目标函数的闭包中删除 self。可以通过使用 @staticmethod 装饰器并删除函数中所有提及 self 来完成。

  • 另外,logging 模块不是用来处理多个进程的。基本上,来自已启动Process 的所有日志都将随着您当前的代码丢失。要解决此问题,您可以在启动第二个 Process(在 conn_handler 的开头)后启动一个新的 logging,或使用 multiprocessing 日志记录实用程序。

这可以给出这样的结果:

import logging
import socket

from select import select
from threading import Thread
from multiprocessing import util, get_context
from sys import stdout
from time import sleep

util.log_to_stderr(20)
ctx = get_context("spawn")


class ServerApp(object):

    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler(stdout)
    formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    def __init__(self, id, port=4545, ip='127.0.0.1', method='tcp',
                buffer_size=2048):

        self.id = id
        self.port = port
        self.ip = ip

        self.socket = None
        self.listener = None
        self.buffer_size = buffer_size

        # Additional attributes here....

        self.clients = []
        self.client_buffers = []

    @staticmethod
    def conn_handler(id, connection, address, buffer):

        print("test")
        util.info("[%d] - Connection from %s:%d", id, address[0], address[1])

        try:
            while True:

                command = None
                received_data = b''
                # Check for client commands
                readable, writable, exceptional = select([connection], [], [],
                                                        0)

                if readable:
                    # Get Command  ... There is more code here
                    command = 'Something'

                if command == 'Something':
                    connection.sendall(b"Coucouc")
                    break
                else:
                    print(':(')
                sleep(.1)

        except Exception as e:
            print(e)
        finally:
            connection.close()
            util.info("[%d] - Connection from %s:%d has been closed.", id,
                    address[0], address[1])
            print("Close")

    def join(self):

        while self.listener.is_alive():
            self.listener.join(0.5)

    def acceptor(self):

        while True:
            self.logger.info("[%d] - Waiting for connection on %s:%d", self.id,
                            self.ip, self.port)

            # Accept a connection on the bound socket and fork a child process
            # to handle it.
            conn, address = self.socket.accept()

            # Create Queue which will represent buffer for specific client and
            # add it o list of all client buffers
            buffer = ctx.Queue()
            self.client_buffers.append(buffer)

            process = ctx.Process(target=self.conn_handler,
                                args=(self.id, conn, address, buffer))
            process.daemon = True
            process.start()
            self.clients.append(process)

            # Close the connection fd in the parent, since the child process
            # has its own reference.
            conn.close()

    def run(self):

        # Create TCP socket, bind port and listen for incoming connections
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.bind((self.ip, self.port))
        self.socket.listen(5)

        # Run acceptor thread to handle new connection
        self.listener = Thread(target=self.acceptor)
        self.listener.daemon = True
        self.listener.start()

        self.listener.join()


def main():
    app = ServerApp(0)
    app.run()


if __name__ == '__main__':
    main()

我只在 Unix 和 python3.6 上测试过它,但它的行为应该不会有太大的不同,因为我在 windows 中使用 spawn context, which should behave like theProcess`。

【讨论】:

  • 抱歉耽搁了。我已经测试了allow_connection_pickling,但它没有效果。但是,我注意到我遇到了两个不同的错误(两次运行相同的代码)。这是first one,这是second one。当我没有将侦听器分配为 ServerApp 属性(只是 listener 而不是 self.listener)时,我没有错误,但处理程序进程也永远不会执行。
  • 这不是socket 酸洗的问题。如果您阅读错误,它无法腌制 _thread.Lock 和一些 io 对象。我想说这与整个ServerApp 对象的酸洗有关,当您使用实例方法启动新的Process 时需要。您应该为conn_handler 使用@staticmethod 装饰器或将其从类中删除。这也是一个很好的做法,因为腌制整个对象是不安全的(例如,如果它处理一些密码)。另外,请尝试提供一些基本脚本来重现您的错误以进行测试。
  • 另外,作为一个很好的实践,您应该使用multiprocessing.utils.log_to_stderrmultiprocessing.utils.debug/info 在您的Process 中获得一致的日志记录。如果您需要使用自定义日志记录,则应在目标函数的开头启动它,因为 logging 仅设计为与一个 Process 一起使用。
  • 谢谢!我设法通过将listener 作为对象的一部分删除来使其运行。请检查此commit。这部分if __name__ == '__main__':也给我带来了困扰。现在它适用于一个客户端(将 tinyPDC 作为客户端运行),但只要我运行另一个 tinyPDC 实例,我就会得到这个error。不确定是否可以在 Unix 上复制,因为它对我有用。
  • 看起来 Python 正在自己处理大部分酸洗。我现在尝试将handler 设为静态方法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-22
  • 2013-04-09
  • 2013-01-03
  • 2017-03-18
  • 1970-01-01
  • 1970-01-01
  • 2013-05-09
相关资源
最近更新 更多