55import logging
66import pathlib
77import re
8+ import socket
89from functools import partial
910
1011from . import errors , pathio
@@ -109,7 +110,8 @@ def __init__(self, *, socket_timeout=None,
109110 read_speed_limit = None , write_speed_limit = None ,
110111 path_timeout = None , path_io_factory = pathio .PathIO ,
111112 encoding = "utf-8" , ssl = None , parse_list_line_custom = None ,
112- passive_commands = ("epsv" , "pasv" ), ** siosocks_asyncio_kwargs ):
113+ passive_commands = ("epsv" , "pasv" ), tcp_keepalive = None ,
114+ ** siosocks_asyncio_kwargs ):
113115 self .socket_timeout = socket_timeout
114116 self .throttle = StreamThrottle .from_limits (
115117 read_speed_limit ,
@@ -124,11 +126,36 @@ def __init__(self, *, socket_timeout=None,
124126 self ._passive_commands = passive_commands
125127 self ._open_connection = partial (open_connection , ssl = self .ssl ,
126128 ** siosocks_asyncio_kwargs )
129+ assert tcp_keepalive is None or isinstance (tcp_keepalive , (list , tuple )) and len (tcp_keepalive ) == 3 and all (isinstance (x , int ) and x > 0 for x in tcp_keepalive )
130+ self .tcp_keepalive = tcp_keepalive
131+ self ._sock = None
132+
133+ def set_tcp_keepalive (self ):
134+ """
135+ Enable TCP keepalive if asked to do so
136+ """
137+
138+ if self .tcp_keepalive is None :
139+ return
140+ if self ._sock is None :
141+ return
142+
143+ self ._sock .setsockopt (socket .SOL_SOCKET , socket .SO_KEEPALIVE , 1 )
144+ if hasattr (socket , "TCP_KEEPIDLE" ):
145+ self ._sock .setsockopt (socket .IPPROTO_TCP , socket .TCP_KEEPIDLE , self .tcp_keepalive [0 ])
146+ if hasattr (socket , "TCP_KEEPINTVL" ):
147+ self ._sock .setsockopt (socket .IPPROTO_TCP , socket .TCP_KEEPINTVL , self .tcp_keepalive [1 ])
148+ if hasattr (socket , "TCP_KEEPCNT" ):
149+ self ._sock .setsockopt (socket .IPPROTO_TCP , socket .TCP_KEEPCNT , self .tcp_keepalive [2 ])
127150
128151 async def connect (self , host , port = DEFAULT_PORT ):
129152 self .server_host = host
130153 self .server_port = port
131154 reader , writer = await self ._open_connection (host , port )
155+
156+ self ._sock = writer .get_extra_info ("socket" )
157+ self .set_tcp_keepalive ()
158+
132159 self .stream = ThrottleStreamIO (
133160 reader ,
134161 writer ,
@@ -587,6 +614,12 @@ class Client(BaseClient):
587614 dictionary with fields "modify", "type", "size". For more
588615 information see sources.
589616 :type parse_list_line_custom: callable
617+
618+ :param tcp_keepalive: optional three elements tuple containing integers
619+ to set TCP keepalive on socket (TCP_KEEPIDLE, TCP_KEEPINTVL,
620+ TCP_KEEPCNT) or None to disable this feature
621+ :type tcp_keepalive: :py:class:`list`, :py:class:`tuple` or `None`
622+
590623 :param **siosocks_asyncio_kwargs: siosocks key-word only arguments
591624 """
592625 async def connect (self , host , port = DEFAULT_PORT ):
0 commit comments