【问题标题】:Sending Multiple Files Python Using Socket使用 Socket 发送多个文件 Python
【发布时间】:2018-11-03 23:17:42
【问题描述】:

我目前正在尝试创建一个客户端-服务器应用程序,其中客户端可以使用 TCP 协议将多个文件发送到服务器。服务器最终将创建一个哈希算法并将其发送回客户端,但我遇到了将多个文件从客户端发送到服务器的问题。在当前的形式中,第一个文件正确发送,但之后的文件遇到信息合并在一起的错误。 IE 将文件大小列为第二个文件的名称。我是一个 javascript 花花公子,对 python 非常陌生,因此我将不胜感激如何实现这一点的解释。我相信线程是答案,但由于我对 python 的了解有限,我不知道如何使这项工作。目前我可以一次发送一个文件并且服务器保持打开状态。但是,我想从当前目录中输入几个文件名并对其进行处理。我最终会将整个客户端转换为 C,但我正在努力让服务器在 python 中正常工作。任何建议将不胜感激!

服务器.py

import socket
import hashlib
import threading
import struct

HOST = '127.0.0.1'
PORT = 2345

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(10)
print("Waiting for a connection.....")

conn, addr = s.accept()
print("Got a connection from ", addr)

while True:

hash_type = conn.recv(1024)
print('hash type: ', hash_type)
if not hash_type:
    break

file_name = conn.recv(1024)
print('file name: ', file_name)

file_size = conn.recv(1024)
file_size = int(file_size, 2)
print('file size: ', file_size )

f = open(file_name, 'wb')
chunk_size = 4096
while file_size > 0:
    if file_size < chunk_size:
        chuk_size = file_size
    data = conn.recv(chunk_size)
f.write(data)

file_size -= len(data)
f.close()
print('File received successfully')
s.close()

客户端.py

import socket
import threading
import os

HOST = '127.0.0.1'
PORT = 2345

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

hash_type = input('Enter hash type: ')

files = input('Enter file(s) to send: ')
files_to_send = files.split()

for file_name in files_to_send:
s.send(hash_type.encode())

print(file_name)
s.send(file_name.encode())

file_size = os.path.getsize(file_name)
file_size = bin(file_size)
print(file_size)
s.send(file_size.encode())

f = open(file_name, 'rb')
l = f.read()
while(l):
    s.send(l)
    l = f.read()
f.close()
print('File Sent')

s.close()

【问题讨论】:

  • 您错误地使用了套接字。 TCP 是一种流协议。您必须在其之上构建一些协议,才能正确分离文件。将众所周知的协议(如 HTTP、FTP 等)与库一起使用。
  • 哦,对不起,这就是扭曲。我只想使用 TCP 协议
  • HTTP 和 FTP 只使用 TCP。它们是用于分离正确发送的项目的协议。你可以自己发明,但何必呢?
  • 谢谢!感谢您的建议,使用 FTP 更容易!

标签: python sockets


【解决方案1】:

处理您正在做的事情的一种方法是缓冲您的套接字数据。下面是一个缓冲数据并知道如何发送和接收以空字符结尾、UTF-8 编码的字符串和原始字节块的类:

buffer.py:

class Buffer:
    def __init__(self,s):
        '''Buffer a pre-created socket.
        '''
        self.sock = s
        self.buffer = b''

    def get_bytes(self,n):
        '''Read exactly n bytes from the buffered socket.
           Return remaining buffer if <n bytes remain and socket closes.
        '''
        while len(self.buffer) < n:
            data = self.sock.recv(1024)
            if not data:
                data = self.buffer
                self.buffer = b''
                return data
            self.buffer += data
        # split off the message bytes from the buffer.
        data,self.buffer = self.buffer[:n],self.buffer[n:]
        return data

    def put_bytes(self,data):
        self.sock.sendall(data)

    def get_utf8(self):
        '''Read a null-terminated UTF8 data string and decode it.
           Return an empty string if the socket closes before receiving a null.
        '''
        while b'\x00' not in self.buffer:
            data = self.sock.recv(1024)
            if not data:
                return ''
            self.buffer += data
        # split off the string from the buffer.
        data,_,self.buffer = self.buffer.partition(b'\x00')
        return data.decode()

    def put_utf8(self,s):
        if '\x00' in s:
            raise ValueError('string contains delimiter(null)')
        self.sock.sendall(s.encode() + b'\x00')

