【问题标题】:Python thread won't start using socketPython 线程不会开始使用套接字
【发布时间】:2016-08-21 23:26:52
【问题描述】:

我的程序使用套接字和线程时遇到问题。

我做了一个套接字服务器,在一个线程中添加客户端,但客户端线程永远不会启动......

这是我的代码:

套接字服务器

import socket, threading, logging, sys
from client_thread import ClientThread

class SocketServer:
    CLIENTS = list()

    def __init__(self, server_ip, server_port, max_connections):
        try:
            self.tcpsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.tcpsock.bind((server_ip, server_port))
            self.tcpsock.listen(10)

            logging.info('Socket server successfully started !')
    except Exception as e:
            logging.error(format(e))

    def start(self):
        from src.realmserver.core.hetwan import EmulatorState, Core

        while (Core.STATE == EmulatorState.IN_RUNNING):
            try:
                (clientsock, (ip, port)) = self.tcpsock.accept()
                new_client = threading.Thread(target=ClientThread, args=[len(self.CLIENTS), ip, port, clientsock])
                self.CLIENTS.append(new_client)
                new_client.start()
            except Exception as e:
                print format(e)

        for client in self.CLIENTS:
            client.join()

和客户端线程

import logging, string, random

class ClientThread:
    def __init__(self, client_id, client_ip, client_port, socket):
        self.client_id = client_id
        self.client_ip = client_ip
        self.client_port = client_port
        self.socket = socket
        logging.debug('(%d) Client join us !', client_id)

    def run(self):
        key = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(32))

        print self.send('HC%s' % key)

        while True:
            entry = self.socket.recv(4096)
            entry.replace("\n", "")

            if not entry:
                break
            else:
                logging.debug('(%d) Packet received : %s', self.client_id, str(entry))

        self.kill()

    def send(self, packet):
        return self.socket.send("%s\x00" % packet)

    def kill(self):
        self.socket.close()
        logging.debug('(%d) Client is gone...', self.client_id)

抱歉缩进不好,这是表格,不是我的文件。

请帮帮我:(

提前谢谢你(抱歉英语不好,我是法国人......)

【问题讨论】:

  • 我修复了您代码中的缩进,但请仔细检查以确保我做对了。
  • 到目前为止您尝试过什么?如果在new_client.start() 之前添加打印语句,它会打印吗?如果在ClientThread 中的run 方法顶部添加打印语句,它会打印吗?

标签: python multithreading sockets


【解决方案1】:

你的Server实例start函数中有这行代码:

new_client = threading.Thread(target=ClientThread,
     args=[len(self.CLIENTS), ip, port, clientsock])

threading.Threadtarget= 参数必须是可调用函数。这里ClientThread 是您的类ClientThread 的构造函数的名称,因此它一个可调用函数,返回该类的一个实例。请注意,它实际上还没有被调用! args= 参数通常是一个元组,但实际上是一个列表。当您使用这个特定的线程模型时,这些参数将在最终被调用时传递给 target 函数。 (您也可以使用 kwargs= 和字典传递关键字参数。)

现在发生的事情有点棘手。现在两个参数(target=args=)已经被计算,Python 运行时创建了一个 threading.Thread 类的新实例。目前,这个新实例只是一个数据对象。

如果我们添加 print 语句/函数(不清楚这是 py2k 还是 py3k 代码)我们可以看到对象本身:

print('new_client id is', id(new_client))

这将打印类似:1

new_client id is 34367605072

接下来,将其添加到列表中,然后调用其start

self.CLIENTS.append(new_client)
new_client.start()

列表添加很简单,但start 相当棘手。

start 调用本身实际上创建了一个新的操作系统/运行时线程(其 ID 与数据对象的 ID 无关——原始线程 ID 是内部实现细节)。这个新线程开始在它的run 方法上运行。2 默认的run 方法实际上是:3

try:
    if self.__target:
        self.__target(*self.__args, **self.__kwargs)
finally:
    # Avoid a refcycle if the thread is running a function with
    # an argument that has a member that points to the thread.
    del self.__target, self.__args, self.__kwargs

由于您使用的是常规的threading.Thread 实例对象,因此您将获得此默认行为,其中new_thread.start() 创建新线程本身,然后调用默认的run 方法,该方法调用其self.__target,即你的 ClientThread 类实例创建函数。

所以现在,在新线程中,Python 创建了一个ClientThread 对象的实例,调用它的__init__,并将self.__argsself.__kwargs 保存在new_thread 实例中(它本身在原始 Python 和新线程之间共享)。

这个新的ClientThread 对象执行它的__init__ 代码并返回。这相当于读取run 方法:

def run(self):
    ClientThread(**saved_args)

请注意,这不是

def run(self):
    tmp = ClientThread(**saved_args)
    tmp.run()

也就是说,ClientThread 实例的run 方法永远不会被调用。仅调用threading.Thread 实例的run 方法。如果修改ClientThread__init__ 方法以打印出 ID,您将看到此ID 与threading.Thread 实例的ID 不同:

class ClientThread:
    def __init__(self, client_id, client_ip, client_port, socket):
        print('creating', id(self), 'instance')

这将打印一个不同的 ID(并且肯定会在new_client id is 行之后打印):

new_client id is 34367605072
creating 34367777464 instance

如果您将额外的 prints 添加到您的 run 方法中,您将看到它永远不会被调用。

怎么办

这里有两个主要选项。

  • 您可以将ClientThread 设为threading.Thread子类

    class ClientThread(threading.Thread):
        def __init__(self, client_id, client_ip, client_port, socket):
            ...
            threading.Thread.__init__(self)
    

    在这种情况下,您将自己创建客户端对象,而不是使用threading.Thread 来创建它:

    new_thread = ClientThread(...)
    ...
    new_thread.start()
    

    .start 方法将是 threading.Thread.start,因为您没有覆盖它,然后该方法将创建实际的操作系统/运行时线程,然后调用您的 run 方法,因为您 确实 覆盖它——将是 你的 run

  • 或者,您可以创建一个标准的threading.Thread 对象,为其提供target,并让此target 调用您对象的run 方法,例如:

    new_client = ClientThread(...)
    new_thread = threading.Thread(target=new_client.run, ...)
    ...
    new_thread.start()
    

    选择权在你:继承,还是使用单独的对象。


1实际 ID 高度依赖于实现。

2它到达run 函数的路径有些复杂,通过引导代码进行一些内部初始化,然后为您调用self.run,不传递任何参数。您只被保证以某种方式输入self.run;你不应该依赖“如何”。

3至少,这是Python 2.7和3.4中的代码;其他实现可能略有不同。

【讨论】:

    猜你喜欢
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-03
    相关资源
    最近更新 更多