Итак, минимальный пример вашей ситуации:
import socket, time
import threading
header = 64
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('', 5055))
soc.listen()
conn, addr = soc.accept()
def loop(conn):
while True:
msglen = conn.recv(header).decode('UTF-8')
if msglen:
msglen = int(msglen)
msg = conn.recv(msglen).decode('UTF-8')
print(msg)
conn.send(b'10')
conn.send(b'0123456789')
time.sleep(0.025)
thread = threading.Thread(target=loop, args=(conn,))
thread.start()
Теперь, поскольку многопоточность не имеет к этому никакого отношения, я также могу сократить код до фактической проблемы, которая является частью сокета.
(Это может быть связано с опытом, это также может быть просто поиском проблемы в Интернете, и вы найдете много ресурсов, посвященных «сбросу соединения одноранговым узлом») .
Итак, если мы сведем его к основным компонентам проблемы, это будет выглядеть так:
import socket
# No threading needed to reproduce the problem
header = 64
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('', 5055))
soc.listen()
conn, addr = soc.accept()
while True: # Replaced thread with a simple while loop
msglen = conn.recv(header).decode('UTF-8')
if msglen:
msglen = int(msglen)
msg = conn.recv(msglen).decode('UTF-8')
print(msg)
conn.send(b'10')
conn.send(b'0123456789')
Что дает мне:
PS C:\Users\anton> python .\server.py
hello
Traceback (most recent call last):
File ".\server.py", line 12, in <module>
msglen = conn.recv(header).decode('UTF-8')
ConnectionResetError: [WinError 10054] An existing connection was forcibly closed by the remote host
Теперь моя ошибка сообщение немного отличается. И это потому, что я на windows, а вы * на Linux. Но jizt все тот же: «Сброс соединения» какой-то стороной соединения.
Теперь, почему это?
Все очень просто, потому что другой конец соединения (клиент в в данном случае, поскольку проблема находится на стороне сервера) закрыл соединение. И почему так? Это потому, что ничто не поддерживает жизнь клиента. Все, что он делает, это отправляет hello
, проверяет, есть ли входящие данные, и делает print(f"[{addr}]{msg}")
. После этого клиент завершает работу.
Итак, пока это происходит, вы пытаетесь выполнить (несколько раз) :
conn.recv(msglen)
Что не сработает, потому что на другом конце ничего нет.
Итак, решение вашей проблемы - не выходить из клиента. А также сделайте обработку ошибок. В этом топе c есть о чем рассказать, поскольку серверное и клиентское программное обеспечение имеет много подводных камней. Но в вашем случае, выполняя:
try:
ConnectionResetError
except ConnectionResetError:
self.close()
Или вы можете переключиться на более удобный способ первой проверки наличия данных через библиотеку select .
import socket, select
header = 64
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('', 5055))
soc.listen()
conn, addr = soc.accept()
while True:
readable, writable, exceptional = select.select([conn], [conn], [])
print(readable)
if readable:
msglen = conn.recv(header).decode('UTF-8')
if msglen:
msglen = int(msglen)
msg = conn.recv(msglen).decode('UTF-8')
print(msg)
# Could technically check writable here, just to be sure
print(writable)
conn.send(b'10')
conn.send(b'0123456789')
Но даже тогда это может потерпеть неудачу из-за проблемы синхронизации между проверкой и попыткой чтения. Вот где появляется epoll , он может запускаться по событиям и быть немного более гибким (возможно, вы можете добиться того же с select.select
, но я давно отказался от go, поэтому не Я сам не знаю) .
import socket, select
header = 64
soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
soc.bind(('', 5055))
soc.listen()
conn, addr = soc.accept()
poll_obj = select.epoll()
poll_obj.register(conn.fileno(), select.EPOLLIN | select.EPOLLHUP)
while True:
for fileno, eventid in poll_obj.poll(0.5):
if eventid == select.EPOLLIN:
msglen = conn.recv(header).decode('UTF-8')
if msglen:
msglen = int(msglen)
msg = conn.recv(msglen).decode('UTF-8')
print(msg)
conn.send(b'10')
conn.send(b'0123456789')
else:
print('Client disconnected (prob eventid 25)')
break
Это сначала проверит наличие данных в канале, а затем определит, являются ли данные фактическими данными или это событие базового сокета (RST/ACK
например был отправлен). Если все в порядке, вы, вероятно, сможете получать данные без ошибок. Пока вы обрабатываете разъединения.
Включите это в свой лог потока c снова, и вы должны быть готовы go.