python - 为什么服务器和客户端不同步? ( python 套接字)

标签 python sockets

我目前正在编写一个小型客户端-服务器应用程序,用于通过套接字将任意文件从服务器传输到客户端。

服务器一次只能处理一个客户端,但是当一个客户端被服务时,它应该准备好处理一个新的客户端连接。

客户端会请求一个文件,如果文件存在,客户端会收到文件,将其写入磁盘并关闭连接。

服务器代码:

PORT = 9000
BUFSIZE = 1000

def main(argv):
    print('The server is ready to receive')
    server_socket = socket(AF_INET, SOCK_STREAM)
    server_socket.bind(('', PORT))
    server_socket.listen(1)
    while True:
        connection_socket, addr = server_socket.accept()

        try:
            requested_filepath = connection_socket.recv(BUFSIZE).decode()
            print("Client requested the file: " + requested_filepath)
            capital_sentence = requested_filepath.upper()
            if(os.path.isfile(requested_filepath)):
                filesize = str(os.path.getsize(requested_filepath))
                connection_socket.send(filesize.encode())
                with open(requested_filepath, 'rb') as f:
                    while(True):
                        content = f.read(BUFSIZE)
                        if not content:
                            break
                        connection_socket.send(content)
                print('File has been send')
            else:
                error = "error"
                connection_socket.send(error.encode())
        finally: 
            connection_socket.close()

客户端代码:

PORT = 9000
BUFSIZE = 1000

def main(argv):
    servername = argv[0]
    filepath = argv[1]

    client_socket = socket(AF_INET, SOCK_STREAM)    
    client_socket.connect((servername, PORT))
    try:
        client_socket.send(filepath.encode())
        response = client_socket.recv(BUFSIZE).decode()
        if(response != "error"):
            filesize = int(response)
            print("Requested filesize: " + str(filesize))
            filename = filepath.split('/')[-1]
            with open(filename, 'wb') as f:
                while(True):
                    content = client_socket.recv(BUFSIZE)
                    if not content:
                        break
                    f.write(content)
            print('File recived')
        else:
            print("The requested file did not exist")
    finally:
        client_socket.close()

我可以运行服务器并让客户端请求并获取文件,但是当我第二次或第三次运行客户端时,服务器和客户端似乎不同步。两个程序都中断并返回以下错误消息:

客户端错误:

Traceback (most recent call last):
  File "client.py", line 37, in <module>
    main(sys.argv[1:])
  File "client.py", line 16, in main
    response = client_socket.recv(BUFSIZE).decode()
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 6: invalid start byte

服务器错误:

The server is ready to receive
Client requested the file: /pepe.jpeg
File has been send
Client requested the file: /pepe.jpeg
File has been send
Client requested the file: /pepe.jpeg
Traceback (most recent call last):
  File "server.py", line 44, in <module>
    main(sys.argv[1:])
  File "server.py", line 30, in main
    connection_socket.send(content)
ConnectionResetError: [Errno 104] Connection reset by peer

我是否没有以正确的方式关闭套接字连接?

最佳答案

您已陷入最常见的 TCP 套接字编程陷阱之一。你假设你的套接字会发送消息,而它只发送和接收数据并且完全不知道你的消息结构。即使您使用多个 send 调用发送数据,您的 recv 调用也不会收到这个确切的结构,而是缓冲区中的任何内容。如果您发送一个字节一千次,您的 recv(1000) 将收到一千个字节,这就是这里发生的情况。

您的问题是由于您的服务器比您的客户端快一点造成的。我必须调整您的代码才能可靠地重现代码,但这样做了:

client_socket.send(filepath.encode())
sleep(1)
response = client_socket.recv(BUFSIZE).decode()

这模拟您的服务器比客户端更快,无论如何最终都会发生。通过添加 sleep,我们可以让它每次都发生。

当您在 TCP 套接字上调用 recv 时,可能会发生以下五种情况之一:

  1. 没有数据,调用阻塞
  2. 您收到了数据,并且您收到的数据恰好是一条“消息”,无论您的上下文是什么
  3. 在您从套接字中读取之前,您的服务器已发送了不止一条消息,并且您一次性收到了所有消息
  4. 您的客户太想阅读了,当您的第一条消息只有一部分可用时,它决定阅读
  5. 3 和 4 的组合:您收到多条完整消息和一条部分消息

您的代码发生的情况是您的服务器已设法发送编码文件大小和您的一些数据。在您的客户端上,您现在假设您的第一个 recv 仅接收文件大小,但这无法保证。可能已经有一些文件数据(正如您将读取的 BUFSIZE - 那里可能几乎是一个完整的数据缓冲区)并且当您尝试将其解码为整数时,奇怪的事情发生了,因为数据不是您期望的那样.

处理 TCP 套接字的唯一可靠方法是从套接字读取,附加到临时处理缓冲区,然后解析该缓冲区并查看其中的内容。如果有“消息”,则对其进行处理并将其从缓冲区中删除。缓冲区中剩余的任何内容都必须保留在那里,并且您的下一个 recv 结果将附加到此。

快速修复此问题的最简单方法是,如果您的服务器生成固定长度的初始消息。然后您可以安全地从套接字中准确读取这些字符并将其作为大小/错误消息处理,其余的将是数据。这在很多很多方面都是一个可怕的修复,你应该瞄准更好的东西。 “正确”的方法是设计一个协议(protocol),服务器在其中放置定界符,以便您的客户端可以检测到消息的含义。例如,您的协议(protocol)可以是

SIZE: <decimal>\n
DATA: <data>

甚至可以简单地假设换行符之前的所有内容都是文件大小,而后面的所有内容都是数据。

但是即使添加了 sleep(1) 也能更好地工作,因为它现在会将初始消息填充到恰好 100 字节。由于 (4) 这仍然可能出错,所以实际上您需要检查您最初是否收到 100 个字符并继续阅读直到收到为止,但我将把它留给您来实现。

        if(os.path.isfile(requested_filepath)):
            filesize = str(os.path.getsize(requested_filepath))
            connection_socket.send(("%s" % filesize).encode().ljust(100))
            with open(requested_filepath, 'rb') as f:
                while(True):
                    content = f.read(BUFSIZE)
                    if not content:
                        break
                    connection_socket.send(content)
            print('File has been send')
        else:
            error = "error"
            connection_socket.send(error.encode().ljust(100))

客户:

try:
    client_socket.send(filepath.encode())
    sleep(1)
    response_raw = client_socket.recv(100)
    response = response_raw.strip().decode()

请注意,您的服务器应该捕获“连接由对等方重置”错误。如果存在网络问题或客户端应用程序崩溃,则可能会发生这种情况。服务器可以安全地忽略此错误并停止向该特定客户端套接字发送数据。

关于python - 为什么服务器和客户端不同步? ( python 套接字),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55160527/

相关文章:

python - PyCharm,什么是 python_stubs?

python - 在 python 中读取文件时写入 C 中的文件

python - 如何为远程沙盒执行做一个嵌入式python模块?

c - C语言基于UDP的socket编程

c - 使用 recv 填充垃圾的缓冲区

python - 无法从网页中抓取静态信息

Python 如何使用用户名/密码发出 curl 请求

c - 绑定(bind)到零地址的意义是什么

ios - Socket.io 通过套接字对象在服务器上获取事件字符串

Python 3 - 带有 select.select() 的套接字 - 检测连接丢失