套接字 P2P 和线程

Socket P2P and Threading

我正在尝试创建消息传递应用程序(很可能是通过其他方式完成的)并且我正在使用线程,因此我可以随时发送或接收消息

我已经为每个线程使用了类,一个用于接收消息一个用于发送

正在发送文件

class snd (threading.Thread):
    def run (self):
        while True:
            msg = input("You: ")
            if msg == endprompt:
                closecon()
                break
            else:
                s.send(bytes(msg, "UTF-8"))
                sen()
                pass

它是这样调用的:

def sen ():
    s.start()
s = snd()

接收类似,只是如您所料的模糊变化

正在接收

class rcv (threading.Thread):
    def run (self):
        while True:
            reply = s.recv(2048)
            print("User: ", reply.decode("UTF-8"))
            rec()

再次这样称呼:

def rec ():
    r.start()
r = rcv()

问题

"send"不是snd的属性 与 recv 相同,我假设

这是从对象中触发我的套接字发送的问题 所以我尝试了一些东西

我试图解决这个问题

一个是: 改变 from socket import *import socket as sck

然后我继续尝试将类似的东西添加到 类,例如 class snd (threading.Thread, sck.send)

这再次无效并吐出错误: AttributeError: 'module' 对象没有属性 'send'

问题

我该如何解决这个问题?这样我就可以在对象内部发送消息了?

附加信息:

s = sck.socket(sck.AF_INET, sck.SOCK_STREAM)

~~~~

def closecon ():
    s.close()

~~~~

def con ():
    s.connect((host, port))

所以首先让我指出一个简单的问题,这可能会使您的情况变得清楚。让我们分析一下这些代码片段之间发生了什么:

s = sck.socket(sck.AF_INET, sck.SOCK_STREAM) # s defined at the global level

def closecon ():
    # python looks for s in the local namespace, doesn't find it
    # and uses the global s above.
    s.close()  # s.close() refers to the socket object's (defined above) .close() method
def con ():
    s.connect((host, port))  # Again s not defined locally, so it uses the global s.

在你的函数中使用全局变量,或者其他任何东西通常被认为是不好的做法,最终会让你的代码难以阅读,甚至对你自己来说也是如此。您遇到的具体问题归结为这个片段:

def sen ():
    s.start()
s = snd()  # s is now a snd object, no longer a socket

通过重新定义 s 所指的内容,您的 class snd,每当您 运行() 它使用 s 的定义而不是原来的插座。

class snd (threading.Thread):
    def run (self):
        while True:
            msg = input("You: ")
            if msg == endprompt:
                closecon()
                break
            else:
                s.send(bytes(msg, "UTF-8")) # snd objects (subclassed Thread) has no send() method
                sen()
                pass

如果你能一直关注我到现在,你会发现每当你的 snd class 运行 的 run() 中的代码,它就会到达行 s.send(bytes(msg, "UTF-8")) 并且正在使用 s 的当前全局定义,因为它在本地的范围内没有 s 定义。要解决此问题,您可以为套接字创建变量名称对象和您的 snd 对象彼此不同,并在您想要引用套接字和 snd 对象的地方更新您的代码,并使用它们的新名称。但是,我不建议将此作为您的代码的解决方案,即使它可能有效。

我推荐的

在您的 Thread 子 class 中,您应该覆盖 threading.Thread__init__() 方法以包括接受套接字对象。您的 snd class(以及您的 rec class)的代码看起来更像:

class snd (threading.Thread):
    ## Added __init__ override
    def __init__(self, sock):
        threading.Thread.__init__(self)
        self.sock = sock
    def run (self):
        while True:
            msg = input("You: ")
            if msg == endprompt:
                closecon()
                break
            else:
                self.sock.send(bytes(msg, "UTF-8")) # Now we use the socket specifically given to this object
                sen()
                pass # You don't need this

当你想使用你的线程时,你只需要稍微修改一下你的代码:

my_sock = sck.socket(sck.AF_INET, sck.SOCK_STREAM)
sender = snd(my_sock)
sender.start()

请注意,您的代码此时仍然无法运行,因为您的 con() 和 closecon() 函数需要这样重写:

