The example below creates a TCP server listening on a stream (i.e. SOCK_STREAM
) socket. A similar approach can be followed to
create a UDP server on a datagram (i.e. SOCK_DGRAM
) socket. See man systemd.socket
for details.
Create an simple echo server at ~/tmp/foo/serve.py
.
#!/usr/bin/env python3
from socketserver import TCPServer, StreamRequestHandler
import socket
import logging
class Handler(StreamRequestHandler):
def handle(self):
self.data = self.rfile.readline().strip()
logging.info("From <%s>: %s" % (self.client_address, self.data))
self.wfile.write(self.data.upper() + "\r\n".encode("utf-8"))
class Server(TCPServer):
# The constant would be better initialized by a systemd module
SYSTEMD_FIRST_SOCKET_FD = 3
def __init__(self, server_address, handler_cls):
# Invoke base but omit bind/listen steps (performed by systemd activation!)
TCPServer.__init__(
self, server_address, handler_cls, bind_and_activate=False)
# Override socket
self.socket = socket.fromfd(
self.SYSTEMD_FIRST_SOCKET_FD, self.address_family, self.socket_type)
if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
HOST, PORT = "localhost", 9999 # not really needed here
server = Server((HOST, PORT), Handler)
server.serve_forever()
Create the service definition unit file at ~/.config/systemd/user/foo.service
(note that this unit refers to foo.socket
):
[Unit]
Description=Foo Service
After=network.target foo.socket
Requires=foo.socket
[Service]
Type=simple
ExecStart=/usr/bin/python3 %h/tmp/foo/serve.py
TimeoutStopSec=5
[Install]
WantedBy=default.target
Create the socket definition unit file at ~/.config/systemd/user/foo.socket
:
[Unit]
Description=Foo Socket
PartOf=foo.service
[Socket]
ListenStream=127.0.0.1:9999
[Install]
WantedBy=sockets.target
systemctl --user daemon-reload
systemctl --user start foo.socket
Send a message to the listening service:
date | netcat 127.0.0.1 9999
Watch the log for the echo:
journalctl -f --user-unit foo.service
Forked by @kylemanna:
- Convert docs to use the systemd user daemon
- Update to work with python3
- More logs
how does the HOST and PORT are populated as you have added a comment not really needed in the
if __name__ == "__main__":
block ?