有了这个类,你的客户端和服务器就变成了:

client.py:

import socket
import threading
import os

import buffer

HOST = '127.0.0.1'
PORT = 2345

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

with s:
    sbuf = buffer.Buffer(s)

    hash_type = input('Enter hash type: ')

    files = input('Enter file(s) to send: ')
    files_to_send = files.split()

    for file_name in files_to_send:
        print(file_name)
        sbuf.put_utf8(hash_type)
        sbuf.put_utf8(file_name)

        file_size = os.path.getsize(file_name)
        sbuf.put_utf8(str(file_size))

        with open(file_name, 'rb') as f:
            sbuf.put_bytes(f.read())
        print('File Sent')

server.py:

import socket
import os

import buffer

HOST = ''
PORT = 2345

# If server and client run in same local directory,
# need a separate place to store the uploads.
try:
    os.mkdir('uploads')
except FileExistsError:
    pass

s = socket.socket()
s.bind((HOST, PORT))
s.listen(10)
print("Waiting for a connection.....")

while True:
    conn, addr = s.accept()
    print("Got a connection from ", addr)
    connbuf = buffer.Buffer(conn)

    while True:
        hash_type = connbuf.get_utf8()
        if not hash_type:
            break
        print('hash type: ', hash_type)

        file_name = connbuf.get_utf8()
        if not file_name:
            break
        file_name = os.path.join('uploads',file_name)
        print('file name: ', file_name)

        file_size = int(connbuf.get_utf8())
        print('file size: ', file_size )

        with open(file_name, 'wb') as f:
            remaining = file_size
            while remaining:
                chunk_size = 4096 if remaining >= 4096 else remaining
                chunk = connbuf.get_bytes(chunk_size)
                if not chunk: break
                f.write(chunk)
                remaining -= len(chunk)
            if remaining:
                print('File incomplete.  Missing',remaining,'bytes.')
            else:
                print('File received successfully.')
    print('Connection closed.')
    conn.close()

演示

客户:

Enter hash type: abc
Enter file(s) to send: demo1.dat demo2.dat
demo1.dat
File Sent
demo2.dat
File Sent

服务器:

Waiting for a connection.....
Got a connection from  ('127.0.0.1', 22126)
hash type:  abc
file name:  uploads\demo1.dat
file size:  488892
File received successfully.
hash type:  abc
file name:  uploads\demo2.dat
file size:  212992
File received successfully.
Connection closed.

【讨论】:

    【解决方案2】:

    1。 file_size = conn.recv(1024) 在您的服务器代码中,您读取 1024 个字节作为文件大小,文件大小只有 4 或 8 个字节长

    2。 file_name = conn.recv(1024)您的服务器不知道文件名/哈希类型有多长。

    -> 对两种大小都使用 long,并且只从流中读取 sizeof(long) 个字节。

    您可以使用https://docs.python.org/2/library/struct.html 对这些数字进行打包/编码

    -> 或者直接使用https://docs.python.org/3/library/pickle.html 进行序列化

    【讨论】:

    • 谢谢!您可以看到我正在考虑使用 struct(我导入了它),但我很欣赏 pickle 方法。我认为这是更简单的方法......我需要导入 json 才能发送它还是我可以只腌制加载/卸载?
    • conn.recv 最多读取 1024。您必须始终认为,它每次只读取 1 个字节。 pickle 很危险。可以用它破解服务器。
    • 感谢丹尼尔的建议。为了安全起见,我会避免泡菜
    猜你喜欢
    • 2015-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-28
    • 1970-01-01
    相关资源
    最近更新 更多