【问题标题】:Python for loop slows and evenutally hangsPython for 循环变慢并最终挂起
【发布时间】:2011-04-20 22:21:15
【问题描述】:

我对 Python 完全陌生(截至半小时前)并尝试编写一个简单的脚本来枚举 SMTP 服务器上的用户。

用户文件是一个简单的用户名列表(每行一个)。

脚本运行良好,但随着循环的每次迭代,它都会变慢,直到在循环 14 附近,它似乎完全挂起。没有错误 - 我必须 ^c。

谁能解释一下这个问题?

TIA, 汤姆

#!/usr/bin/python

import socket
import sys

if len(sys.argv) != 2:
        print "Usage: vrfy.py <username file>"
        sys.exit(0)

#open user file
file=open(sys.argv[1], 'r')
users=[x.strip() for x in file.readlines()]
file.close

#Just for debugging
print users

# Create a Socket
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to the Server
connect=s.connect(('192.168.13.222',25))

for x in users:
        # VRFY a user
        s.send('VRFY ' + x + '\r\n')
        result=s.recv(1024)
        print result

# Close the socket
s.close()

【问题讨论】:

  • 对于菜鸟来说相当先进的东西。 :)
  • 不过有两个注意事项:file.close 不做任何事情,你想要file.close()(将来,你应该慢慢习惯上下文管理器),.readlines() 调用毫无意义(您可以按原样逐行迭代文件)。
  • 问题?你惹恼了系统管理员。
  • 如果 Ignacio 的评论不清楚,系统管理员很可能故意减慢您的连接速度,因为他们认为您是垃圾邮件机器人。如果您使用其他语言执行此操作,请查看它是否会变慢。

标签: python for-loop freeze


【解决方案1】:

您的 SMTP 服务器很可能正在缓送您的客户端连接。这是对失控客户端或提交大量“垃圾”命令的客户端的防御。来自 Postfix smtpd 的手册页:

   smtpd_junk_command_limit (normal: 100, stress: 1)
          The number of junk commands (NOOP, VRFY, ETRN or  RSET)  that  a
          remote  SMTP  client  can  send  before  the Postfix SMTP server
          starts to increment the error counter with each junk command.

看到一定数量的垃圾后,smtpd 守护进程会在回复之前插入 1 秒延迟。如果您对有问题的 smtp 服务器具有 root 访问权限,请尝试 strace 以查看服务器是否正在发出 nanosleep 系统调用。

这是针对我的本地服务器运行脚本的跟踪。在 100 个 VRFY 命令之后,它开始在命令之间休眠。您的服务器可能有大约 15 个垃圾命令的下限:

nanosleep({1, 0}, 0x7fffda9a67a0)       = 0
poll([{fd=9, events=POLLOUT}], 1, 300000) = 1 ([{fd=9, revents=POLLOUT}])
write(9, "252 2.0.0 pat\r\n", 15)       = 15
poll([{fd=9, events=POLLIN}], 1, 300000) = 1 ([{fd=9, revents=POLLIN}])
read(9, "VRFY pat\r\n", 4096)           = 10

【讨论】:

  • 我没有(尽管我确实有权戳它:))。有趣的想法,谢谢
  • 不客气。通过远程登录到 smtpd 端口并手动发出“VRFY 用户”命令很容易确认。如果您发现响应延迟,这肯定表明您的服务器正在缓送。祝你好运!
  • 按照您的建议手动确认。完全相同的行为。看起来问题是由于 SMTP 服务器的配置而不是脚本造成的。很好发现,谢谢!
【解决方案2】:

s.recv 阻塞,所以如果套接字上没有更多数据,它将永远阻塞。
您必须跟踪收到的数据量。您需要提前知道这一点,以便客户端和服务器可以就大小达成一致。

【讨论】:

  • @eat_a_lemon - 感谢您的回答。我不明白为什么它似乎逐渐减速并停止......根据你的回答,我预计套接字会在第一次迭代时阻塞而不是逐渐减速......
  • @Tom 这取决于您从服务器接收数据的速率。
  • 恐怕我还没有这方面的名声......如果/当我这样做时会这样做:)
【解决方案3】:

解决完全相同的问题我也遇到了这个问题。 我几乎可以肯定@samplebias 是对的。我发现我可以通过更多地滥用糟糕的系统、拆除并重建每个连接来解决“tarpitting”问题:

#[ ...Snip... ]
import smtplib
#[ ...Snip... ]
for USER in open(opts.USERS,'r'):
    smtpserver = smtplib.SMTP(HOST,PORT)
    smtpserver.ehlo()
    verifyuser = smtpserver.verify(USER)
    print("%s %s:  %s") % (HOST.rstrip(), USER.rstrip(), verifyuser)
    smtpserver.quit()

我很好奇这种特殊类型的锤击是否会在现场环境中发挥作用,但太确定它会让一些人非常不高兴。

PS、python:包括电池。

【讨论】:

    【解决方案4】:

    一目了然,您的代码没有错误。但是,您会注意到 TCP 不是面向“消息”的协议。因此,您不能在循环中使用 socket.send ,假设每次调用都会通过介质实际发送一条消息。因此,如果某些调用开始在输出缓冲区中缓冲,而您只是在其后调用 socket.recv,您的程序将陷入死锁。

    您应该做的是线程或异步代码。也许 Twisted Framework 可以帮助你。

    【讨论】:

    • 谢谢,我去看看。
    • 这不太正确,不会出现死锁。调用.recv(1024) 肯定有问题(例如响应比预期的短或长),但死锁不会是其中之一。
    • socket.recv 在缓冲区中读取的数据少于当前数据时没有任何问题,其余部分仍将在缓冲区中,并且可以在第一次 recv 调用后的某个时间读取。也许OP可能会做一个实验:在循环中发送所有请求并在此之后读取输入缓冲区。或者,为了不丢失任何数据,使用线程实现程序。从逻辑上讲,之后将结果发送给我们。
    猜你喜欢
    • 2011-10-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-16
    • 2021-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多