122 lines
3.6 KiB
Python
122 lines
3.6 KiB
Python
# Copyright (c) Microsoft Corporation. All rights reserved.
|
|
# Licensed under the MIT License. See LICENSE in the project root
|
|
# for license information.
|
|
|
|
import socket
|
|
import sys
|
|
import threading
|
|
|
|
from debugpy.common import log
|
|
from debugpy.common.util import hide_thread_from_debugger
|
|
|
|
|
|
def create_server(host, port=0, backlog=socket.SOMAXCONN, timeout=None):
|
|
"""Return a local server socket listening on the given port."""
|
|
|
|
assert backlog > 0
|
|
if host is None:
|
|
host = "127.0.0.1"
|
|
if port is None:
|
|
port = 0
|
|
|
|
try:
|
|
server = _new_sock()
|
|
server.bind((host, port))
|
|
if timeout is not None:
|
|
server.settimeout(timeout)
|
|
server.listen(backlog)
|
|
except Exception:
|
|
server.close()
|
|
raise
|
|
return server
|
|
|
|
|
|
def create_client():
|
|
"""Return a client socket that may be connected to a remote address."""
|
|
return _new_sock()
|
|
|
|
|
|
def _new_sock():
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP)
|
|
if sys.platform == "win32":
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)
|
|
else:
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
|
|
# Set TCP keepalive on an open socket.
|
|
# It activates after 1 second (TCP_KEEPIDLE,) of idleness,
|
|
# then sends a keepalive ping once every 3 seconds (TCP_KEEPINTVL),
|
|
# and closes the connection after 5 failed ping (TCP_KEEPCNT), or 15 seconds
|
|
try:
|
|
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
|
|
except (AttributeError, OSError):
|
|
pass # May not be available everywhere.
|
|
try:
|
|
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
|
|
except (AttributeError, OSError):
|
|
pass # May not be available everywhere.
|
|
try:
|
|
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)
|
|
except (AttributeError, OSError):
|
|
pass # May not be available everywhere.
|
|
try:
|
|
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)
|
|
except (AttributeError, OSError):
|
|
pass # May not be available everywhere.
|
|
return sock
|
|
|
|
|
|
def shut_down(sock, how=socket.SHUT_RDWR):
|
|
"""Shut down the given socket."""
|
|
sock.shutdown(how)
|
|
|
|
|
|
def close_socket(sock):
|
|
"""Shutdown and close the socket."""
|
|
try:
|
|
shut_down(sock)
|
|
except Exception:
|
|
pass
|
|
sock.close()
|
|
|
|
|
|
def serve(name, handler, host, port=0, backlog=socket.SOMAXCONN, timeout=None):
|
|
"""Accepts TCP connections on the specified host and port, and invokes the
|
|
provided handler function for every new connection.
|
|
|
|
Returns the created server socket.
|
|
"""
|
|
|
|
assert backlog > 0
|
|
|
|
try:
|
|
listener = create_server(host, port, backlog, timeout)
|
|
except Exception:
|
|
log.reraise_exception(
|
|
"Error listening for incoming {0} connections on {1}:{2}:", name, host, port
|
|
)
|
|
host, port = listener.getsockname()
|
|
log.info("Listening for incoming {0} connections on {1}:{2}...", name, host, port)
|
|
|
|
def accept_worker():
|
|
while True:
|
|
try:
|
|
sock, (other_host, other_port) = listener.accept()
|
|
except (OSError, socket.error):
|
|
# Listener socket has been closed.
|
|
break
|
|
|
|
log.info(
|
|
"Accepted incoming {0} connection from {1}:{2}.",
|
|
name,
|
|
other_host,
|
|
other_port,
|
|
)
|
|
handler(sock)
|
|
|
|
thread = threading.Thread(target=accept_worker)
|
|
thread.daemon = True
|
|
hide_thread_from_debugger(thread)
|
|
thread.start()
|
|
|
|
return listener
|