Saya mengalami masalah berulang saat berkomunikasi dengan peralatan yang memiliki tautan TCP terpisah untuk mengirim dan menerima. Masalah dasarnya adalah bahwa tumpukan TCP umumnya tidak memberi tahu Anda bahwa soket ditutup ketika Anda hanya mencoba membaca - Anda harus mencoba dan menulis untuk diberi tahu bahwa ujung tautan yang lain terputus. Sebagian, begitulah cara TCP dirancang (membaca bersifat pasif).
Saya menduga jawaban Blair berfungsi dalam kasus di mana soket telah dimatikan dengan baik di ujung yang lain (yaitu mereka telah mengirim pesan pemutusan yang tepat), tetapi tidak dalam kasus di mana ujung yang lain berhenti mendengarkan dengan tidak sopan.
Apakah ada tajuk dengan format tetap di awal pesan Anda, yang dapat Anda mulai dengan mengirim, sebelum seluruh respons siap? misalnya sebuah doctype XML? Apakah Anda juga dapat lolos dengan mengirimkan beberapa ruang tambahan di beberapa titik dalam pesan - hanya beberapa data nol yang dapat Anda keluarkan untuk memastikan soket masih terbuka?
Modul pilih berisi apa yang Anda perlukan. Jika Anda hanya memerlukan dukungan Linux dan memiliki kernel yang cukup baru, select.epoll()
harus memberi Anda informasi yang Anda butuhkan. Sebagian besar sistem Unix akan mendukung select.poll()
.
Jika Anda memerlukan dukungan lintas platform, cara standarnya adalah menggunakan select.select()
untuk memeriksa apakah soket ditandai memiliki data yang tersedia untuk dibaca. Jika ya, tapi recv()
mengembalikan nol byte, ujung lainnya telah ditutup.
Saya selalu menemukan Panduan Beej untuk Pemrograman Jaringan bagus (perhatikan bahwa ini ditulis untuk C, tetapi secara umum berlaku untuk operasi soket standar), sedangkan Panduan Pemrograman Soket memiliki ikhtisar Python yang layak.
Edit :Berikut adalah contoh bagaimana server sederhana dapat ditulis untuk mengantri perintah masuk tetapi berhenti memproses segera setelah menemukan koneksi telah ditutup di ujung jarak jauh.
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()
Untuk menggunakannya, jalankan skrip dan di terminal lain telnet ke localhost, port 7557. Output dari contoh yang saya jalankan, mengantri tiga permintaan tetapi menutup koneksi selama pemrosesan yang ketiga:
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.
alternatif epoll
Pengeditan lainnya: Saya telah membuat contoh lain menggunakan select.epoll
untuk memantau peristiwa. Saya tidak berpikir itu menawarkan lebih dari contoh aslinya karena saya tidak dapat melihat cara untuk menerima acara ketika ujung jarak jauh ditutup. Anda masih harus memantau peristiwa penerimaan data dan memeriksa pesan dengan panjang nol (sekali lagi, saya ingin pernyataan ini terbukti salah).
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)
Catatan :Ini hanya mendeteksi shutdown yang benar. Jika ujung jarak jauh berhenti mendengarkan tanpa mengirim pesan yang tepat, Anda tidak akan tahu sampai Anda mencoba menulis dan mendapatkan kesalahan. Memeriksa yang tersisa sebagai latihan untuk pembaca. Selain itu, Anda mungkin ingin melakukan beberapa pemeriksaan kesalahan pada loop keseluruhan sehingga server itu sendiri dimatikan dengan baik jika ada yang rusak di dalamnya.
Opsi soket KEEPALIVE memungkinkan untuk mendeteksi skenario "putus koneksi tanpa memberi tahu pihak lain" semacam ini.
Anda harus menyetel opsi SO_KEEPALIVE pada level SOL_SOCKET. Di Linux, Anda dapat memodifikasi waktu tunggu per soket menggunakan TCP_KEEPIDLE (detik sebelum mengirim probe keepalive), TCP_KEEPCNT (probe keepalive gagal sebelum menyatakan ujung lainnya mati) dan TCP_KEEPINTVL (interval dalam detik antara probe keepalive).
Dengan Python:
import socket
...
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 5)
netstat -tanop
akan menunjukkan bahwa soket dalam mode keepalive:
tcp 0 0 127.0.0.1:6666 127.0.0.1:43746 ESTABLISHED 15242/python2.6 keepalive (0.76/0/0)
sementara tcpdump
akan menampilkan probe keepalive:
01:07:08.143052 IP localhost.6666 > localhost.43746: . ack 1 win 2048 <nop,nop,timestamp 848683438 848683188>
01:07:08.143084 IP localhost.43746 > localhost.6666: . ack 1 win 2050 <nop,nop,timestamp 848683438 848682438>
01:07:09.143050 IP localhost.6666 > localhost.43746: . ack 1 win 2048 <nop,nop,timestamp 848683688 848683438>
01:07:09.143083 IP localhost.43746 > localhost.6666: . ack 1 win 2050 <nop,nop,timestamp 848683688 848682438>