Traceback с сокетами и многопоточностью - PullRequest
0 голосов
/ 25 сентября 2019

Я создаю два простых скрипта Python для сервера-клиента, которые отображают команды.То есть сервер открывает сокет, затем клиент подключается, отправляет простое сообщение, затем сервер возвращает сообщение обратно клиенту, который затем печатает его.Я пытаюсь сделать это многопоточным, и вот здесь возникает проблема.

Я следую этому Real Python учебнику, и смогуспешно запустите echo-client.py и echo-server.py .Я понимаю основную концепцию и могу просто следовать коду.Поэтому я добавил код в серверную программу, чтобы сделать его многопоточным:

server-multithreaded.py

#!/usr/bin/env python3

import socket
from threading import Thread

HOST = "127.0.0.1"
PORT = 65432
BUFFER_SIZE = 16


def handler(conn, addr):
    while True:
        data = conn.recv(BUFFER_SIZE)
        print(f"Server received {len(data)} bytes from {addr[0]}")
        if not data:
            break
        conn.sendall(data)


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print(f"Connection from {addr[0]}")
        Thread(target=handler, args=(conn, addr)).start()

И вот клиентская программа.

client.py

#!/usr/bin/env python3

import socket

HOST = "127.0.0.1"
PORT = 65432
BUFFER_SIZE = 16

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b"Hello, world!")
    data = s.recv(BUFFER_SIZE)

print(f"Received {data}")

Когда я запускаю многопоточный сервер, я получаю следующую трассировку:

Connection from 127.0.0.1
Server received 13 bytes from 127.0.0.1
Exception in thread Thread-1:
Traceback (most recent call last):
  File "D:\Program Files\Python\Python37\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "D:\Program Files\Python\Python37\lib\threading.py", line 865, in run
    self._target(*self._args, **self._kwargs)
  File "server-multithreaded-new.py", line 17, in handler
    conn.sendall(data)
OSError: [WinError 10038] An operation was attempted on something that is not a socket

Кроме того, клиент не получает полный текст «Hello, World!»обратное сообщение (как в не многопоточной версии).Вместо этого он просто получает пустую байтовую строку:

Received b''

Я предполагаю, что каким-то образом сокет, кажется, закрывается, пока данные все еще передаются, но я не знаю почему.В конце концов, я понимаю, что менеджер контекста with должен обрабатывать закрытие разумно и автоматически.

Кто-нибудь знает, почему это происходит, и как я могу это исправить?

1 Ответ

0 голосов
/ 25 сентября 2019

В конце концов, я понимаю, что менеджер контекста должен автоматически и автоматически обрабатывать закрытие.

Ну, да, и нет.Диспетчер контекста закроет сокет , когда выполнение выйдет из области действия префикса with , то есть почти сразу после запуска вашего потока.Между тем, ваш поток все еще работает (параллельно с основным потоком), и в какой-то момент через несколько миллисекунд после его запуска (или, возможно, даже до его запуска, в зависимости от того, как ОС планирует новый поток), он обнаруживает, чтоосновной поток закрыл свой сокет из-под него.

В частности, менеджер контекста в основном потоке недостаточно умен, чтобы понять, что вы передали ссылку на сокет в отдельный поток, которыйзанят, используя это;он просто всегда закрывает сокет при выходе из области видимости, точка.

Простое решение этой проблемы - просто назначить сетевой поток ответственным за закрытие сокета, а не основного потока;Например, вы можете поместить код менеджера контекста / with-block внутри сетевого потока, а не в основной поток, например:

# network I/O thread
def handler(conn, addr):
    with conn:
        while True:
            data = conn.recv(BUFFER_SIZE)
            print(f"Server received {len(data)} bytes from {addr[0]}")
            if not data:
               break
            conn.sendall(data)

# main thread
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    print(f"Connection from {addr[0]}")
    Thread(target=handler, args=(conn, addr)).start()
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...