Python 3 - Sockets with select.select() - detecting loss of connection - python

I've got a Python 3 server script which runs a TCP socket server, detecting and responding to incoming data using select.select()
I'm using select.select() to handle multiple connections without threading and the server is mainly reactive (only waits for data and responds to that). It keeps a dictionary for each connection and parameters of the device at the other end; each device's entry is deleted upon its connection being closed.
My problem is my clients will sometimes lose connection without actually closing the TCP socket, I can't work out how to catch or create a timeout to close the sockets and remove old connections from the dictionary.
Is there a good way to do this?
Here's a simplified copy of the script:
host = '192.168.0.252'
port = 9989
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host,port))
server.listen(16)
socks = [server]
devices = {}
while True:
readable,writable,exceptionavailable = select.select(socks,[],[])
for s in readable:
if(s == server):
client, address = server.accept()
socks.append(client)
else:
try: data = s.recv(1024)
except ConnectionResetError: data = 0
if data:
print(data) # Would append device to "devices" dictionary
else:
s.close()
socks.remove(s)
del(devices[did]) # did is the ID that needs deleting from dictionary
Any help would be much appreciated.

Edit: Updated with better code per #Daniel's comment.
Let's say you want to close a connection if you haven't read from it in X seconds. Then you have to:
For each socket keep track of the last time you read from it.
Each time select returns update the last read time and close the connections which have timed-out.
In this code a connection's timeout is set to 300 seconds.
lastread = {} # a dictionary with sockets as keys
...
readable,_,_ = select.select(socks,[],[], 60)
now = time()
for s in readable:
... read from the socket s and process input ...
lastread[s] = now
closed = []
for s in lastread:
if s not in readable and now - lastread[s] > 300:
... close connection ...
closed.append(s)
for s in closed: del lastread[s]
Notes:
The timeout passed to select (60 in this case) doesn't have much to do with the timeout for a connection. It just says that you want control handed back to you after at most 60 seconds.
Be sure to initialize lastread[s] when you create the socket s and delete the key when you close the connection.
Some more resources:
A tutorial on using select with a timeout (link)
An article which discusses the dropped TCP connection problem and some other solutions: (link)

Related

How do I gracefully close a socket with a persistent HTTP connection?

