Многопоточность для обработки нескольких входящих запросов

#python #multithreading #sockets

#python #многопоточность #сокеты

Вопрос:

Я новичок в python. И я должен написать прослушиватель / сервер, который будет обрабатывать запросы, поступающие из той же сети. Итак, я использую socket . Я буду получать запрос json от клиента, сервер / прослушиватель выполнит обработку и вернет результат. Вот и все, что произойдет. И между сервером и клиентом не будет обратной связи.

Код выглядит примерно так:

 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
server_address = ('localhost', 10000)            
sock.bind(server_address)    
sock.listen(1)

while True: 
    connection, client_address = sock.accept()

    while True:
        #read request data
        #process request 
        #reply response to **connection** object
        break  #continue waiting for new request
 

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

 requestHandler = RequestHandler(deepcopy(connection))
requestHandler.daemon = True
requestHandler.start()  
 

Однако здесь я застрял. Могу ли я передавать один и тот же connection объект в новые экземпляры RequestHandler , создаваемые каждый раз, когда я получаю новый запрос? Глубокое копирование, как в приведенном выше коде, дало мне следующую ошибку:

 TypeError: Cannot serialize socket object
 

Я делаю что-то абсолютно неправильное? Должен ли я следовать какому-то другому подходу?

Ответ №1:

Нет необходимости расширять Thread класс. Самая простая версия такова:

 import threading

def client_handler(connection):
    #read request data
    #process request 
    #reply response to **connection** object

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)    
server_address = ('localhost', 10000)            
sock.bind(server_address)    
sock.listen(1)

while True: 
    connection, client_address = sock.accept()
    threading.Thread(target=client_handler, args=(connection,)).start()
 

Теперь вы можете обернуть свой объект сокета в RequestHandler класс, если хотите, это нормально. Но вы не можете копировать сокеты. Вместо этого просто передайте ссылку : RequestHandler(connection) . Зачем вам вообще его копировать?

Причина, по которой он не может быть скопирован, заключается в том, что он представляет некоторый системный ресурс (т. Е. Сокет). Если бы было 2 объекта, указывающих на один и тот же ресурс, то они конфликтовали бы друг с другом. И на уровне C чрезвычайно сложно синхронизировать чистые файловые дескрипторы (сокеты — это просто числа на уровне C), если есть несколько владельцев. Таким образом, объект socket является своего рода уникальным «шлюзом» для базового файлового дескриптора.

Комментарии:

1. (1) итак, без копирования он сможет принимать несколько входящих запросов? Что, если я передаю connection ссылку на объект в один поток, а приходит другой запрос, который присваивает новый экземпляр connection переменной в строке connection, client_address = sock.accept() ? (2) Будет ли создаваться новый объект подключения для каждого входящего запроса? (3) Также будет ли мой подход к расширению класса потоков работать нормально? Я полагаю target=client_handler , что внутри происходит то же самое?

2.@Mahesha999 1) Переменные в Python являются указателями (или ссылками). Когда вы это делаете connection, client_address = sock.accept() , вы не копируете и не изменяете какой-либо объект. Вы просто назначаете новый объект сокета старой connection переменной. Но на старый connection объект это никак не влияет.

3. 2) Нет, для каждого нового подключенного клиента будет новый объект подключения. Обычно клиент и сервер заключают эту «сделку»: «теперь я подключен к вам по этому каналу столько, сколько потребуется». Один клиент может выдавать несколько запросов. В вашем случае, однако, кажется, что вы хотите закрыть соединение после первого запроса. Итак, в вашем случае ответ — да. 3) Да, это будет работать нормально. Просто в этом нет никакой необходимости.

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