def closecon(sock):
    sock.close()

def con(sock, host, port):
    sock.connect((host,port))

最终,您的代码中还有其他几个问题需要重新处理,但我想我已经为您留下了足够的信息来弄清楚如何去做,或者了解如何弄清楚。如果你不想看下面关于python的更基本的解释,我至少会留给你几点:

  • 网络真的很复杂。
  • 套接字真的很低级,使联网变得困难。
  • 线程是一种高级工具,需要对程序流程有充分的了解才能正确执行。
  • 继承class继承是一个中等高级的概念。

如果您对语言基础知识缺乏扎实的基础理解,尝试使用上述任何一点进行编码将是一场复制粘贴之战,并且由于理解上的小漏洞而产生混乱。我最终建议您先读 python 一本书,或者 仔细阅读 在线教程,然后再尝试使用套接字和线程进行联网。如果您只是想让某些东西工作,并且更专注于其他代码,我建议您查看其他更高级的网络库 python。除此之外,祝你的项目好运。

如果你想让你的代码对你和其他人更有意义,了解局部变量和全局变量之间的区别,以及 python 中变量作用域的概念将帮助你理解为什么你的代码会失败。随着代码变大,它将帮助您更好地组织代码。但是,如果您只想快速 运行 ,请继续阅读。我不是 python 或编程方面的专家,所以可能这里或那里有错误,但我会尽力解释。

作用域

当您的 python 代码为 运行ning 并遇到变量名称时,它会检查该变量的定义。首先它检查本地命名空间,然后从那里向上,一直到最高的命名空间,即全局命名空间。请注意,它从不查看下层命名空间的定义。这意味着您可以在不同范围级别重用变量名,而不会发生冲突。如果您不知道名称空间是什么,只需将其想象成您在代码中的位置即可。例如,

x=1 # Here you are at the global scope/namespace
def func():
    x=2 # << Here you are in func's namespace
print(x) # Back out at the global namespace

如果您认为此代码导致 2 打印到屏幕,那您就错了。 x=2行在func范围内,print(x)行在全局范围内。当执行 print(x) 时,它从全局范围开始查找 x 的值,这在第一行中定义为 x=1。当您将变量定义为给定范围内的局部变量时,它们通常会在您离开该范围时被销毁。因此,如果您调用 func(),它会在局部范围内设置 x=2,然后退出该函数。通过执行 return 或只是完成一个函数,我们将离开它的作用域,并且作用域中定义的变量 x 被销毁(取消引用和垃圾收集)。由于 func 的内容在比全局范围更小的范围内,在全局级别执行的代码永远不会看到其中分配的值。

因此作用域使函数和 classes 非常孤立,但解决方案是将参数传递给这些构造。通过将参数传递给函数,它们将在其本地作用域中定义的变量设置为从另一个作用域传递给它们的值。解释一下,执行此代码对 args 的操作:

def func(value):
    print(value)
# By passing 100 to func(), there is essentially 
# an invisible line in func that does 'value=100'.
func(100)

您必须使用全局变量执行此操作:

def func():
    value = x
    print(value)
# By not using arguments, we have to manually do that invisible
# assignment of value=100 by setting x=100 and then the function setting value=x
x=100
func()  

要了解它提供的实用程序,而不是让 python 在全局范围内搜索变量,这两个片段都会将 2 打印到屏幕上

使用函数参数:

x = 1
def func_args(value):
    print(value*2)

func_args(x)

使用全局变量:

x=1
def func_globals():
    print(x*2)

func_globals()

但是,请注意 func_globals() 只会打印 x*2,而 func_args(value) 会为传递给它的任何值打印 value*2。这意味着为了像 func_args() 一样使用 func_globals(),您必须知道 x 在调用 func_globals() 时设置为您想要的,或者手动设置每次调用它时,它都会达到您想要处理的价值。使用 func_args(),您只需要知道作为 value.

传递给函数的内容

这绝不是对变量作用域和命名空间的详尽解释,但希望您对什么是作用域有一些基本的了解。 Internet 上有关于 python 中此类内容的有用信息,我建议您熟悉这些概念,因为它们是 python 和其他语言中代码结构的核心。