En la entrada http://codigo-python.blogspot.com.es/2017/02/socket-server-tcp-multi-thread-ii.html construíamos un socket server basado en hilos con control de número hilos en ejecución y de forma que lo que que recibíamos desde los diferentes clientes era almacenado a un fichero de log, el cual además rotaba de forma periódica.
A continuación vamos a ver como ejecutar la aplicación para que se comporte como un servicio en sistemas unix, en concreto en debian y para que corra con un usuario del sistema con los permisos adecuados.
Vamos a suponer que el código de nuestro socket server lo tenemos en el fichero socket-server.py dentro del directorio /opt/socket-server/ y que queremos ejecutar el código como usuario socket-user, el cual pertenece al grupo socket-group.
Tendremos algo como lo siguiente:
Los logs de la aplicación vamos a guardarlos en el directorio /var/log/socket-server/, con lo cual nos aseguraremos que el directorio tiene los permisos adecuados:
Con estas premisas vamos a montar un script de bash que llamaremos socket-server que hará las veces de manejador del servicio y que guardaremos en el directorio /etc/init.d/. El contenido del fichero es el siguiente:
Un par de detalles a tener en cuenta: el script define un fichero de logs DAEMONLOG=/var/log/socket-server/daemon.log, y os preguntaréis para qué? si el propio socket server ya escribe su fichero de logs. En este caso en el fichero daemon.log va a almacenar los logs del propio script de bash y además las excepciones no capturadas del fichero socket-server.py
También podéis observar que definimos un fichero PIDFILE donde se va a almacenar el PID del proceso. Si echamos la vista atrás sobre el código socket-server.py vemos que no se maneja en ningún sitio el PID del proceso. Debemos por tanto incluirlo. Para ello simplemente añadimos un trocito de código a nuestro fichero socket-server.py, de forma que tendremos:
Con todo lo anterior ya estamos en disposición de manejar nuestro socket-server como un servicio:
A continuación vamos a ver como ejecutar la aplicación para que se comporte como un servicio en sistemas unix, en concreto en debian y para que corra con un usuario del sistema con los permisos adecuados.
Vamos a suponer que el código de nuestro socket server lo tenemos en el fichero socket-server.py dentro del directorio /opt/socket-server/ y que queremos ejecutar el código como usuario socket-user, el cual pertenece al grupo socket-group.
Tendremos algo como lo siguiente:
root@debian:/opt# pwd
/opt
root@debian:/opt# ls -l
total 4
drwxr-xr-x 2 socket-user socket-group 4096 mar 15 19:15 socket-server
root@debian:/opt/socket-server# ls -l
total 4
-rwxr-xr-x 1 socket-user socket-group 3231 mar 15 19:38 socket-server.py
Los logs de la aplicación vamos a guardarlos en el directorio /var/log/socket-server/, con lo cual nos aseguraremos que el directorio tiene los permisos adecuados:
root@debian:/var/log# pwd
/var/log
root@debian:/var/log# ls -l | grep socket-server
drwxr-xr-x 2 socket-user socket-group 4096 mar 15 19:25 socket-server
Con estas premisas vamos a montar un script de bash que llamaremos socket-server que hará las veces de manejador del servicio y que guardaremos en el directorio /etc/init.d/. El contenido del fichero es el siguiente:
#!/bin/bash
# /etc/init.d/scripts
# Description: Script for manage socket-server
# ————————————————–
#
### BEGIN INIT INFO
# Provides: Scripts for socket-server
# Required-Start: $network $local_fs $syslog
# Required-Stop: $local_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Description: Start Python scripts to provide socket-server service
### END INIT INFO
PIDFILE=/tmp/socket-server
DAEMONLOG=/var/log/socket-server/daemon.log
case "$1" in
start)
if [ ! -f $PIDFILE ] ; then
echo "Starting socket-server..."
su socket-user -c "nohup /usr/bin/python -u /opt/socket-server/socket-server.py > $DAEMONLOG 2>&1 &"
else
for pid in $(cat $PIDFILE) ; do
if ! ps --no-headers p "$pid" | grep socket-server > /dev/null ; then
echo "Starting socket-server..."
su socket-user -c "nohup /usr/bin/python -u /opt/socket-server/socket-server.py > $DAEMONLOG 2>&1 &"
else
echo "The socket-server is already running!!"
fi
done
fi
;;
stop)
if [ ! -f $PIDFILE ] ; then
echo "The socket-server is not running"
else
for pid in $(cat $PIDFILE) ; do
if ! ps --no-headers p "$pid" | grep socket-server > /dev/null ; then
echo "The socket-server is not running"
else
echo "Stopping socket-server..."
kill -9 $pid
fi
done
fi
;;
restart)
$0 stop
sleep 1
$0 start
;;
*)
echo "usage: $0 {start|stop|restart}"
esac
Un par de detalles a tener en cuenta: el script define un fichero de logs DAEMONLOG=/var/log/socket-server/daemon.log, y os preguntaréis para qué? si el propio socket server ya escribe su fichero de logs. En este caso en el fichero daemon.log va a almacenar los logs del propio script de bash y además las excepciones no capturadas del fichero socket-server.py
También podéis observar que definimos un fichero PIDFILE donde se va a almacenar el PID del proceso. Si echamos la vista atrás sobre el código socket-server.py vemos que no se maneja en ningún sitio el PID del proceso. Debemos por tanto incluirlo. Para ello simplemente añadimos un trocito de código a nuestro fichero socket-server.py, de forma que tendremos:
#!/usr/bin/env python
import threading
import SocketServer, socket
import sys
import os
import logging, logging.handlers
TIMEOUT = 10
HOST = '0.0.0.0'
PORT = 3456
MAX_THREADS = 50
LOG_FOLDER = '/var/log/socket-server/'
LOG_FILE = 'socket-server.log'
ROTATE_TIME = 'midnight'
LOG_COUNT = 10
PID = "/tmp/socket-server"
if os.access(os.path.expanduser(PID), os.F_OK):
logger.info('Checking if socket-server is already running...')
pidfile = open(os.path.expanduser(PID), "r")
pidfile.seek(0)
old_pd = pidfile.readline()
# process PID
if os.path.exists("/proc/%s" % old_pd) and old_pd!="":
logger.info('You already have an instance of the socket-server running')
logger.info('It is running as process %s' , old_pd)
sys.exit(1)
else:
logger.info('socket-server is not running. Trying to start socket-server...')
os.remove(os.path.expanduser(PID))
pidfile = open(os.path.expanduser(PID), 'a')
logger.info('socket-server started with PID: %s' , os.getpid())
pidfile.write(str(os.getpid()))
pidfile.close()
log_folder = os.path.dirname(LOG_FOLDER)
if not os.path.exists(log_folder):
try:
os.makedirs(log_folder)
except Exception as error:
print 'Error creating the log folder: %s' % error
exit()
try:
logger = logging.getLogger('socket-server')
loggerHandler = logging.handlers.TimedRotatingFileHandler(LOG_FOLDER + LOG_FILE , ROTATE_TIME, 1, backupCount=LOG_COUNT)
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
loggerHandler.setFormatter(formatter)
logger.addHandler(loggerHandler)
logger.setLevel(logging.DEBUG)
except Exception as error:
print '------------------------------------------------------------------'
print '[ERROR] Error writing log at %s: %s' % (LOG_FOLDER, error)
print '[ERROR] Please verify path folder exits and write permissions'
print '------------------------------------------------------------------'
exit()
class RequestHandler(SocketServer.BaseRequestHandler):
def handle(self):
try:
# chequeamos el numero de threads activos. Si es mayor que el limite establecido cerramos la conexion y no atendemos al cliente. Lo trazamos
if threading.activeCount() > MAX_THREADS:
logger.warn('%s -- Execution threads number: %d', threading.currentThread().getName(),
threading.activeCount() - 1)
logger.warn('Max threads number as been reached.')
self.closed()
# si no hemos alcanzado el limite lo atendemos
else:
threadName = threading.currentThread().getName()
activeThreads = threading.activeCount() - 1
clientIP = self.client_address[0]
logger.info('[%s] -- New connection from %s -- Active threads: %d' , threadName, clientIP, activeThreads)
data = self.request.recv(1024)
logger.info('[%s] -- %s -- Received: %s' , threadName, clientIP, data)
response = 'Thanks %s, message received!!' % clientIP
self.request.send(response)
except Exception, error:
if str(error) == "timed out":
logger.error ('[%s] -- %s -- Timeout on data transmission ocurred after %d seconds.' ,threadName, clientIP, TIMEOUT)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def server_bind(self):
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind(self.server_address)
def finish_request(self, request, client_address):
request.settimeout(TIMEOUT)
SocketServer.TCPServer.finish_request(self, request, client_address)
SocketServer.TCPServer.close_request(self, request)
try:
print "Starting server TCP at IP %s and port %d..." % (HOST,PORT)
server = ThreadedTCPServer((HOST, PORT), RequestHandler)
server.serve_forever()
except KeyboardInterrupt:
server.socket.close()
Con todo lo anterior ya estamos en disposición de manejar nuestro socket-server como un servicio:
root@debian:~# /etc/init.d/socket-server
usage: /etc/init.d/socket-server {start|stop|restart}
Probamos a arrancarlo:
root@debian:~# /etc/init.d/socket-server start
Starting socket-server...
Si todo ha ido bien el proceso debería estar corriendo con el usuario que hemos definido. Si hubiera algún error podemos comprobar el fichero /var/log/socket-server/daemon.log
Para detenerlo:
root@debian:~# /etc/init.d/socket-server stop
Stoping socket-server...
Si queremos que el proceso se arranque automáticamente al arrancar el sistema, en el caso de debian ejecutamos los siguientes comandos:
root@debian:~# cd /etc/init.d/
root@debian:/etc/init.d# update-rc.d socket-server defaults
El script que maneja el servicio se puede adaptar fácilmente a vuestro propio código python si queréis convertirlo en servicio. Animaos y probadlo!!
lo que te gustan los debian....
ResponderEliminar