I'm writing a very simple client in Python that fetches an HTML page from the WWW. This is the code I've come up with so far:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("www.mywebsite.com", 80))
sock.send(b"GET / HTTP/1.1\r\nHost:www.mywebsite.com\r\n\r\n")
while True:
chunk = sock.recv(1024) # (1)
if len(chunk) == 0:
break
print(chunk)
sock.close()
The problem is: being an HTTP/1.1 connection persistent by default, the code gets stuck in # (1) waiting for more data from the server once the transmission is over.
I know I can solve this by a) adding the Connection: close request header, or by b) setting a timeout to the socket. A non-blocking socket here would not help, as the select() syscall would still hang (unless I set a timeout on it, but that's just another form of case b)).
So is there another way to do it, while keeping the connection persistent?
As has already been said in the comments, there's a lot to consider if you're trying to write an all-singing, all-dancing HTTP processor. However, if you're just practising with sockets then consider this.
Let's assume that you know how the response will end. For example, if we do essentially what you're doing in your code to the main Google page, we know that the response will end with '\r\n\r\n'. So, what we can do is just read 1 byte at a time and look out for that terminating sequence.
This code will NOT give you the full Google main page because, as you will see, the response is chunked - and that's a whole new ball game.
Having said all of that, you may find this instructive:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect(('www.google.com', 80))
sock.send(b'GET / HTTP/1.1\r\nHost:www.google.com\r\n\r\n')
end = [b'\r', b'\n', b'\r', b'\n']
d = []
while d[-len(end):] != end:
d.append(sock.recv(1))
print(''.join(b.decode() for b in d))
finally:
sock.close()

Problems with sockets

I'm trying to set up a small server where when the client logs in gets some messages.
The server code
import socket
#Networking
s = socket.socket()
print("Network successfully created")
port = 3642
s.bind(('',port))
print("Network has been binded to %s" %(port))
s.listen(5)
print("Waiting for connections")
while True:
c, addr = s.accept()
print("Got a connection from",addr)
c.send(bytes("Thank you for connecting to me. Currently we","utf-8"))
c.send(bytes("Working on the server","utf-8"))
c.close()
This is the client code
# Import socket module
import socket
# Create a socket object
s = socket.socket()
# Define the port on which you want to connect
port = 3642
# connect to the server on local computer
s.connect(('MyIp..', port))
# receive data from the server
print(s.recv(1024))
# close the connection
s.close()
Everything works fine such as the connecting and the first message gets printed, however I can't get the second message to get printed. The one that says working on the server. I have just began learning about sockets and barely know anything about them so the solution probably is obvious it's just
I can't seem to figure it out. Thank you for any responses. (I would appreciate thorough responses)
If the two sent buffers happen to not get consolidated into a single buffer in the recv (which can happen based on timing, which OS you're running and other factors), then it makes sense that you would not see the second buffer because you're only making one recv call. If you want to receive everything the server sent, put the recv in a loop until it returns an empty string. (Empty string indicates end-of-file [i.e. socket closed by the other end].) – Gil Hamilton

Will select.poll detect read event (select.POLLIN) if the socket is closed

I am not able to detect socket client closing in a particular network. I am running a socket server and once a client connects I am saving the client socket and periodically sending a request to the client . I am using select.poll then to check if there is any data to be read from the socket, and if there is , will read from the socket. All this is fine as of now.
Question is , if the remote socket client is terminated, will select.poll signal a read event in the client socket. If this happens then I can check the data length returned in socket.recv to detect the client has disconnected - as is described here
Adding a code snippet for select
def _wait_for_socket_poller(self, read, write, message=None):
"""
Instead of blockign wait, this polls and check if the read or write socket is ready. If so it proceeds with
reading or writing to the socket. The advantage is that while the poll blocks, it yeilds back to the other
waiting greenlets; poll blocks because we have not given a timeout
:param read: The read function
:param write: The write function
:param message: The CrowdBox API call
:return: The result : In case of read - In JSON format; But catch is that the caller cannot wait on the
result being available,as else the thread will block
"""
if not self.client_socket:
logging.error("CB ID =%d - Connection closed", self.id)
return
poller = select.poll()
# Commonly used flag setes
READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
WRITE_ONLY = select.POLLOUT
READ_WRITE = READ_ONLY | select.POLLOUT
if read and write:
poller.register(self.client_socket, READ_WRITE)
elif write:
poller.register(self.client_socket, WRITE_ONLY)
elif read:
poller.register(self.client_socket, READ_ONLY)
# Map file descriptors to socket objects
fd_to_socket = {self.client_socket.fileno(): self.client_socket, }
result = ''
retry = True
while retry:
# Poll will Block!!
events = poller.poll(
1) # using poll instead of select as the latter runs out of file descriptors on load
# Note here, Poll needs to timeout or will block ,as there is no gevent patched poll, the moment it blocks
# neither greenlets or Twisted Deffered can help -Everything freezes,as all of this is in main thread
if not events:
retry = True
gevent.sleep(0) # This is needed to yeild in case no input comes from CB
else:
retry = False
clientsock = None
fd = None
flag = None
for fd, flag in events:
# Retrieve the actual socket from its file descriptor to map return of poll to socket
clientsock = fd_to_socket[fd]
if clientsock is None:
logging.error("Problem Houston")
raise ValueError("Client Sokcet has Become Invalid")
if flag & select.POLLHUP:
logging.error("Client Socket Closed")
self.client_socket.close()
self.client_socket = None
return None
if flag & (select.POLLIN | select.POLLPRI):
if read:
result = read()
if flag & select.POLLOUT:
if write:
result = write(message)
# poller.uregister(self.client_socket)
return result
In general, yes, a socket will be marked as "readable" when a TCP connection is closed. But this assumes that there was a normal closing, meaning a TCP FIN or RST packet.
Sometimes TCP connections don't end that way. In particular, if TCP Keep-Alive is not enabled (and by default it is not), a network outage between server and client could effectively terminate the connection without either side knowing until they try to send data.
So if you want to make sure you are promptly notified when a TCP connection is broken, you need to send keep-alive messages at either the TCP layer or the application layer.
Keep-alive messages have the additional benefit that they can prevent unused connections from being automatically dropped by various network appliances due to long periods of inactivity.
For more on keep-alive, see here: http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
Thought of adding an anwer here so that I can post some tcp dump trace. We tested this in a live network. The Socket client process in the remote machine terminated and python socket.send ( on a non blocking socket) client_socket.setblocking(0), did not return any error, for subsequent request send to the client from the server There was no event generated to indicate (EPOLLIN) something to read either.
So to detect the client connection loss, we ping the client periodically and if there is no expected response after three retrials , disconnect the client. Basically handled this in the application layer. Clients also changed to reply with some data for our 'are you alive' requests instead of just ignoring it.
sent = 0
try:
sent = self.client_socket.send(out)
except socket.error as e:
if e.args[0] == errno.EPIPE:
logging.error("Socket connection is closed or broken")
if sent == 0 and self.client_socket is not None:
logging.error("socket connection is already closed by client, cannot write request")
self.close_socket_connection()
else
# send succcessfully
Below is the tcpdump wireshark trace where you can see the re-transmit happening. IP details masked for security

How to limit the number of connections to a socket and trigger timeout on client (Python)

How can I set a limit on the number of connections that a server socket can accept at once? I want to be able to set a max number of connections, and then once that limit is reached, any further attempts from clients to connect will result in a timeout. So far, I have tried something like this for the server:
sock = socket.socket()
sock.setblocking(0)
sock.bind(address)
sock.listen(0)
connections = []
while True:
readable, writable, exceptional = select.select([sock], [], [])
if readable and len(connections) < MAX_CONNECTIONS:
connection, client_address = s.accept()
connections.append(connection)
# Process connection asynchronously
and for the client:
try:
sock = socket.create_connection(self.address, timeout=TIMEOUT)
sock.settimeout(None)
print "Established connection."
except socket.error as err:
print >> sys.stderr, "Socket connection error: " + str(err)
sys.exit(1)
# If connection successful, do stuff
Because of the structure of the rest of the program, I have chosen to use a non-blocking socket on the server and I do not wish to change this. Right now, the clients are able to connect to the server, even after the limit is reached and the server stops accepting them. How do I solve this? Thanks.
I believe there might be a slight misunderstanding of select() at play here. According to the manpage, select() returns file descriptors that are "ready for some class of IO operation", where ready means "it is possible to perform a corresponding IO operation without blocking".
The corresponding IO operation on a listening socket is accept(), which can only be performed without blocking if the OS already made the full TCP handshake for you, otherwise you might block waiting for the client's final ACK, for instance.
This means that as long as the listening socket is open, connections will be accepted by the OS, even if not being handled by the application.
If you want to reject connections after a set number, you have basically two options:
simply accept and close directly after accepting.
close the listening socket upon reaching the limit and reopen when done.
The second option is more convoluted and requires use of the SO_REUSEADDR option, which might not be the right thing in your case. It might also not work on all OSs, though it does seem to work reliably on Linux.
Here's a quick sketch of the second solution (since the first is pretty straightforward).
def get_listening_socket():
sock = socket.socket()
sock.setblocking(0)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 5555))
sock.listen(0)
return sock
sock = get_listening_socket()
LIMIT = 1
conns = {sock}
while True:
readable, writable, exceptional = select.select(conns, [], [])
if sock in readable: # new connection on the listening socket
conn, caddr = sock.accept()
conns.add(conn)
if len(conns) > LIMIT: # ">" because "sock" is also in there
conns.remove(sock)
sock.close()
else: # reading from an established connection
for c in readable:
buf = c.recv(4096)
if not buf:
conns.remove(c)
sock = get_listening_socket()
conns.add(sock)
else:
print("received: %s" % buf)
You may, however, want to rethink why you'd want to do this in the first place. If it's only about saving some memory on the server, than you might be over-optimizing and should be looking into syn-cookies instead.

python sockets: make sure that TCP packets are send before closing connection

I am working with a relay that is controlled via TCP. As far as I understood the following code is supposed to work:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.0.200', 17494))
s.send(chr(101))
s.close()
However, I noticed that the socket gets closed before the package is actually send, and the relay does not do anything. As dirty solution I now put a sleep statement before closing the connection and it works properly.
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('192.168.0.200', 17494))
s.send(chr(101))
time.sleep(0.01)
s.close()
Is there something more clever one can do to ensure that the package got actually send before closing the connection?
You could set the SO_LINGER option using s.setsockopt. The linger option makes the socket wait (internally) and close only after sending all the pending data upto the specified timeout value. Something like:
linger_enabled = 1
linger_time = 10 #This is in seconds.
linger_struct = struct.pack('ii', linger_enabled, linger_time)
s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, linger_struct)

Categories

Resources