Модуль select содержит то, что вам нужно.Если вам нужна только поддержка Linux и у вас достаточно свежее ядро, select.epoll()
предоставит вам необходимую информацию.Большинство систем Unix поддерживают select.poll()
.
. Если вам требуется межплатформенная поддержка, стандартным способом является использование select.select()
для проверки, помечен ли сокет как имеющий данные, доступные для чтения.Если это так, но recv()
возвращает ноль байтов, другой конец завис.
Я всегда находил Руководство Биджа по сетевому программированию хорошим (обратите внимание, он написан для C,но обычно применимо к стандартным операциям с сокетами), в то время как Руководство по программированию сокетов имеет хороший обзор Python.
Edit : Ниже приведен пример того, какпростой сервер может быть записан в очередь для входящих команд, но завершить обработку, как только он обнаружит, что соединение удалено на удаленном конце.
import select
import socket
import time
# Create the server.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), 7557))
serversocket.listen(1)
# Wait for an incoming connection.
clientsocket, address = serversocket.accept()
print 'Connection from', address[0]
# Control variables.
queue = []
cancelled = False
while True:
# If nothing queued, wait for incoming request.
if not queue:
queue.append(clientsocket.recv(1024))
# Receive data of length zero ==> connection closed.
if len(queue[0]) == 0:
break
# Get the next request and remove the trailing newline.
request = queue.pop(0)[:-1]
print 'Starting request', request
# Main processing loop.
for i in xrange(15):
# Do some of the processing.
time.sleep(1.0)
# See if the socket is marked as having data ready.
r, w, e = select.select((clientsocket,), (), (), 0)
if r:
data = clientsocket.recv(1024)
# Length of zero ==> connection closed.
if len(data) == 0:
cancelled = True
break
# Add this request to the queue.
queue.append(data)
print 'Queueing request', data[:-1]
# Request was cancelled.
if cancelled:
print 'Request cancelled.'
break
# Done with this request.
print 'Request finished.'
# If we got here, the connection was closed.
print 'Connection closed.'
serversocket.close()
Чтобы использовать его, запустите скрипт и в другом терминале telnet дляlocalhost, порт 7557. Вывод из примера запуска, который я выполнил, поставил в очередь три запроса, но закрыл соединение во время обработки третьего:
Connection from 127.0.0.1
Starting request 1
Queueing request 2
Queueing request 3
Request finished.
Starting request 2
Request finished.
Starting request 3
Request cancelled.
Connection closed.
альтернатива epoll
Другойedit: Я разработал еще один пример, используя select.epoll
для мониторинга событий.Я не думаю, что это предлагает больше по сравнению с оригинальным примером, так как я не вижу способа получить событие, когда удаленный конец зависает.Вам по-прежнему необходимо отслеживать событие получения данных и проверять наличие сообщений нулевой длины (опять же, я хотел бы, чтобы в этом утверждении было ошибочным).
import select
import socket
import time
port = 7557
# Create the server.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), port))
serversocket.listen(1)
serverfd = serversocket.fileno()
print "Listening on", socket.gethostname(), "port", port
# Make the socket non-blocking.
serversocket.setblocking(0)
# Initialise the list of clients.
clients = {}
# Create an epoll object and register our interest in read events on the server
# socket.
ep = select.epoll()
ep.register(serverfd, select.EPOLLIN)
while True:
# Check for events.
events = ep.poll(0)
for fd, event in events:
# New connection to server.
if fd == serverfd and event & select.EPOLLIN:
# Accept the connection.
connection, address = serversocket.accept()
connection.setblocking(0)
# We want input notifications.
ep.register(connection.fileno(), select.EPOLLIN)
# Store some information about this client.
clients[connection.fileno()] = {
'delay': 0.0,
'input': "",
'response': "",
'connection': connection,
'address': address,
}
# Done.
print "Accepted connection from", address
# A socket was closed on our end.
elif event & select.EPOLLHUP:
print "Closed connection to", clients[fd]['address']
ep.unregister(fd)
del clients[fd]
# Error on a connection.
elif event & select.EPOLLERR:
print "Error on connection to", clients[fd]['address']
ep.modify(fd, 0)
clients[fd]['connection'].shutdown(socket.SHUT_RDWR)
# Incoming data.
elif event & select.EPOLLIN:
print "Incoming data from", clients[fd]['address']
data = clients[fd]['connection'].recv(1024)
# Zero length = remote closure.
if not data:
print "Remote close on ", clients[fd]['address']
ep.modify(fd, 0)
clients[fd]['connection'].shutdown(socket.SHUT_RDWR)
# Store the input.
else:
print data
clients[fd]['input'] += data
# Run when the client is ready to accept some output. The processing
# loop registers for this event when the response is complete.
elif event & select.EPOLLOUT:
print "Sending output to", clients[fd]['address']
# Write as much as we can.
written = clients[fd]['connection'].send(clients[fd]['response'])
# Delete what we have already written from the complete response.
clients[fd]['response'] = clients[fd]['response'][written:]
# When all the the response is written, shut the connection.
if not clients[fd]['response']:
ep.modify(fd, 0)
clients[fd]['connection'].shutdown(socket.SHUT_RDWR)
# Processing loop.
for client in clients.keys():
clients[client]['delay'] += 0.1
# When the 'processing' has finished.
if clients[client]['delay'] >= 15.0:
# Reverse the input to form the response.
clients[client]['response'] = clients[client]['input'][::-1]
# Register for the ready-to-send event. The network loop uses this
# as the signal to send the response.
ep.modify(client, select.EPOLLOUT)
# Processing delay.
time.sleep(0.1)
Примечание : это толькообнаруживает правильные отключения.Если удаленный конец просто перестает слушать, не отправляя правильные сообщения, вы не узнаете, пока не попробуете написать и получить ошибку.Проверка на это оставлена в качестве упражнения для читателя.Кроме того, вы, вероятно, захотите выполнить некоторую проверку ошибок в общем цикле, чтобы сам сервер корректно завершил работу, если внутри него что-то сломалось.