Handling socket error and Keepalive

Handling TCP socket error and TCP_KEEPALIVE

The 'Net' abounds with simple 20 line examples of TCP client behavior; however they all assume the TCP server exists and that crashing due to exceptions is helpful education for you. trying to locate realistic examples often pushes you into complex examples using hundreds of lines of code to do real work.

So here is a simple-dummy example TCP client application which runs on either a PC or Digi product:

To use this example, set the IP address to any Digi TS/DS with TCP Sockets active. Then by powering up or down the Digi TS/DS you can cause the connect(('x.x.x.x',2101) to succeed or fail. Sending simple ASCII data into the serial port of the Digi TS/DS would enable you to force or skip the "socket.timeout" try-except clause.

TCP Keepalive

So how does your Python code understand if no data means the TCP peer is being quiet - or if the TCP socket has gone away? The normal answer is that eventually a socket error will cause the "socket.error" clause to execute. However, that could easily be hours (or forever) after the socket fails.

By default sockets in your Digi Python application (as well as Windows or Linux) open with TCP keepalives turned off, and thus it is possible your application will languish for a very long time with a dead socket open. This simple line of Python code will turn TCP Keepalives on:

x = sock.setsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

By default your Digi Python product will have a default TCP idle time of two hours, so even if you turn on TCP Keepalives don't expect your program to recover in minutes. Different TCP Keepalive settings can be entered in the Digi Web interface at Configuration > Network > Advanced Network Settings > TCP Keep-Alive Settings, or by telnet with the set net and show net commands. If you truly wish to force TCP Keepalive settings to always contain a smaller setting, you could use Module:digicli to force specific settings. This example sets roughly a 5 minute detection of a failed TCP socket.

status,results = digicli.digicli( "set net idle=240 probe_count=5 probe_interval=10" )

Do not try to use TCP Keepalive to detect TCP socket failure more quickly than a few minutes.

People who try to set it for 5 seconds (or for milliseconds) invariably cause serious compatibility issues with other products - and invariably fail to be satisfied. If you truly require detecting a TCP socket failure in 1 second or less, which implies your TCP peers normally send data many times per second, then use non-blocking sockets with the "socket.timeout" exception to detect when no data had been received in your required time-frame. And if you accept that a TCP peer quiet for 1 second is bad, then close the socket manually and attempt recovery directly. Do not use TCP Keepalive for such short-period detection.

Example code

(Note that this code uses " import socket " and thus all methods and all constants such as SOCK_STREAM must include the " socket. " preface. Many other example applications use " from socket import * ", which eliminates this needs for " socket. " preface. Either solution works - just be mindful of this detail if you mix-and-match sample code from diverse sources.)

import sys
import socket
import traceback
import time

def do_work( forever = True):

    while True:

        # start with a socket at 5-second timeout
        print "Creating the socket"
        sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout( 5.0)

        # check and turn on TCP Keepalive
        x = sock.getsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE)
        if( x == 0):
            print 'Socket Keepalive off, turning on'
            x = sock.setsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
            print 'setsockopt=', x
        else:
            print 'Socket Keepalive already on'

        try:
            sock.connect(('192.168.196.8',2101))

        except socket.error:
            print 'Socket connect failed! Loop up and try socket again'
            traceback.print_exc()
            time.sleep( 5.0)
            continue

        print 'Socket connect worked!'

        while 1:
            try:
                req = sock.recv(6)

            except socket.timeout:
                print 'Socket timeout, loop and try recv() again'
                time.sleep( 5.0)
                # traceback.print_exc()
                continue

            except:
                traceback.print_exc()
                print 'Other Socket err, exit and try creating socket again'
                # break from loop
                break

            print 'received', req

        try:
            sock.close()
        except:
            pass

        # loop back up & restart

if __name__ == '__main__':

    do_work( True)