【问题标题】:How to establish a 2 way communication using sockets in Python?如何在 Python 中使用套接字建立 2 路通信?
【发布时间】:2018-08-11 00:31:50
【问题描述】:

所以,我有一个服务器脚本,它从客户端脚本接收图像,并且应该发送一个“OK”确认。但确认永远不会通过。

服务器脚本 -

import socket,sys               

s = socket.socket()         
print("Socket successfully created")

port =80                

s.bind(('', port))        
print("socket binded to %s" %(port))
s.listen(5)     
print("socket is listening")            

while True:
   c, addr = s.accept()     
   print('Got connection from', addr)
   file_name=s.recv(1024)
   file_name=fil_ename.decode("utf-8")
   with open(file_name,"wb")as f:
      while True:
         data=c.recv(1024)
         if not data:
            break
         f.write(data)

   c.send(bytes('Thank you ! File received.',"utf-8"))
   c.close()

客户端脚本 -

import socket            

s = socket.socket()         

# Define the port on which you want to connect
port = 80              

s.connect(('IP address of my server', port))
s.send(bytes("hand.jpeg","utf-8"))
f=open("back.jpeg","rb")
data=f.read(512)
while data:
    s.send(data)
    data=f.read(512)
f.close()
print(s.recv(10))

服务器没有发送任何确认,似乎卡在了 for 循环中。但是,如果我从服务器脚本中删除c.send(bytes('Thank you ! File received.',"utf-8")) 行,则代码运行良好。此外,如果我从服务器端删除接收部分并只发送确认部分,即 c.send(bytes('Thank you ! File received.',"utf-8")) ,客户端会收到消息。但是,如果如代码所示在服务器端进行接收(图像文件)和确认的组合,则服务器端无法响应。

需要注意的重要一点是,在上述程序的 KeyBoardInterrupt-ing 上,它显示服务器端脚本被挂起/卡在 data=c.recv(1024) 行中。但是如果删除确认行,同样的问题就会消失。

注意:- 客户端脚本在我的本地计算机上运行,​​而服务器端脚本在 Google Cloud VM 实例上运行。

请帮忙。 谢谢。

【问题讨论】:

    标签: python-3.x sockets client-server google-compute-engine python-sockets


    【解决方案1】:

    这里

    while True:
        data=c.recv(1024)
        if not data:
           break
        f.write(data)
    

    它在收到一条消息后循环回到等待消息,因为您在接收到数据后没有中断 while 循环。 if not data: 没有做任何事情,因为 recv() 停止并等待它收到消息,因此 data 永远不会什么都不是。您应该在收到消息后通过在f.write(data) 之后添加break 来中断循环,或者在循环中发送OK。

    【讨论】:

    • 那么,一旦收到所有数据,我该如何打破循环?
    【解决方案2】:

    嗯...我不认为我完全相信你对行为的描述。但我知道出了什么问题。您的服务器处于接收循环中是完全合理的,因为客户端没有向连接发出 EOF 信号。在什么情况下你认为这实际上会break

    if not data:
        break
    

    答案是客户端需要close 套接字,或者使用shutdown(SHUT_WR) 表示它不会再发送任何数据。所以在客户端做你想做的事:

    ...
    f.close()
    s.shutdown(socket.SHUT_WR)
    ...
    

    现在服务器下次调用recv时,会返回一个空字符串,并取上面的break

    这会使连接在一个方向上打开,而不是在另一个方向上。因此客户端将无法再发送任何数据。但是,在 关闭套接字(或使用 shutdown 本身)之前,服务器仍然能够发送到客户端。

    还有一个更微妙的问题。您假设您的第一个服务器端 recv 将仅接收包含您的文件名的字节。 99.9% 的时间都可以。但是,当服务器第一次调用recv 时,来自下一个客户端send 的数据可能可用。这可能会给您一个虚假的文件名(尽管不一定是非法的),并且肯定意味着您的文件没有被忠实地传输。

    您永远不应假设一个对等方的单个send 提供的数据将由另一侧相应的单个recv 接收。接收到的数据可能或多或少,由应用程序来构建数据以确保它准确接收到预期的数量。

    【讨论】:

    • 非常感谢您的回答。我现在意识到我的错误了。只是补充一下你上面解释的内容,假设随着文件名,一些冗余数据也被传输,我将如何防止它?我应该从客户端关闭,一旦文件名被传输并等待它的确认?如果我这样做,我将无法传输图像内容本身。对不起,我是 python 网络方面的新手,所以我不知道我将如何实际建立这种 2 路连接。
    • 或者,我如何可靠地发送 EOF ?我曾尝试发送一个自定义 EOF,如 xxxclosexxx,然后尝试在服务器端对其进行解码,但它一直向我抛出 utf-8 解码错误。
    • 好的,我想出了一种发送 EOF 的方法。在发送实际图像之前,我已经发送了文件的长度,以提醒服务器传入的总字节数,然后一次发送一个字节以进行图像传输。在这种方法中,即使在传输发生后我的套接字也是打开的,因此如果需要,我可以发送另一条消息。不过感谢您的帮助。
    • 是的。通常协议是二进制的(您在每个元素之前发送一个长度[本身在具有指定字节顺序的固定长度二进制字段中编码];在接收方,您读取固定长度字段,它告诉您后续可变长度的大小字段),或者它们是基于文本的,如 HTTP 1.X 在这种情况下,您有一些保留的分隔符允许您解析元素(HTTP 使用 CR-LF 作为分隔符)。二进制协议对初学者来说设置起来有点困难,但从长远来看,使得接收端的解析更加更简单。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-18
    • 2021-11-07
    • 2014-09-23
    • 1970-01-01
    • 1970-01-01
    • 2021-07-01
    • 1970-01-01
    相关资源
    最近更新 更多