我正在尝试通过套接字将数据发送到相同的IP,但通过不同的端口。以下是我到目前为止开发的测试脚本:
服务器:
# test_server.py
import socket
import select
# module-level variables ##############################################################################################
HOST1 = '127.0.0.1'
PORT1 = 65432
HOST2 = '127.0.0.1'
PORT2 = 65433
#######################################################################################################################
def main():
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.bind((HOST1, PORT1))
sock1.listen()
conn1, addr1 = sock1.accept()
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.bind((HOST2, PORT2))
sock2.listen()
conn2, addr2 = sock2.accept()
conns = [ conn1, conn2 ]
while True:
readyConns, _, _ = select.select(conns, [], [])
for conn in readyConns:
data = conn.recv(1024)
if not data:
print('no data received')
else:
print('received: ' + data.decode("utf-8"))
# end if
conn.sendall(bytes('acknowledgement from server', 'utf-8'))
# end for
# end while
# end main
#######################################################################################################################
if __name__ == '__main__':
main()客户端:
# test_client.py
import socket
import time
# module-level variables ##############################################################################################
HOST1 = '127.0.0.1'
PORT1 = 65432
HOST2 = '127.0.0.1'
PORT2 = 65433
#######################################################################################################################
def main():
myCounter = 1
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.connect((HOST1, PORT1))
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.connect((HOST2, PORT2))
while True:
# sock1 ############################################
# send the original message
messageAsStr1 = 'message 1-' + myCounter
sock1.sendall(bytes(messageAsStr1, 'utf-8'))
# receive the acknowledgement
ack1 = sock1.recv(1024)
if ack1 is None:
print('error receiving acknowledgement on port 1')
else:
print('received: ' + ack1.decode('utf-8'))
# end if
time.sleep(2)
# sock2 ############################################
# send the original message
messageAsStr2 = 'message 2-' + myCounter
sock2.sendall(bytes(messageAsStr2, 'utf-8'))
# receive the acknowledgement
ack2 = sock2.recv(1024)
if ack2 is None:
print('error receiving acknowledgement on port 2')
else:
print('received: ' + ack2.decode('utf-8'))
# end if
time.sleep(2)
myCounter += 1
# end while
# end main
#######################################################################################################################
if __name__ == '__main__':
main()如果我先启动test_server.py,然后启动test_client.py,test_server.py将成功启动,但在启动test_client.py时,我会得到:
$ python3 test_client.py
Traceback (most recent call last):
File "test_client.py", line 66, in <module>
main()
File "test_client.py", line 23, in main
sock2.connect((HOST2, PORT2))
ConnectionRefusedError: [Errno 111] Connection refused我不明白为什么第二个连接不能通过,b/c如果我将test_client.py分成两个独立的程序如下:
# test_client1.py
import socket
import time
# module-level variables ##############################################################################################
HOST1 = '127.0.0.1'
PORT1 = 65432
#######################################################################################################################
def main():
myCounter = 1
sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock1.connect((HOST1, PORT1))
while True:
# sock1 ############################################
# send the original message
messageAsStr1 = 'message 1-' + str(myCounter)
sock1.sendall(bytes(messageAsStr1, 'utf-8'))
# receive the acknowledgement
ack1 = sock1.recv(1024)
if ack1 is None:
print('error receiving acknowledgement on port 1')
else:
print('received: ' + ack1.decode('utf-8'))
# end if
myCounter += 1
time.sleep(2)
# end while
# end main
#######################################################################################################################
if __name__ == '__main__':
main()和:
# test_client2.py
import socket
import time
# module-level variables ##############################################################################################
HOST2 = '127.0.0.1'
PORT2 = 65433
#######################################################################################################################
def main():
myCounter = 1
sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock2.connect((HOST2, PORT2))
while True:
# sock2 ############################################
# send the original message
messageAsStr2 = 'message 2-' + str(myCounter)
sock2.sendall(bytes(messageAsStr2, 'utf-8'))
# receive the acknowledgement
ack2 = sock2.recv(1024)
if ack2 is None:
print('error receiving acknowledgement on port 2')
else:
print('received: ' + ack2.decode('utf-8'))
# end if
myCounter += 1
time.sleep(2)
# end while
# end main
#######################################################################################################################
if __name__ == '__main__':
main()然后按test_server.py,test_client1.py,test_client2.py的顺序启动它们,我得到了预期的结果:
(第一个命令提示符):
$ python3 test_server.py
received: message 1-1
received: message 2-1
received: message 1-2
received: message 2-2
received: message 1-3
received: message 2-3
received: message 1-4
received: message 2-4(第二个命令提示符):
$ python3 test_client1.py
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server(第三个命令提示符):
python3 test_client2.py
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server现在我的问题是:
1)为什么第一种方式(在test_client.py中使用不同的端口号)不起作用,但如果我将它分成两个脚本,它就会起作用?
2)有没有更优雅/更健壮的方法来实现同样的目标?值得一提的是,在我的最终程序中,我绝对需要确认内容以及使用不同端口号和IP的灵活性。
3)上面的另一个限制是在第二种情况下,我必须按照test_server.py,test_client1.py,test_client2.py的特定顺序启动程序。在生产版本中,我最终要做的顺序会有所不同。有没有推荐的改变来优雅地处理这个问题?
发布于 2019-11-11 12:46:00
我明白为什么这会令人困惑。幸运的是,解释很简单。
问题1-阻塞调用和竞争条件
在你的服务器脚本中,当你告诉它接受()时,你的脚本会“等待”(我们说这个函数调用是“阻塞”的)。您的服务器脚本的其余部分甚至还没有被查看。现在你去运行你的客户端脚本,它在第一个端口上连接,这会导致你的服务器脚本中的调用解除阻塞并继续。现在“竞争”开始了,在客户端下一次调用connect()之前,您的服务器脚本是否会执行listen()调用,然后再接受()??也许吧!但不太可能,除非你让你的客户睡着了。然后你就会看到它每次都能工作。但这不是正确的解决方案。
问题2-“优雅”的方式
您真的希望同时在X个端口上等待连接,而不是串行连接。要做到这一点,简单的方法是使用python多处理模块启动并行服务器线程的执行。但是,这是一个沉重的解决方案。
真正正确的方法是使用select(),这是一种官方方式,表示“我只是一个进程,但我想侦听多个事物(端口)上的等待活动”。因此,您将设置多个套接字,然后让select列出您想要等待的东西。它将阻塞,直到其中任何一个都有活动为止。您完成了处理它的工作,然后再次循环到select()以阻塞并等待更多的活动(可能下一次是在不同的端口上,或者可能是在同一端口上的新连接)。
有一个重要的警告,这可能会使选择一个“高级”工具。在处理请求时,请注意不要花费太多时间进行处理,因为所有其他连接都在等待。有一些技术可以正确地做到这一点,例如使用libevent管理所有套接字和文件io。
使用多处理更简单,但它不会像架构良好的基于事件的设计那样具有伸缩性。Here's讨论了一个这样的例子,Apache与Nginx。基于事件处理架构的解决方案的另一个强大功能的示例是NodeJS,它以在事件循环中运行一切而闻名。
所有这些细节都是要强调的,因为您说过您将在下一个中逐步实现生产,所以有很多需要考虑的地方。
最佳解决方案:专注于解决您的实际问题,让其他人设计服务器。学习使用gunicorn或wsgi (已经很好的服务器了),并把你的请求处理放在那里。
问题3-客户端的正确方法
当然,你也会想让你的客户变得更好。当然,与客户打交道的正确方式是不要期望一切都是完美的。您正在尝试连接到一台远程计算机。网络可能停机,服务器可能离线,等等。因此,从客户机的流行选择中选择您的策略:
因此,一般的答案是:捕获异常,然后决定要做什么。在循环、保释或“其他”中等待并重试?
https://stackoverflow.com/questions/58795028
复制相似问题