Почему мой объект socket.makefile блокируется даже после закрытия? - PullRequest
2 голосов
/ 23 июля 2011

Если я использую socket.makefile и затем закрываю файловый объект, а также нижележащий сокет, то последующие вызовы read вызовут исключение, как я бы этого хотел.Например, следующий код работает так, как я ожидал:

import socket
from time import sleep
from threading import Thread

ADDR = ("localhost", 4321)

def listener(sock):
    client,addr = sock.accept()
    sleep(1)
    client.close()
    sock.close()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
Thread(target=listener, args=[server_sock]).start()

sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)

f.close()
sock.close()
f.read(8)    # throws an exception, as I'd expect

Однако, если я сделаю вызов read, пока файл / сокет все еще открыты, этот вызов заблокируется, и затем если я закрою сокет, метод чтения все равно не вернется.Фактически, он висит бесконечно, пока сокет не будет закрыт на другом конце.Следующий код демонстрирует это удручающее поведение:

import socket
from time import sleep
from threading import Thread

ADDR = ("localhost", 4321)

def reader(f):
    print("about to read")
    print("we read %r" % f.read(8))
    print("finished reading")

def listener(sock):
    client, addr = sock.accept()
    sleep(3)
    client.close()
    sock.close()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
Thread(target=listener, args=[server_sock]).start()

sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
Thread(target=reader, args=[f]).start()

sleep(1)
print("closing pseudo-file and socket")
f.close()
sock.close()
sleep(1)
print("we still haven't finished reading!")

Это серьезная проблема для меня, так как я хотел бы сделать блокирующий вызов на f.read в потоке, но затем все еще иметь возможность закрытьсокет и поток вернется из этого вызова (возможно, вызвав исключение) и выйдет.Однако все, что происходит, это то, что вызов блокируется навсегда, пока другая сторона никогда не закроет сокет.

Таким образом, есть ли способ для Thread1 вызвать read для файлового объекта, созданногоsocket.makefile и затем Thread2 отключить сокет таким образом, что Thread1 прекратит блокировку при вызове read?

РЕДАКТИРОВАТЬ: Я попытался переписатьмоя программа полностью использует gevent и ее сокет и Гринлет подход к многопоточности, но моя программа все еще делает то же самое:

from gevent import sleep, socket, spawn

ADDR = ("localhost", 4321)

def reader(f):
    print("about to read")
    print("we read %r" % f.read(8))
    print("finished reading")

def listener(sock):
    client, addr = sock.accept()
    sleep(3)
    client.close()
    sock.close()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_sock.bind(ADDR)
server_sock.listen(1)
spawn(listener, server_sock)

sock = socket.create_connection(ADDR)
f = sock.makefile("r+b", bufsize=0)
spawn(reader, f)

sleep(1)
print("closing pseudo-file and socket")
f.close()
sock.close()

sleep(1)
print("we still haven't finished reading!")
sleep(2)

Я был удивлен, узнав, что даже при использовании gevent сокетов, вызововread будет блокироваться даже после закрытия основного сокета.Если нет способа предотвратить это, я, вероятно, просто должен принять удручающий ответ Томаса «это невозможно»: (

Ответы [ 2 ]

4 голосов
/ 23 июля 2011

В подобных ситуациях я успешно использовал eventlet , и в наши дни gevent делает то же самое:

'monkey patching' theбиблиотека сокетов для использования неблокирующего ввода / вывода.Вот пример того, что вы можете попробовать:

>>> from gevent import monkey; monkey.patch_socket()
>>> import socket

и посмотрите, как это повлияет на ваши результаты

1 голос
/ 23 июля 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...