Вы попали в одну из самых распространенных ловушек программирования сокетов TCP. Вы предполагали, что ваш сокет будет отправлять сообщения, в то время как он отправляет и получает только данные и абсолютно не зависит от вашей структуры обмена сообщениями. Даже если вы отправляете данные, используя несколько вызовов send, ваши вызовы recv не получают такую точную структуру, как это ни происходит в буфере. Если вы отправили один байт тысячу раз, ваш recv (1000) получит тысячу байт, и это то, что здесь происходит.
Ваша проблема вызвана тем, что ваш сервер работает немного быстрее вашего клиента. Мне пришлось настроить ваш код, чтобы иметь возможность надежно воспроизводить код, но это делает это:
client_socket.send(filepath.encode())
sleep(1)
response = client_socket.recv(BUFSIZE).decode()
Это эмулирует ваш сервер быстрее, чем клиент, что в конечном итоге произойдет в любом случае. Добавив sleep
, мы можем сделать это каждый раз.
Когда вы вызываете recv на сокете TCP, может произойти одно из следующих пяти событий:
- Нет данных и блоки вызова
- Вы получили данные, и данные, которые вы получили, представляют собой одно «сообщение», что бы это ни было в вашем контексте
- Ваш сервер отправил более одного сообщения, прежде чем вы прочитали из сокета, и вы получили их все за один раз
- Ваш клиент слишком хотел прочитать, и он решил читать, когда была доступна только часть вашего первого сообщения
- Комбинация 3 и 4: вы получаете несколько полных сообщений плюс одно частичное
Что происходит с вашим кодом, так это то, что вашему серверу удалось отправить размер закодированного файла, а также некоторые ваши данные. На вашем клиенте вы теперь предполагаете, что ваш первый recv получает только размер файла, но это никак не гарантировано. Там уже могут быть некоторые данные файла (как вы будете читать BUFSIZE - там может быть почти полный буфер данных), и когда вы пытаетесь декодировать это как целое число, происходят странные вещи, так как данные не соответствуют вашим ожиданиям ,
Единственный надежный способ обработки TCP-сокетов - это чтение из сокета, добавление во временный буфер обработки, затем анализ этого буфера и просмотр того, что там находится. Если есть «сообщение», обработайте его и удалите из буфера. Все, что остается в буфере, должно оставаться там, и ваш следующий результат будет добавлен к этому.
Самый простой способ исправить это, если ваш сервер отправляет исходное сообщение фиксированной длины. Затем вы можете безопасно прочитать именно это количество символов из сокета и обработать его как сообщение о размере / ошибке, а остальные будут данными. Это ужасное исправление во многих отношениях, и вы должны стремиться к чему-то лучшему. «Правильный» способ - разработать протокол, в котором сервер устанавливает разделители, чтобы ваш клиент мог определить, какое сообщение означает, что. Ваш протокол может быть, например,
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()
PS ваш сервер должен поймать ошибку «сброс соединения по одноранговой сети». Это может произойти, если возникнет проблема с сетью или произойдет сбой клиентского приложения. Сервер может спокойно проигнорировать эту ошибку и просто прекратить отправку в этот конкретный клиентский сокет.