From 846c54b439acdde4629aaf8470c2da3e133333ba Mon Sep 17 00:00:00 2001 From: juanjo Date: Fri, 27 Oct 2023 11:32:40 +0200 Subject: [PATCH] =?UTF-8?q?C=C3=B3digo=20fuente=20Python=20PSP?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 01.Procesos/P1-11-EjecucionAsincrono.py | 26 +++ 01.Procesos/P1-12-ObtenrerLIstadoDir.py | 10 + 01.Procesos/P1-13-ComunicacionProceso.py | 15 ++ 01.Procesos/P1-14-ComunicacionPortapapeles.py | 14 ++ 01.Procesos/P1.01-Fork.py | 21 ++ 01.Procesos/P1.02-likefork-win.py | 18 ++ 01.Procesos/P1.03-Lanza-notepad.py | 7 + 01.Procesos/P1.04-proceso-parametros.py | 8 + 01.Procesos/P1.05-proceso-entorno.py | 14 ++ 01.Procesos/P1.06-proceso-referencia.py | 17 ++ 01.Procesos/P1.07-Listado-procesos-sistema.py | 12 ++ ...P1.08-Listado-procesos-sistema-win-wmic.py | 10 + .../P1.09-Acceso-propiedades-proceso.py | 27 +++ 01.Procesos/P1.10-Eliminacion-procesos.py | 13 ++ 01.Procesos/algo.txt | 1 + 02.Threads/P2.01-HelloThread.py | 9 + 02.Threads/P2.02-MultiplesHIlos.py | 13 ++ 02.Threads/P2.03-Alternancia.py | 13 ++ 02.Threads/P2.04-AccesoDatosComunes.py | 17 ++ .../P2.04b-AccesoDatosComunesMuchosHilos.py | 22 +++ 02.Threads/P2.05-PasarDatos.py | 14 ++ 02.Threads/P2.06-PasarDatosDict.py | 15 ++ 02.Threads/P2.07-LocalSotrage.py | 24 +++ 02.Threads/P2.08-Propiedades.py | 16 ++ 02.Threads/P2.09-ListarProcesos.py | 13 ++ 02.Threads/P2.10-daemon-falso.py | 13 ++ 02.Threads/P2.11-daemon-true.py | 14 ++ 02.Threads/P2.12-FinPostergado.py | 10 + 02.Threads/P2.13-ThreadDentroClase.py | 23 +++ 02.Threads/P2.14-CambiarDatosEnEjecucion.py | 24 +++ 02.Threads/P2.15-Join.py | 25 +++ 02.Threads/P2.16-Acceso_concurrente.py | 25 +++ 02.Threads/P2.16a-Acceso_concurrente_Lock.py | 30 +++ 02.Threads/P2.17-Lock_como_parametro.py | 27 +++ 02.Threads/P2.18-LockCuentas.py | 55 ++++++ 02.Threads/P2.19-Lock-consucutivos.py | 14 ++ 02.Threads/P2.19a-RLock.py | 15 ++ 02.Threads/P2.20-Condicionales.py | 25 +++ 02.Threads/P2.21-Semaforos.py | 35 ++++ 02.Threads/P2.22-Eventos.py | 20 ++ 02.Threads/P2.23-Timer.py | 17 ++ 02.Threads/P2.24-Barreras.py | 14 ++ 02.Threads/P2.25-Productor-consumidor.py | 50 +++++ 02.Threads/P2.26-Exception.py | 13 ++ 02.Threads/P2.27-Abort.py | 32 ++++ 03.Network-Sockets/P3.01-IPv4Address.py | 17 ++ 03.Network-Sockets/P3.02-IPv4Network.py | 20 ++ 03.Network-Sockets/P3.03-ObtenerIPpropia.py | 6 + 03.Network-Sockets/P3.04-ObtenerTodasIP.py | 8 + 03.Network-Sockets/P3.05-ObtenrIPExterna.py | 9 + 03.Network-Sockets/P3.06-Nslookup.py | 8 + 03.Network-Sockets/P3.07-tcpserver-try.py | 18 ++ 03.Network-Sockets/P3.08-tcpclient-try.py | 14 ++ .../P3.09-tcpserver-datos-with.py | 17 ++ .../P3.09b-tcpserver-datos-with-try.py | 23 +++ .../P3.10-tcpclient-datos-with.py | 14 ++ 03.Network-Sockets/P3.11-udpserver.py | 18 ++ 03.Network-Sockets/P3.11b-udpserver-bucle.py | 19 ++ 03.Network-Sockets/P3.12-udpclient.py | 19 ++ 04.Servicios/P4.01-TcpServer-Forever.py | 32 ++++ 04.Servicios/P4.02-TcpServer-Handle.py | 33 ++++ 04.Servicios/P4.03-ClienteTCP.py | 16 ++ .../P4.03-TcpServer-Forever-shutdown.py | 59 ++++++ 04.Servicios/P4.04-UDPServer.py | 26 +++ 04.Servicios/P4.05-ClienteUDP.py | 15 ++ 04.Servicios/P4.06-TcpServerStream.py | 38 ++++ 04.Servicios/P4.07-TcpServerThreding.py | 29 +++ 04.Servicios/P4.08-TcpServerAsyncio.py | 27 +++ 04.Servicios/P4.09-AdivinaNumero.py | 28 +++ 04.Servicios/P4.10-AdivinaNumeroMulti.py | 36 ++++ 04.Servicios/P4.11-PPTServidorBase.py | 157 +++++++++++++++ 04.Servicios/P4.12-PPTClienteBasico.py | 14 ++ 04.Servicios/P4.13-PPTClienteGUI.py | 50 +++++ .../P4.13a-PPTClienteGUI-asincrono.py | 55 ++++++ 04.Servicios/P4.14-PPTServidorSondeo.py | 160 ++++++++++++++++ 04.Servicios/P4.15-PPTClienteSondeoGUI.py | 59 ++++++ 04.Servicios/P4.16-PPTServidorFullDuplex.py | 180 ++++++++++++++++++ 04.Servicios/P4.17-PPTClienteFullDuplexGUI.py | 80 ++++++++ 04.Servicios/P4.18-Ftp-list.py | 25 +++ 04.Servicios/P4.19-Ftp-Upload.py | 29 +++ 04.Servicios/P4.20-Ftp-Download.py | 25 +++ 04.Servicios/P4.21-email-smtp.py | 32 ++++ 04.Servicios/P4.22-email-imap.py | 36 ++++ 04.Servicios/P4.23-Webbrowser.py | 15 ++ 04.Servicios/P4.24-leerUrl.py | 19 ++ 04.Servicios/P4.25-ApiREST-OpenWeather.py | 19 ++ 04.Servicios/P4.26-ConsumirAPIExterna.py | 16 ++ 04.Servicios/bajado.txt | 1 + 04.Servicios/subido.txt | 1 + 05.Seguridad/Datos_Encriptados.bin | Bin 0 -> 369 bytes 05.Seguridad/P5.01-Hash-MD5.py | 13 ++ 05.Seguridad/P5.02-CifradoCesar.py | 28 +++ 05.Seguridad/P5.03-DES-key-iv.py | 28 +++ 05.Seguridad/P5.04-3DES.py | 32 ++++ 05.Seguridad/P5.05-DES3-file.py | 29 +++ 05.Seguridad/P5.06-AES.py | 21 ++ 05.Seguridad/P5.07-AES-iv.py | 33 ++++ 05.Seguridad/P5.08-Generar-clave-RSA.py | 23 +++ 05.Seguridad/P5.09-Generar-par-claves-RSA.py | 12 ++ 05.Seguridad/P5.10-RSA-encriptar.py | 19 ++ 05.Seguridad/P5.11-RSA-desencriptar.py | 18 ++ 05.Seguridad/P5.12-GenerarPar-DSA.py | 15 ++ 05.Seguridad/P5.13-DSA-firmar.py | 21 ++ 05.Seguridad/P5.14-DSA-verificar.py | 26 +++ 05.Seguridad/P5.15-SocketServer-SSL.py | 24 +++ 05.Seguridad/P5.16-SocketCliente-SSL.py | 20 ++ 05.Seguridad/cert-ssl/certificado.pem | 19 ++ 05.Seguridad/cert-ssl/clave-privada.key | 28 +++ 05.Seguridad/mensajefirmado.txt | 1 + 05.Seguridad/privada_usuario_A.pem | 27 +++ 05.Seguridad/private_key.pem | 15 ++ 05.Seguridad/private_key_firma.pem | 15 ++ 05.Seguridad/public_key.pem | 0 05.Seguridad/public_key_firma.pem | 20 ++ 05.Seguridad/publica_usuario_A.pem | 9 + 05.Seguridad/rsa_key_privada.bin | 30 +++ 05.Seguridad/textoDesencriptado.txt | 4 + 05.Seguridad/textoEncriptado.txt | 1 + 05.Seguridad/textoPlano.txt | 4 + PROTOCOLO PPT V 1.pdf | Bin 0 -> 116485 bytes README.md | 5 + 121 files changed, 2912 insertions(+) create mode 100644 01.Procesos/P1-11-EjecucionAsincrono.py create mode 100644 01.Procesos/P1-12-ObtenrerLIstadoDir.py create mode 100644 01.Procesos/P1-13-ComunicacionProceso.py create mode 100644 01.Procesos/P1-14-ComunicacionPortapapeles.py create mode 100644 01.Procesos/P1.01-Fork.py create mode 100644 01.Procesos/P1.02-likefork-win.py create mode 100644 01.Procesos/P1.03-Lanza-notepad.py create mode 100644 01.Procesos/P1.04-proceso-parametros.py create mode 100644 01.Procesos/P1.05-proceso-entorno.py create mode 100644 01.Procesos/P1.06-proceso-referencia.py create mode 100644 01.Procesos/P1.07-Listado-procesos-sistema.py create mode 100644 01.Procesos/P1.08-Listado-procesos-sistema-win-wmic.py create mode 100644 01.Procesos/P1.09-Acceso-propiedades-proceso.py create mode 100644 01.Procesos/P1.10-Eliminacion-procesos.py create mode 100644 01.Procesos/algo.txt create mode 100644 02.Threads/P2.01-HelloThread.py create mode 100644 02.Threads/P2.02-MultiplesHIlos.py create mode 100644 02.Threads/P2.03-Alternancia.py create mode 100644 02.Threads/P2.04-AccesoDatosComunes.py create mode 100644 02.Threads/P2.04b-AccesoDatosComunesMuchosHilos.py create mode 100644 02.Threads/P2.05-PasarDatos.py create mode 100644 02.Threads/P2.06-PasarDatosDict.py create mode 100644 02.Threads/P2.07-LocalSotrage.py create mode 100644 02.Threads/P2.08-Propiedades.py create mode 100644 02.Threads/P2.09-ListarProcesos.py create mode 100644 02.Threads/P2.10-daemon-falso.py create mode 100644 02.Threads/P2.11-daemon-true.py create mode 100644 02.Threads/P2.12-FinPostergado.py create mode 100644 02.Threads/P2.13-ThreadDentroClase.py create mode 100644 02.Threads/P2.14-CambiarDatosEnEjecucion.py create mode 100644 02.Threads/P2.15-Join.py create mode 100644 02.Threads/P2.16-Acceso_concurrente.py create mode 100644 02.Threads/P2.16a-Acceso_concurrente_Lock.py create mode 100644 02.Threads/P2.17-Lock_como_parametro.py create mode 100644 02.Threads/P2.18-LockCuentas.py create mode 100644 02.Threads/P2.19-Lock-consucutivos.py create mode 100644 02.Threads/P2.19a-RLock.py create mode 100644 02.Threads/P2.20-Condicionales.py create mode 100644 02.Threads/P2.21-Semaforos.py create mode 100644 02.Threads/P2.22-Eventos.py create mode 100644 02.Threads/P2.23-Timer.py create mode 100644 02.Threads/P2.24-Barreras.py create mode 100644 02.Threads/P2.25-Productor-consumidor.py create mode 100644 02.Threads/P2.26-Exception.py create mode 100644 02.Threads/P2.27-Abort.py create mode 100644 03.Network-Sockets/P3.01-IPv4Address.py create mode 100644 03.Network-Sockets/P3.02-IPv4Network.py create mode 100644 03.Network-Sockets/P3.03-ObtenerIPpropia.py create mode 100644 03.Network-Sockets/P3.04-ObtenerTodasIP.py create mode 100644 03.Network-Sockets/P3.05-ObtenrIPExterna.py create mode 100644 03.Network-Sockets/P3.06-Nslookup.py create mode 100644 03.Network-Sockets/P3.07-tcpserver-try.py create mode 100644 03.Network-Sockets/P3.08-tcpclient-try.py create mode 100644 03.Network-Sockets/P3.09-tcpserver-datos-with.py create mode 100644 03.Network-Sockets/P3.09b-tcpserver-datos-with-try.py create mode 100644 03.Network-Sockets/P3.10-tcpclient-datos-with.py create mode 100644 03.Network-Sockets/P3.11-udpserver.py create mode 100644 03.Network-Sockets/P3.11b-udpserver-bucle.py create mode 100644 03.Network-Sockets/P3.12-udpclient.py create mode 100644 04.Servicios/P4.01-TcpServer-Forever.py create mode 100644 04.Servicios/P4.02-TcpServer-Handle.py create mode 100644 04.Servicios/P4.03-ClienteTCP.py create mode 100644 04.Servicios/P4.03-TcpServer-Forever-shutdown.py create mode 100644 04.Servicios/P4.04-UDPServer.py create mode 100644 04.Servicios/P4.05-ClienteUDP.py create mode 100644 04.Servicios/P4.06-TcpServerStream.py create mode 100644 04.Servicios/P4.07-TcpServerThreding.py create mode 100644 04.Servicios/P4.08-TcpServerAsyncio.py create mode 100644 04.Servicios/P4.09-AdivinaNumero.py create mode 100644 04.Servicios/P4.10-AdivinaNumeroMulti.py create mode 100644 04.Servicios/P4.11-PPTServidorBase.py create mode 100644 04.Servicios/P4.12-PPTClienteBasico.py create mode 100644 04.Servicios/P4.13-PPTClienteGUI.py create mode 100644 04.Servicios/P4.13a-PPTClienteGUI-asincrono.py create mode 100644 04.Servicios/P4.14-PPTServidorSondeo.py create mode 100644 04.Servicios/P4.15-PPTClienteSondeoGUI.py create mode 100644 04.Servicios/P4.16-PPTServidorFullDuplex.py create mode 100644 04.Servicios/P4.17-PPTClienteFullDuplexGUI.py create mode 100644 04.Servicios/P4.18-Ftp-list.py create mode 100644 04.Servicios/P4.19-Ftp-Upload.py create mode 100644 04.Servicios/P4.20-Ftp-Download.py create mode 100644 04.Servicios/P4.21-email-smtp.py create mode 100644 04.Servicios/P4.22-email-imap.py create mode 100644 04.Servicios/P4.23-Webbrowser.py create mode 100644 04.Servicios/P4.24-leerUrl.py create mode 100644 04.Servicios/P4.25-ApiREST-OpenWeather.py create mode 100644 04.Servicios/P4.26-ConsumirAPIExterna.py create mode 100644 04.Servicios/bajado.txt create mode 100644 04.Servicios/subido.txt create mode 100644 05.Seguridad/Datos_Encriptados.bin create mode 100644 05.Seguridad/P5.01-Hash-MD5.py create mode 100644 05.Seguridad/P5.02-CifradoCesar.py create mode 100644 05.Seguridad/P5.03-DES-key-iv.py create mode 100644 05.Seguridad/P5.04-3DES.py create mode 100644 05.Seguridad/P5.05-DES3-file.py create mode 100644 05.Seguridad/P5.06-AES.py create mode 100644 05.Seguridad/P5.07-AES-iv.py create mode 100644 05.Seguridad/P5.08-Generar-clave-RSA.py create mode 100644 05.Seguridad/P5.09-Generar-par-claves-RSA.py create mode 100644 05.Seguridad/P5.10-RSA-encriptar.py create mode 100644 05.Seguridad/P5.11-RSA-desencriptar.py create mode 100644 05.Seguridad/P5.12-GenerarPar-DSA.py create mode 100644 05.Seguridad/P5.13-DSA-firmar.py create mode 100644 05.Seguridad/P5.14-DSA-verificar.py create mode 100644 05.Seguridad/P5.15-SocketServer-SSL.py create mode 100644 05.Seguridad/P5.16-SocketCliente-SSL.py create mode 100644 05.Seguridad/cert-ssl/certificado.pem create mode 100644 05.Seguridad/cert-ssl/clave-privada.key create mode 100644 05.Seguridad/mensajefirmado.txt create mode 100644 05.Seguridad/privada_usuario_A.pem create mode 100644 05.Seguridad/private_key.pem create mode 100644 05.Seguridad/private_key_firma.pem create mode 100644 05.Seguridad/public_key.pem create mode 100644 05.Seguridad/public_key_firma.pem create mode 100644 05.Seguridad/publica_usuario_A.pem create mode 100644 05.Seguridad/rsa_key_privada.bin create mode 100644 05.Seguridad/textoDesencriptado.txt create mode 100644 05.Seguridad/textoEncriptado.txt create mode 100644 05.Seguridad/textoPlano.txt create mode 100644 PROTOCOLO PPT V 1.pdf create mode 100644 README.md diff --git a/01.Procesos/P1-11-EjecucionAsincrono.py b/01.Procesos/P1-11-EjecucionAsincrono.py new file mode 100644 index 0000000..2dc9302 --- /dev/null +++ b/01.Procesos/P1-11-EjecucionAsincrono.py @@ -0,0 +1,26 @@ +from os import system +import subprocess +import asyncio + +def showNotepad1(): + try: + subprocess.run(['Notepad.exe',]) + except subprocess.CalledProcessError as e: + print(e.output) +async def showNotepad2(): + try: + await asyncio.create_subprocess_exec('notepad.exe') + except subprocess.CalledProcessError as e: + print(e.output) + +async def main(): + print ("inicio llamada síncrona") + showNotepad1() + print ("fin llamada síncrona") + print ("inicio llamada asíncrona") + await showNotepad2() + print ("fin llamada asíncrona") + print("pulsa una tecla para terminar") + system ('Pause') + +asyncio.run(main()) \ No newline at end of file diff --git a/01.Procesos/P1-12-ObtenrerLIstadoDir.py b/01.Procesos/P1-12-ObtenrerLIstadoDir.py new file mode 100644 index 0000000..98f46b3 --- /dev/null +++ b/01.Procesos/P1-12-ObtenrerLIstadoDir.py @@ -0,0 +1,10 @@ +import subprocess + +p1 = subprocess.Popen('dir', shell=True, stdin=None, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + +while True: + line = p1.stdout.readline() + if not line: + break + #the real code does filtering here + print (line.rstrip()) diff --git a/01.Procesos/P1-13-ComunicacionProceso.py b/01.Procesos/P1-13-ComunicacionProceso.py new file mode 100644 index 0000000..b6dbefc --- /dev/null +++ b/01.Procesos/P1-13-ComunicacionProceso.py @@ -0,0 +1,15 @@ +import subprocess + +p1 = subprocess.Popen('ftp', shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) +comandos = [b"verbose\n", + b"open test.rebex.net\n", + b"demo\n", + b"password\n", + b"ls\n", + b"get readme.txt\n"] + +for cmd in comandos: + p1.stdin.write (cmd) + +respuesta = p1.communicate(timeout=5)[0] +print (respuesta.decode("cp850", "ignore")) diff --git a/01.Procesos/P1-14-ComunicacionPortapapeles.py b/01.Procesos/P1-14-ComunicacionPortapapeles.py new file mode 100644 index 0000000..a5239a7 --- /dev/null +++ b/01.Procesos/P1-14-ComunicacionPortapapeles.py @@ -0,0 +1,14 @@ +#instalar previamente: "pip install pywin32" +import win32clipboard + +#enviar datos al portapapeles +win32clipboard.OpenClipboard() +win32clipboard.EmptyClipboard() +win32clipboard.SetClipboardText("MARCOMBO") +win32clipboard.CloseClipboard() + +# obtener datos del portapapeles +win32clipboard.OpenClipboard() +datos = win32clipboard.GetClipboardData() +win32clipboard.CloseClipboard() +print (datos) diff --git a/01.Procesos/P1.01-Fork.py b/01.Procesos/P1.01-Fork.py new file mode 100644 index 0000000..2ec4379 --- /dev/null +++ b/01.Procesos/P1.01-Fork.py @@ -0,0 +1,21 @@ +# fork solo funciona en unix/macos +import os + + +def padre(): + while True: + newpid = os.fork() + if newpid == 0: + hijo() + else: + pids = (os.getpid(), newpid) + print("Padre: %d, Hijo: %d\n" % pids) + reply = input("Pulsa 's' si quieres crear un nuevo proceso") + if reply != 's': + break + +def hijo(): + print('\n>>>>>>>>>> Nuevo hijo creado con el pid %d a punto de finalizar<<<<<' % os.getpid()) + os._exit(0) + +padre() \ No newline at end of file diff --git a/01.Procesos/P1.02-likefork-win.py b/01.Procesos/P1.02-likefork-win.py new file mode 100644 index 0000000..72cab95 --- /dev/null +++ b/01.Procesos/P1.02-likefork-win.py @@ -0,0 +1,18 @@ +from multiprocessing import Process +import os + +def hijo(): + print("Padre: %d, Hijo: %d\n" % ( os.getppid(),os.getpid())) + os._exit(0) + +def padre(): + while True: + p = Process(target=hijo) + p.start() + print ("\nNuevo hijo creado " , p.pid) + p.join() + reply = input("Pulsa 's' si quieres crear un nuevo proceso\n") + if reply != 's': + break +if __name__ == '__main__': + padre() \ No newline at end of file diff --git a/01.Procesos/P1.03-Lanza-notepad.py b/01.Procesos/P1.03-Lanza-notepad.py new file mode 100644 index 0000000..9eb3a4a --- /dev/null +++ b/01.Procesos/P1.03-Lanza-notepad.py @@ -0,0 +1,7 @@ +import subprocess +try: + subprocess.run(['Notepad.exe',]) + subprocess.run(['c:/windows/notepad.exe',]) + subprocess.run(['Notepad.exe','texto.txt']) +except subprocess.CalledProcessError as e: + print(e.output) \ No newline at end of file diff --git a/01.Procesos/P1.04-proceso-parametros.py b/01.Procesos/P1.04-proceso-parametros.py new file mode 100644 index 0000000..dea07be --- /dev/null +++ b/01.Procesos/P1.04-proceso-parametros.py @@ -0,0 +1,8 @@ +import subprocess + +try: + subprocess.run(["ping", "www.marcombo.com","-n","5"]) + #en linux + #subprocess.run(["ping", "www.marcombo.com","-c","5"]) +except subprocess.CalledProcessError as e: + print(e.output) \ No newline at end of file diff --git a/01.Procesos/P1.05-proceso-entorno.py b/01.Procesos/P1.05-proceso-entorno.py new file mode 100644 index 0000000..e8c3565 --- /dev/null +++ b/01.Procesos/P1.05-proceso-entorno.py @@ -0,0 +1,14 @@ +import subprocess + +def iniciaPrograma(): + try: + SW_SHOWMAXIMIZED = 3 + info = subprocess.STARTUPINFO() + info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + info.wShowWindow = SW_SHOWMAXIMIZED + subprocess.Popen('Notepad.exe', startupinfo=info) + except subprocess.CalledProcessError as e: + print(e.output) +iniciaPrograma() +input("Pulsa una tecla para terminar la ejecución") + diff --git a/01.Procesos/P1.06-proceso-referencia.py b/01.Procesos/P1.06-proceso-referencia.py new file mode 100644 index 0000000..b484e2e --- /dev/null +++ b/01.Procesos/P1.06-proceso-referencia.py @@ -0,0 +1,17 @@ +import subprocess +import time + +def CrearProceso(): + try: + SW_SHOWMAXIMIZED = 3 + info = subprocess.STARTUPINFO() + info.dwFlags |= subprocess.STARTF_USESHOWWINDOW + info.wShowWindow = SW_SHOWMAXIMIZED + proc = subprocess.Popen('notepad.exe', startupinfo=info) + return proc + except subprocess.CalledProcessError as e: + print(e.output) +p = CrearProceso() +print ("El PID de este proceso es: " + str(p.pid)) +time.sleep(5) + diff --git a/01.Procesos/P1.07-Listado-procesos-sistema.py b/01.Procesos/P1.07-Listado-procesos-sistema.py new file mode 100644 index 0000000..aef2e5c --- /dev/null +++ b/01.Procesos/P1.07-Listado-procesos-sistema.py @@ -0,0 +1,12 @@ +#instalar psutil: pip install psutil +import psutil + +try: + for proc in psutil.process_iter(): + #Obtener el nombre y el PID de cada proceso + processName = proc.name() + processID = proc.pid + print(processName , ' ::: ', processID) +#except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess) as e: +except: + print("error") \ No newline at end of file diff --git a/01.Procesos/P1.08-Listado-procesos-sistema-win-wmic.py b/01.Procesos/P1.08-Listado-procesos-sistema-win-wmic.py new file mode 100644 index 0000000..260c472 --- /dev/null +++ b/01.Procesos/P1.08-Listado-procesos-sistema-win-wmic.py @@ -0,0 +1,10 @@ +import subprocess + +# obtención de los procesos +Datos = subprocess.check_output(['wmic', 'process', 'list', 'brief']) +a = str(Datos) +try: + for i in range(len(a)): + print(a.split("\\r\\r\\n")[i]) +except IndexError as e: + print("Finalizado") diff --git a/01.Procesos/P1.09-Acceso-propiedades-proceso.py b/01.Procesos/P1.09-Acceso-propiedades-proceso.py new file mode 100644 index 0000000..2436174 --- /dev/null +++ b/01.Procesos/P1.09-Acceso-propiedades-proceso.py @@ -0,0 +1,27 @@ +import psutil +import os +import subprocess +import sys + +def ProcesoActual (): + return psutil.Process(os.getpid()) + +def esWindows(): + try: + sys.getwindowsversion() + except AttributeError: + return (False) + else: + return (True) + +print (ProcesoActual().name()) #nombre +print (ProcesoActual().cwd()) #path de ejecución +#prioridad ante del cambio +print (ProcesoActual().nice()) +if esWindows(): + subprocess.check_output("wmic process where processid=\""+str(os.getpid())+"\" CALL setpriority \"below normal\"") +else: + os.nice(1) +#prioridad después del cambio +print (ProcesoActual().nice()) +a = input() \ No newline at end of file diff --git a/01.Procesos/P1.10-Eliminacion-procesos.py b/01.Procesos/P1.10-Eliminacion-procesos.py new file mode 100644 index 0000000..2d3d3db --- /dev/null +++ b/01.Procesos/P1.10-Eliminacion-procesos.py @@ -0,0 +1,13 @@ +import psutil + +for proc in psutil.process_iter(): + try: + # Obtener el nombre del proceso + nombreProceso = proc.name() + if proc.name() == "notepad.exe": + PID = proc.pid + print("Eliminando el proceso: ", nombreProceso , ' ::: ', PID) + proc.kill() + except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess): + print ("error") + diff --git a/01.Procesos/algo.txt b/01.Procesos/algo.txt new file mode 100644 index 0000000..7284ab4 --- /dev/null +++ b/01.Procesos/algo.txt @@ -0,0 +1 @@ +aaaa \ No newline at end of file diff --git a/02.Threads/P2.01-HelloThread.py b/02.Threads/P2.01-HelloThread.py new file mode 100644 index 0000000..40b7c77 --- /dev/null +++ b/02.Threads/P2.01-HelloThread.py @@ -0,0 +1,9 @@ +import threading + +#método al que se va a aosciar el hilo +def Saludo(): + print ('hola') + +t = threading.Thread(target=Saludo) +t.start() +print ("hola") #impresión en el hilo principal \ No newline at end of file diff --git a/02.Threads/P2.02-MultiplesHIlos.py b/02.Threads/P2.02-MultiplesHIlos.py new file mode 100644 index 0000000..34041f9 --- /dev/null +++ b/02.Threads/P2.02-MultiplesHIlos.py @@ -0,0 +1,13 @@ +import threading + +def actividad(): + print ("Escribo desde un hilo") + return + +print ("INICIO") +hilos = list() +for i in range(50): + t = threading.Thread(target=actividad) + hilos.append(t) + t.start() +print ("ESCRIBO EN PRINCIPAL") diff --git a/02.Threads/P2.03-Alternancia.py b/02.Threads/P2.03-Alternancia.py new file mode 100644 index 0000000..1643ddf --- /dev/null +++ b/02.Threads/P2.03-Alternancia.py @@ -0,0 +1,13 @@ +import threading + +def escribeY(): + for i in range(1000): + print ("y", end="") + return + +print ("INICIO") +t = threading.Thread(target=escribeY) +t.start() + +for i in range(1000): + print ("X", end="") \ No newline at end of file diff --git a/02.Threads/P2.04-AccesoDatosComunes.py b/02.Threads/P2.04-AccesoDatosComunes.py new file mode 100644 index 0000000..90231c7 --- /dev/null +++ b/02.Threads/P2.04-AccesoDatosComunes.py @@ -0,0 +1,17 @@ +import threading +import time +import random + +def tareaUno(): + global Realizado + #time.sleep (random.random()) + if not Realizado: + print("Tarea realizada") + Realizado = True + return + +Realizado = False +t = threading.Thread(target=tareaUno) +t.start() +tareaUno() +time.sleep(1) diff --git a/02.Threads/P2.04b-AccesoDatosComunesMuchosHilos.py b/02.Threads/P2.04b-AccesoDatosComunesMuchosHilos.py new file mode 100644 index 0000000..dbf08b8 --- /dev/null +++ b/02.Threads/P2.04b-AccesoDatosComunesMuchosHilos.py @@ -0,0 +1,22 @@ +import threading +import time +import random + +def tareaUno(): + global Done + #time.sleep (random.random()) + if not Done: + print("Tarea realizada") + Done = True + else : + print ("tarea NO REALIZADA") + return + +Done = False +hilos = list() +for i in range(50): + t = threading.Thread(target=tareaUno) + hilos.append(t) + t.start() +tareaUno() +time.sleep(1) \ No newline at end of file diff --git a/02.Threads/P2.05-PasarDatos.py b/02.Threads/P2.05-PasarDatos.py new file mode 100644 index 0000000..f039bdb --- /dev/null +++ b/02.Threads/P2.05-PasarDatos.py @@ -0,0 +1,14 @@ +import logging +import threading +import time + +def thread_Apellido(name): + print (name + " Rodríguez") + #print (name + " Rodríguez\n") + +nombres = ["Julio", "Javier", "Eladio", "Jose", "Manuel"] +hilos = list() +for n in nombres: + t = threading.Thread(target=thread_Apellido, args=(n,)) + hilos.append(t) + t.start() \ No newline at end of file diff --git a/02.Threads/P2.06-PasarDatosDict.py b/02.Threads/P2.06-PasarDatosDict.py new file mode 100644 index 0000000..6ac6530 --- /dev/null +++ b/02.Threads/P2.06-PasarDatosDict.py @@ -0,0 +1,15 @@ +import logging +import threading +import time + +def thread_Apellido(name, inicio=1, fin=1): + for x in range(inicio, fin): + print (f"{name} Rodríguez {str(x)} años\n",end="") + + +nombres = ["Julio", "Javier", "Eladio", "Jose", "Manuel"] +hilos = list() +for n in nombres: + t = threading.Thread(target=thread_Apellido, args=(n,), kwargs={'inicio':5, 'fin':8}) + hilos.append(t) + t.start() \ No newline at end of file diff --git a/02.Threads/P2.07-LocalSotrage.py b/02.Threads/P2.07-LocalSotrage.py new file mode 100644 index 0000000..dd23828 --- /dev/null +++ b/02.Threads/P2.07-LocalSotrage.py @@ -0,0 +1,24 @@ +import threading +import random + +def mostrar(d): + try: + val = d.valor + except AttributeError: + print(f"{threading.current_thread().name}: Aún no inicializado\n", end="") + else: + print(f"{threading.current_thread().name}: {val}\n", end="") + +def hilo(dato): + mostrar(dato) + dato.valor = random.randint(1, 100) + mostrar(dato) + +dato = threading.local() #variable con instancia local en cada hilo +mostrar(dato) +dato.valor = 999 +mostrar(dato) + +for i in range(3): + t = threading.Thread(target=hilo, args=(dato,)) + t.start() \ No newline at end of file diff --git a/02.Threads/P2.08-Propiedades.py b/02.Threads/P2.08-Propiedades.py new file mode 100644 index 0000000..4036712 --- /dev/null +++ b/02.Threads/P2.08-Propiedades.py @@ -0,0 +1,16 @@ +import threading + +def Funcion_hilo(): + if (threading.current_thread().name == "miHilo7"): + threading.current_thread().name = "nombre-cambiado" + print (f"Hola desde: {threading.current_thread().name} \ + ID {threading.current_thread().ident}\n", end="") + #threading.current_thread().ident = 666 #comprobaicón error + +hilos = list() +for n in range(1,11): + t = threading.Thread(target=Funcion_hilo, name = "miHilo") + if (n>5): + t.name = "miHilo"+str (n) + hilos.append(t) + t.start() \ No newline at end of file diff --git a/02.Threads/P2.09-ListarProcesos.py b/02.Threads/P2.09-ListarProcesos.py new file mode 100644 index 0000000..b2f8926 --- /dev/null +++ b/02.Threads/P2.09-ListarProcesos.py @@ -0,0 +1,13 @@ +import threading +import time + +def tarea(): + time.sleep(1) + return + +for _ in range(10): + threading.Thread(target=tarea).start() + +print ("Hilos activos: ",threading.active_count()) +for thread in threading.enumerate(): + print(thread.name) \ No newline at end of file diff --git a/02.Threads/P2.10-daemon-falso.py b/02.Threads/P2.10-daemon-falso.py new file mode 100644 index 0000000..b201e02 --- /dev/null +++ b/02.Threads/P2.10-daemon-falso.py @@ -0,0 +1,13 @@ +from threading import * +import time + +def hilo(): + for i in range(10): + print('Hilo no Daemon (Foregoround)') + time.sleep(1) + +t = Thread(target=hilo) +t.start() + +time.sleep(5) +print('Hilo principal') \ No newline at end of file diff --git a/02.Threads/P2.11-daemon-true.py b/02.Threads/P2.11-daemon-true.py new file mode 100644 index 0000000..29f2ada --- /dev/null +++ b/02.Threads/P2.11-daemon-true.py @@ -0,0 +1,14 @@ +from threading import * +import time + +def hilo(): + for i in range(10): + print('Hilo no Daemon (Backgoround)') + time.sleep(1) + +t = Thread(target=hilo) +t.daemon = True +t.start() + +time.sleep(5) +print('Hilo principal') \ No newline at end of file diff --git a/02.Threads/P2.12-FinPostergado.py b/02.Threads/P2.12-FinPostergado.py new file mode 100644 index 0000000..409dd4b --- /dev/null +++ b/02.Threads/P2.12-FinPostergado.py @@ -0,0 +1,10 @@ +from threading import * +import time + +def hilo(): + #time.sleep(1) + print("Hola") + +T = Thread(target = hilo) +T.daemon=True +T.start() diff --git a/02.Threads/P2.13-ThreadDentroClase.py b/02.Threads/P2.13-ThreadDentroClase.py new file mode 100644 index 0000000..c6d9a74 --- /dev/null +++ b/02.Threads/P2.13-ThreadDentroClase.py @@ -0,0 +1,23 @@ +import threading + +class miHilo(threading.Thread): + def __init__(self, num): + super(miHilo, self).__init__() + self.numero = num + self.nombre = "" + def run(self): + if self.nombre == "hiloPar": + print (f'par: {self.numero}\n',end='') + else: + print (f'impar: {self.numero}\n',end='') + +ListaHilos = [] #inicio con la lista vacía +for i in range(4): + t = miHilo(i) + if (i % 2) == 0: + t.nombre = "hiloPar" + else: + t.nombre = "hiloImpar" + ListaHilos.append(t) +for h in ListaHilos: + h.start() \ No newline at end of file diff --git a/02.Threads/P2.14-CambiarDatosEnEjecucion.py b/02.Threads/P2.14-CambiarDatosEnEjecucion.py new file mode 100644 index 0000000..2e820a9 --- /dev/null +++ b/02.Threads/P2.14-CambiarDatosEnEjecucion.py @@ -0,0 +1,24 @@ +from threading import Thread +import time +import logging + +logging.basicConfig( level=logging.DEBUG, + format='[%(threadName)-10s]: %(message)s') + +class miHilo(Thread): + def __init__(self): + super(miHilo, self).__init__() + self.nombre= "" + def run(self): + for i in range (20): + #print (self.nombre) + logging.debug ("iteración %i, nombre: %s", i+1,self.nombre) + time.sleep (0.1) + +t = miHilo() +t.nombre ="Pepe" +t.start() +time.sleep(0.5) +#modificamos un dato posteriormente a inicializarse el hilo +t.dato ="Lola" + \ No newline at end of file diff --git a/02.Threads/P2.15-Join.py b/02.Threads/P2.15-Join.py new file mode 100644 index 0000000..332e9a9 --- /dev/null +++ b/02.Threads/P2.15-Join.py @@ -0,0 +1,25 @@ +import threading +import logging + +logging.basicConfig( level=logging.DEBUG, + format='[%(threadName)-10s]: %(message)s') + +def imprime_x(x, n): + logging.debug("INICIO") + for i in range(n): + logging.debug (x) + logging.debug("FIN") + +t1 = threading.Thread(target=imprime_x, args=("norte", 5,)) +t2 = threading.Thread(target=imprime_x, args=("sur", 10,)) + +t1.start() +t2.start() + +# espera hasta que el hilo t1 hay finalizado +t1.join() +# espera hasta que el hilo t2 hay finalizado +t2.join() + +#llega hasta aquí cuando los dos hilos han +logging.debug("Fin!") \ No newline at end of file diff --git a/02.Threads/P2.16-Acceso_concurrente.py b/02.Threads/P2.16-Acceso_concurrente.py new file mode 100644 index 0000000..fe3ce22 --- /dev/null +++ b/02.Threads/P2.16-Acceso_concurrente.py @@ -0,0 +1,25 @@ +from threading import Lock, Thread +import time + +def suma_uno(): + global g + a = g + time.sleep(0.001) + g = a+1 + +def suma_tres(): + global g + a = g + time.sleep(0.001) + g =a+3 + +g = 0 +threads = [] +for func in [suma_uno,suma_tres]: + threads.append(Thread(target=func)) + threads[-1].start() + +for thread in threads: + thread.join() + +print(g) diff --git a/02.Threads/P2.16a-Acceso_concurrente_Lock.py b/02.Threads/P2.16a-Acceso_concurrente_Lock.py new file mode 100644 index 0000000..0edac28 --- /dev/null +++ b/02.Threads/P2.16a-Acceso_concurrente_Lock.py @@ -0,0 +1,30 @@ +from threading import Lock, Thread +import time + +def suma_uno(): + global g + lock.acquire() + a = g + time.sleep(0.001) + g = a+1 + lock.release() + +def suma_tres(): + global g + with lock: + a = g + time.sleep(0.001) + g =a+3 + +lock = Lock() +g = 0 +threads = [] + +for func in [suma_uno,suma_tres]: + threads.append(Thread(target=func)) + threads[-1].start() + +for thread in threads: + thread.join() + +print(g) diff --git a/02.Threads/P2.17-Lock_como_parametro.py b/02.Threads/P2.17-Lock_como_parametro.py new file mode 100644 index 0000000..7ad68d4 --- /dev/null +++ b/02.Threads/P2.17-Lock_como_parametro.py @@ -0,0 +1,27 @@ +import threading +from time import time + +def f_hilo(lock): + global x + for _ in range(1000000): + #lock.acquire() + for _ in range(100): + a=x + x=a+1 + #lock.release() + +x = 0 +#tiempo_inicial = time() +lock = threading.Lock() + +t1 = threading.Thread(target=f_hilo, args=(lock,)) +t2 = threading.Thread(target=f_hilo, args=(lock,)) + +t1.start() +t2.start() + +t1.join() +t2.join() + +#print (time()-tiempo_inicial) +print (x) diff --git a/02.Threads/P2.18-LockCuentas.py b/02.Threads/P2.18-LockCuentas.py new file mode 100644 index 0000000..508e45d --- /dev/null +++ b/02.Threads/P2.18-LockCuentas.py @@ -0,0 +1,55 @@ + +import threading +import time + +class CuentaBancaria: + def __init__(self, nombre, saldo): + self.nombre = nombre + self.saldo = saldo + + def __str__(self): + return self.nombre+' '+str(self.saldo) + +class BankTransferThread(threading.Thread): + def __init__(self, ordenante, receptor, cantidad): + threading.Thread.__init__(self) + self.ordenante = ordenante + self.receptor = receptor + self.cantidad = cantidad + + def run(self): + lock.acquire() + + saldo_ordenante= self.ordenante.saldo + saldo_ordenante-= self.cantidad + # retraso para permitir ejecutar saltar entre hilos + time.sleep(0.001) + self.ordenante.saldo = saldo_ordenante + + saldo_receptor = self.receptor.saldo + saldo_receptor += self.cantidad + # retraso para permitir ejecutar saltar entre hilos + time.sleep(0.001) + self.receptor.saldo = saldo_receptor + + lock.release() + + +# Las cuentas son recursos compartidos +cuenta1 = CuentaBancaria("cuentaOrigen", 100) +cuenta2 = CuentaBancaria("cuentaDestino", 0) + +lock = threading.Lock() +threads = [] + +for i in range(100): + threads.append(BankTransferThread(cuenta1, cuenta2, 1)) + +for thread in threads: + thread.start() + +for thread in threads: + thread.join() + +print(cuenta1) +print(cuenta2) \ No newline at end of file diff --git a/02.Threads/P2.19-Lock-consucutivos.py b/02.Threads/P2.19-Lock-consucutivos.py new file mode 100644 index 0000000..02e1172 --- /dev/null +++ b/02.Threads/P2.19-Lock-consucutivos.py @@ -0,0 +1,14 @@ +import threading + +algo = 0 + +lock = threading.Lock() + +lock.acquire() +algo +=1 + +lock.acquire() +algo += 2 +lock.release() + +print(algo) \ No newline at end of file diff --git a/02.Threads/P2.19a-RLock.py b/02.Threads/P2.19a-RLock.py new file mode 100644 index 0000000..dd1a219 --- /dev/null +++ b/02.Threads/P2.19a-RLock.py @@ -0,0 +1,15 @@ +import threading + +algo = 0 + +rlock = threading.RLock() + +rlock.acquire() +algo += 1 + +rlock.acquire() +algo += 2 +rlock.release() +rlock.release() + +print(algo) \ No newline at end of file diff --git a/02.Threads/P2.20-Condicionales.py b/02.Threads/P2.20-Condicionales.py new file mode 100644 index 0000000..c85c1a1 --- /dev/null +++ b/02.Threads/P2.20-Condicionales.py @@ -0,0 +1,25 @@ + +import threading + +def ping(cond): + for _ in range(20): + with cond: + cond.wait() + print ("ping") + cond.notify() + +def pong(cond): + for _ in range(20): + with cond: + cond.wait() + print ("pong") + cond.notify() + +cond = threading.Condition() + +t1= threading.Thread(target=ping, args=(cond,)) +t2=threading.Thread(target=pong, args=(cond,)) +t1.start() +t2.start() +with cond: + cond.notify() \ No newline at end of file diff --git a/02.Threads/P2.21-Semaforos.py b/02.Threads/P2.21-Semaforos.py new file mode 100644 index 0000000..5fa5738 --- /dev/null +++ b/02.Threads/P2.21-Semaforos.py @@ -0,0 +1,35 @@ +import threading +import time +import logging + +logging.basicConfig(level=logging.DEBUG, + format='(%(threadName)-9s) %(message)s',) + +class ThreadPool(object): + def __init__(self): + super(ThreadPool, self).__init__() + self.active = [] + self.lock = threading.Lock() + def makeActive(self, name): + with self.lock: + self.active.append(name) + logging.debug('Running: %s', self.active) + def makeInactive(self, name): + with self.lock: + self.active.remove(name) + logging.debug('Running: %s', self.active) + +def f(s, pool): + logging.debug('Esperando para unirse al grupo') + with s: + name = threading.current_thread().name + pool.makeActive(name) + time.sleep(0.5) + pool.makeInactive(name) + + +pool = ThreadPool() +s = threading.Semaphore(3) +for i in range(10): + t = threading.Thread(target=f, name='thread_'+str(i), args=(s, pool)) + t.start() \ No newline at end of file diff --git a/02.Threads/P2.22-Eventos.py b/02.Threads/P2.22-Eventos.py new file mode 100644 index 0000000..779e8f4 --- /dev/null +++ b/02.Threads/P2.22-Eventos.py @@ -0,0 +1,20 @@ +import threading +import time + +def genera_eventos(): + for x in range (5): + time.sleep(2) + ev.set() + +def escribe_algo(): + while (True): + ev.wait() + print ("hola") + ev.clear() + +ev = threading.Event() +T1 = threading.Thread(target=genera_eventos) +T2 = threading.Thread(target=escribe_algo) + +T1.start() +T2.start() \ No newline at end of file diff --git a/02.Threads/P2.23-Timer.py b/02.Threads/P2.23-Timer.py new file mode 100644 index 0000000..a1dbb27 --- /dev/null +++ b/02.Threads/P2.23-Timer.py @@ -0,0 +1,17 @@ +import threading +import time + +def tarea(num): + for _ in range (num): + print("Ejecutando tarea...") + time.sleep(1) + #print (threading.currentThread().name) + +veces = 10 +t = threading.Timer(3, tarea, [veces,]) +print("Iniciando hilo con temporizador") +t.start() #será ejecutado tras tres segundos +time.sleep(5) +#prueba para cancelar el hilo +print("Cancelando la tarea <>") +t.cancel() \ No newline at end of file diff --git a/02.Threads/P2.24-Barreras.py b/02.Threads/P2.24-Barreras.py new file mode 100644 index 0000000..0d90c59 --- /dev/null +++ b/02.Threads/P2.24-Barreras.py @@ -0,0 +1,14 @@ +import time +import random +import threading + +def f(): + time.sleep(random.randint(1,10)) + print("{} despertado: {}".format(threading.current_thread().name, time.ctime())) + barrera.wait() + print("{} pasó la barrera: {}\n".format(threading.current_thread().name, time.ctime())) + +barrera = threading.Barrier(5) +for _ in range(5): + t = threading.Thread(target=f) + t.start() \ No newline at end of file diff --git a/02.Threads/P2.25-Productor-consumidor.py b/02.Threads/P2.25-Productor-consumidor.py new file mode 100644 index 0000000..b390dad --- /dev/null +++ b/02.Threads/P2.25-Productor-consumidor.py @@ -0,0 +1,50 @@ +import threading +import time +import logging +import random +import queue + +logging.basicConfig(level=logging.DEBUG, + format='(%(threadName)-9s) %(message)s',) + +BUF_SIZE = 10 +q = queue.Queue(BUF_SIZE) + +class HiloProductor(threading.Thread): + def __init__(self,name=None): + super(HiloProductor,self).__init__() + self.name = name + + def run(self): + while True: + if not q.full(): + item = random.randint(1,10) + q.put(item) + logging.debug('Insertando "' + str(item) + + '" : ' + str(q.qsize()) + ' elementos en la cola') + time.sleep(random.random()) + return + +class HiloConsumidor(threading.Thread): + def __init__(self,name=None): + super(HiloConsumidor,self).__init__() + self.name = name + return + + def run(self): + while True: + if not q.empty(): + item = q.get() + logging.debug('Sacando "' + str(item) + + '" : ' + str(q.qsize()) + ' elementos en la cola') + time.sleep(random.random()) + return + +p = HiloProductor(name='productor') +p2 = HiloProductor(name='productor2') + +c = HiloConsumidor(name='consumidor') + +p.start() +p2.start() +c.start() \ No newline at end of file diff --git a/02.Threads/P2.26-Exception.py b/02.Threads/P2.26-Exception.py new file mode 100644 index 0000000..c67ebaf --- /dev/null +++ b/02.Threads/P2.26-Exception.py @@ -0,0 +1,13 @@ +import threading + +def f(): + pass + +try: + t = threading.Thread(target=f) + t.start() + t.start() +except Exception as e: + print("Error:", e) +print ("Final del programa") + \ No newline at end of file diff --git a/02.Threads/P2.27-Abort.py b/02.Threads/P2.27-Abort.py new file mode 100644 index 0000000..7265e68 --- /dev/null +++ b/02.Threads/P2.27-Abort.py @@ -0,0 +1,32 @@ +import threading +import time + +class HiloAbortable(threading.Thread): + + def __init__(self): + super(HiloAbortable, self).__init__() + self._abort_event = threading.Event() + + def abort(self): + self._abort_event.set() + + def aborted(self): + return self._abort_event.is_set() + + def run(self): + while (True): + if (self.aborted()): + print ("Hilo abortado") + break + #resto del trabajo a ejecutar + print ("trabajando...") + +h =HiloAbortable() +h.start() +time.sleep(1) +print ("Antes de invocación a abort()") +h.abort() +print ("Después de invocación de abort()") + + + diff --git a/03.Network-Sockets/P3.01-IPv4Address.py b/03.Network-Sockets/P3.01-IPv4Address.py new file mode 100644 index 0000000..05adbd9 --- /dev/null +++ b/03.Network-Sockets/P3.01-IPv4Address.py @@ -0,0 +1,17 @@ +import ipaddress + +ip = ipaddress.IPv4Address('224.0.0.1') + +print("Bits en la IP:", ip.max_prefixlen) +print("Multicast:", ip.is_multicast) +print("Privada:", ip.is_private) +print("Pública:", ip.is_global) +print("No es específica:", ip.is_unspecified) +print("Reservada:", ip.is_reserved) +print("Loopback:", ip.is_loopback) +print("Uso local:", ip.is_link_local) +ip1 = ip + 1 +print("IP siguiente:", ip1) +ip2 = ip - 1 +print("IP anterior:", ip2) +print(ip1 , "mayor que", ip2, ":", ip1 > ip2) diff --git a/03.Network-Sockets/P3.02-IPv4Network.py b/03.Network-Sockets/P3.02-IPv4Network.py new file mode 100644 index 0000000..7ca0d78 --- /dev/null +++ b/03.Network-Sockets/P3.02-IPv4Network.py @@ -0,0 +1,20 @@ +import ipaddress + +network = ipaddress.IPv4Network("192.168.1.0/24") + +print("Dirección de la red:", network.network_address) +print("Dirección de broadcast:", network.broadcast_address) +print("Máscara de red:", network.netmask) +print("Red y máscara de red:", network.with_netmask) +print("Red y máscara de host:", network.with_hostmask) +print("Longitud de la máscara de red", network.prefixlen) +print("Máximo número de equipos en la red:", network.num_addresses) +print("La red 192.168.0.0/16 la contiene:", + network.overlaps(ipaddress.IPv4Network("192.168.0.0/16"))) +print("Supernet:", network.supernet(prefixlen_diff=1)) +print("La red es una subnet de 192.168.0.0/16:", + network.subnet_of(ipaddress.IPv4Network("192.168.0.0/16"))) +print("La red es una supernet de 192.168.0.0/16:", + network.supernet_of(ipaddress.IPv4Network("192.168.0.0/16"))) +print("Comparar con 192.168.0.0/16:", + network.compare_networks(ipaddress.IPv4Network("192.168.0.0/16"))) diff --git a/03.Network-Sockets/P3.03-ObtenerIPpropia.py b/03.Network-Sockets/P3.03-ObtenerIPpropia.py new file mode 100644 index 0000000..42c309d --- /dev/null +++ b/03.Network-Sockets/P3.03-ObtenerIPpropia.py @@ -0,0 +1,6 @@ +import socket + +host = socket.gethostname() +ip = socket.gethostbyname(host) +print ("Nombre del equipo: %s" %host) +print ("Dirección IP: %s" %ip) \ No newline at end of file diff --git a/03.Network-Sockets/P3.04-ObtenerTodasIP.py b/03.Network-Sockets/P3.04-ObtenerTodasIP.py new file mode 100644 index 0000000..4165725 --- /dev/null +++ b/03.Network-Sockets/P3.04-ObtenerTodasIP.py @@ -0,0 +1,8 @@ +import ipaddress +import socket + +direcciones = socket.getaddrinfo(socket.gethostname(), None) +for ip in direcciones: + # ip_ver = ipaddress.ip_address(str(ip[4][0])) + # if ip_ver.version == 4: + print (ip[4][0]) diff --git a/03.Network-Sockets/P3.05-ObtenrIPExterna.py b/03.Network-Sockets/P3.05-ObtenrIPExterna.py new file mode 100644 index 0000000..22e9ef7 --- /dev/null +++ b/03.Network-Sockets/P3.05-ObtenrIPExterna.py @@ -0,0 +1,9 @@ +import socket + +try: + host = 'www.amazon.es' + #host = socket.getfqdn() + #host = 'DESKTOP-KEFADT0' + print ("IP de %s: %s" %(host,socket.gethostbyname(host))) +except socket.error as msg: + print ("%s: %s" %(host, msg)) \ No newline at end of file diff --git a/03.Network-Sockets/P3.06-Nslookup.py b/03.Network-Sockets/P3.06-Nslookup.py new file mode 100644 index 0000000..ed3351a --- /dev/null +++ b/03.Network-Sockets/P3.06-Nslookup.py @@ -0,0 +1,8 @@ +import socket + +ip = "216.58.209.69" +try: + dominio = socket.gethostbyaddr(ip)[0] + print ("La IP %s tiene una entrada DNS: %s" %(ip, dominio)) +except socket.error as msg: + print ("%s: %s" %(ip, msg)) \ No newline at end of file diff --git a/03.Network-Sockets/P3.07-tcpserver-try.py b/03.Network-Sockets/P3.07-tcpserver-try.py new file mode 100644 index 0000000..0e9bf04 --- /dev/null +++ b/03.Network-Sockets/P3.07-tcpserver-try.py @@ -0,0 +1,18 @@ +import socket + +HOST = '127.0.0.1' # direccion de loopback standard para localhost +PORT = 2000 # Puerto de escucha + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +try: + s.bind((HOST, PORT)) + s.listen(1) + conn, addr = s.accept() #función bloqueante + print (f"Conexión exitosa con el cliente. IP ({addr[0]}) Puerto ({addr[1]})") + #cerramos la conexión con ese cliente + conn.close() +except socket.error as exc: + print ("Excepción de socket: %s" % exc) +finally: + #cerramos la escucha general de nuestro servidor + s.close() \ No newline at end of file diff --git a/03.Network-Sockets/P3.08-tcpclient-try.py b/03.Network-Sockets/P3.08-tcpclient-try.py new file mode 100644 index 0000000..82a2897 --- /dev/null +++ b/03.Network-Sockets/P3.08-tcpclient-try.py @@ -0,0 +1,14 @@ +import socket + +HOST = '127.0.0.1' +PORT = 2000 + +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +try: + s.connect((HOST, PORT)) + print('Conectado con éxito') +except socket.error as exc: + print ("Excepción de socket: %s" % exc) +finally: + #cerramos la conexión, aunque el servidor ya la habrá cerrado + s.close() \ No newline at end of file diff --git a/03.Network-Sockets/P3.09-tcpserver-datos-with.py b/03.Network-Sockets/P3.09-tcpserver-datos-with.py new file mode 100644 index 0000000..ab20a62 --- /dev/null +++ b/03.Network-Sockets/P3.09-tcpserver-datos-with.py @@ -0,0 +1,17 @@ +import socket + +HOST = '' # todas las interfaces locales a la escucha +PORT = 2000 # Puerto de escucha + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((HOST, PORT)) + s.listen() + conn, addr = s.accept() #línea bloqueante + with conn: + print(f"Conexión exitosa con el cliente. IP ({addr[0]}) Puerto ({addr[1]})") + while True: + data = conn.recv(1024) #línea bloqueante + print (data) + if data==b"0": + break + conn.sendall(b"mensaje recibido") diff --git a/03.Network-Sockets/P3.09b-tcpserver-datos-with-try.py b/03.Network-Sockets/P3.09b-tcpserver-datos-with-try.py new file mode 100644 index 0000000..42f80bc --- /dev/null +++ b/03.Network-Sockets/P3.09b-tcpserver-datos-with-try.py @@ -0,0 +1,23 @@ +from audioop import add +import socket + +HOST = '' # todas las interfaces locales a la escuchat +PORT = 2000 # Puerto de escucha + +try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((HOST, PORT)) + s.listen() + conn, addr = s.accept() #línea bloqueante + with conn: + print(f"Conexión exitosa con el cliente. IP ({addr[0]}) Puerto ({addr[1]})") + while True: + data = conn.recv(1024) #línea bloqueante + print (data) + if data==b"0": + break + conn.sendall(b"mensaje recibido") + +except socket.error as e: + print ("Error en socket: %s" % e) + \ No newline at end of file diff --git a/03.Network-Sockets/P3.10-tcpclient-datos-with.py b/03.Network-Sockets/P3.10-tcpclient-datos-with.py new file mode 100644 index 0000000..27c9c74 --- /dev/null +++ b/03.Network-Sockets/P3.10-tcpclient-datos-with.py @@ -0,0 +1,14 @@ +import socket + +HOST = '127.0.0.1' +PORT = 2000 + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((HOST, PORT)) + print('Conectado con éxito') + s.send(b'Yo, tu cliente, te saludo.') + #numBytes = s.send(b'Yo, tu cliente, te saludo.') + #print (numBytes) + data = s.recv(1024) #línea bloqueante + +print('Recibido:', repr(data)) \ No newline at end of file diff --git a/03.Network-Sockets/P3.11-udpserver.py b/03.Network-Sockets/P3.11-udpserver.py new file mode 100644 index 0000000..537cd42 --- /dev/null +++ b/03.Network-Sockets/P3.11-udpserver.py @@ -0,0 +1,18 @@ +import socket + +HOST = '' +PORT = 2000 + +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + + s_addr = (HOST, PORT) + s.bind(s_addr) + #recibir + datos, addr = s.recvfrom(1024) #línea bloqueante + print('recibidos {} bytes de {}'.format(len(datos), addr)) + print(datos) + + #enviar + if datos: + sent = s.sendto(b"Saludos desde el Servidor UDP", addr) + print('enviados {} bytes de vuelta a {}'.format(sent, addr)) diff --git a/03.Network-Sockets/P3.11b-udpserver-bucle.py b/03.Network-Sockets/P3.11b-udpserver-bucle.py new file mode 100644 index 0000000..7501f37 --- /dev/null +++ b/03.Network-Sockets/P3.11b-udpserver-bucle.py @@ -0,0 +1,19 @@ +import socket + +HOST = '' +PORT = 2000 + +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + + s_addr = (HOST, PORT) + s.bind(s_addr) + while True: + #recibir + datos, addr = s.recvfrom(1024) #línea bloqueante + print('recibidos {} bytes de {}'.format(len(datos), addr)) + print(datos) + + #enviar + if datos: + sent = s.sendto(b"Saludos desde el Servidor UDP", addr) + print('enviados {} bytes de vuelta a {}'.format(sent, addr)) diff --git a/03.Network-Sockets/P3.12-udpclient.py b/03.Network-Sockets/P3.12-udpclient.py new file mode 100644 index 0000000..b4f659a --- /dev/null +++ b/03.Network-Sockets/P3.12-udpclient.py @@ -0,0 +1,19 @@ +import socket + +HOST = '127.0.0.1' +PORT = 2000 + +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: + s_addr = (HOST, PORT) + server_address = (s_addr) + message = b'Saludos desde el cliente' + + # enviar + print('Enviando {!r}'.format(message)) + sent = s.sendto(message, server_address) + + # recibir + print('Esperando por la respuesta') + data, server = s.recvfrom(1024) #línea bloqueante + print('recibidos {!r}'.format(data)) + diff --git a/04.Servicios/P4.01-TcpServer-Forever.py b/04.Servicios/P4.01-TcpServer-Forever.py new file mode 100644 index 0000000..ebeb68b --- /dev/null +++ b/04.Servicios/P4.01-TcpServer-Forever.py @@ -0,0 +1,32 @@ +import socketserver + +class TCPSocketHandler(socketserver.BaseRequestHandler): + #Clase para manejar las conexiones, se instancia una por cliente + + def handle(self): + #medodo a sobreescribir para nuestro propio manejo + #self.request se refiere al socket client conectado + print (f"Se han conectado desde: {self.client_address[0]} [{self.client_address[1]}]") + while True: + self.data = self.request.recv(128).strip() + print("Datos recibidos: ", self.data) + #remitimos los mismos datos en mayúscula + self.request.sendall(self.data.upper()) + if self.data == b"": + break + if self.data ==b"#": + raise KeyboardInterrupt + +if __name__ == "__main__": + HOST, PORT = "", 2000 + + # instanciamos el socket servidor con la clase asociada de callback + server = socketserver.TCPServer((HOST, PORT), TCPSocketHandler) + + # activamos el servidor (ponemos a la espera de clientes) + # podemos provocar una excepción con Ctrl-C + try: + server.serve_forever() + except KeyboardInterrupt: + print ("servidor finalizado") + \ No newline at end of file diff --git a/04.Servicios/P4.02-TcpServer-Handle.py b/04.Servicios/P4.02-TcpServer-Handle.py new file mode 100644 index 0000000..515d3a5 --- /dev/null +++ b/04.Servicios/P4.02-TcpServer-Handle.py @@ -0,0 +1,33 @@ +import socketserver + +class TCPSocketHandler(socketserver.BaseRequestHandler): + #Clase para manejar las conexiones, se instancia una por cliente + + def handle(self): + #medodo a sobreescribir para nuestro propio manejo + #self.request se refiere al socket client conectado + print (f"Se han conectado desde: {self.client_address[0]} [{self.client_address[1]}]") + + self.data = self.request.recv(128).strip() + print("Datos recibidos: ", self.data) + #remitimos los mismos datos en mayúscula + self.request.sendall(self.data.upper()) + # if self.data == b"0": + # break + # if self.data ==b"#": + # raise KeyboardInterrupt + +if __name__ == "__main__": + HOST, PORT = "", 2000 + + # instanciamos el socket servidor con la clase asociada de callback + server = socketserver.TCPServer((HOST, PORT), TCPSocketHandler) + + # activamos el servidor (ponemos a la espera de clientes) + # podemos provocar una excepción con Ctrl-C + try: + while True: + server.handle_request() + except KeyboardInterrupt: + print ("servidor finalizado") + \ No newline at end of file diff --git a/04.Servicios/P4.03-ClienteTCP.py b/04.Servicios/P4.03-ClienteTCP.py new file mode 100644 index 0000000..b816e8c --- /dev/null +++ b/04.Servicios/P4.03-ClienteTCP.py @@ -0,0 +1,16 @@ +import socket + +HOST, PORT = "localhost", 2000 +data = "hola desde cliente tcp" + + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + #conectar + sock.connect((HOST, PORT)) + #enviar + sock.sendall(bytes(data + "\n", "utf-8")) + #recibir + recibido = str(sock.recv(64), "utf-8") + +print("Enviado: {}".format(data)) +print("Recibido: {}".format(recibido)) \ No newline at end of file diff --git a/04.Servicios/P4.03-TcpServer-Forever-shutdown.py b/04.Servicios/P4.03-TcpServer-Forever-shutdown.py new file mode 100644 index 0000000..76843f3 --- /dev/null +++ b/04.Servicios/P4.03-TcpServer-Forever-shutdown.py @@ -0,0 +1,59 @@ +from asyncio.windows_events import NULL +import socketserver +import time +import threading + +class TCPSocketHandler(socketserver.BaseRequestHandler): + #Clase para manejar las conexiones, se instancia una por cliente + # def __init__(self, server_address, RequestHandlerClass, ): + + def set_shutdown_request(self): + self.shutdown_request = True + + + def handle(self): + #medodo a sobreescribir para nuestro propio manejo + #self.request se refiere al socket client conectado + print (f"Se han conectado desde: {self.client_address[0]} [{self.client_address[1]}]") + #print (self.server.request_queue_size) + self.shutdown_request = False + while not self.shutdown_request: + print (self.shutdown_request) + self.data = self.request.recv(128).strip() + print("Datos recibidos: ", self.data) + #remitimos los mismos datos en mayúscula + self.request.sendall(self.data.upper()) + if self.data == b"0": + break + if self.data ==b"#": + raise KeyboardInterrupt + +def Servir(servidor): + + + # instanciamos el socket servidor con la clase asociada de callback + servidor = socketserver.TCPServer((HOST, PORT), TCPSocketHandler) + # activamos el servidor (ponemos a la escucha) + # podomos porvocar una excepción con Ctrl-C + try: + servidor.serve_forever() + except KeyboardInterrupt: + print ("servidor finalizado") + +if __name__ == "__main__": + HOST, PORT = "", 2000 + # instanciamos el socket servidor con la clase asociada de callback + server = socketserver.TCPServer((HOST, PORT), TCPSocketHandler) + server_thread = threading.Thread(target=server.serve_forever) + # Exit the server thread when the main thread terminates + server_thread.daemon = True + server_thread.start() + + print ("uno") + time.sleep(10) + print ("dos") + server. + server.shutdown() + print ("tres") + + \ No newline at end of file diff --git a/04.Servicios/P4.04-UDPServer.py b/04.Servicios/P4.04-UDPServer.py new file mode 100644 index 0000000..ed0aad7 --- /dev/null +++ b/04.Servicios/P4.04-UDPServer.py @@ -0,0 +1,26 @@ +import socketserver + +class UDPHandler(socketserver.BaseRequestHandler): + #self.request es el par [datos,socketServidor] + + def handle(self): + data = self.request[0].strip() + sock = self.request[1] + print(" El cliente: {} envió:".format(self.client_address[0])) + print(data) + #bajo alguna circunstancia podemos detener el servidor con la siguiente línea + if data ==b"#": + raise KeyboardInterrupt + #envío de datos al cliente + sock.sendto(data.upper(), self.client_address) + +if __name__ == "__main__": + HOST, PORT = "", 2000 + with socketserver.UDPServer((HOST, PORT), UDPHandler) as server: + # activamos el servidor (ponemos a la escucha) + # podomos porvocar una excepción con Ctrl-C + try: + server.serve_forever() + except KeyboardInterrupt: + print ("servidor finalizado") + \ No newline at end of file diff --git a/04.Servicios/P4.05-ClienteUDP.py b/04.Servicios/P4.05-ClienteUDP.py new file mode 100644 index 0000000..21f958e --- /dev/null +++ b/04.Servicios/P4.05-ClienteUDP.py @@ -0,0 +1,15 @@ +import socket + +HOST, PORT = "localhost", 2000 +data = "hola desde cliente udp" + +#definir +with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock: + #se puede observar que no hay conectar + #enviar + sock.sendto(bytes(data + "\n", "utf-8"), (HOST, PORT)) + #recibir + recibido = str(sock.recv(64), "utf-8") + +print("Enviado: {}".format(data)) +print("Recibido: {}".format(recibido)) \ No newline at end of file diff --git a/04.Servicios/P4.06-TcpServerStream.py b/04.Servicios/P4.06-TcpServerStream.py new file mode 100644 index 0000000..1d68e22 --- /dev/null +++ b/04.Servicios/P4.06-TcpServerStream.py @@ -0,0 +1,38 @@ +import socketserver + +class TCPStreamHandler(socketserver.StreamRequestHandler): + #Clase para manejar las conexiones, se instancia una por cliente + #transmisión de datos por stream + + def handle(self): + # self.rfile es un stream de lectura + # self.wfile es un stream de escritura + print (f"Se han conectado desde: {self.client_address[0]} [{self.client_address[1]}]") + while True: + self.data = self.rfile.readline().strip() + print("Datos recibidos: ", self.data) + #remitimos los mismos datos en mayúscula + self.wfile.write(self.data.upper()) + #self.wfile.writelines((b"uno", b"dos",b"tres")) + if self.data == b"": + break + if self.data ==b"#": + raise KeyboardInterrupt + + + +if __name__ == "__main__": + HOST, PORT = "", 2000 + + # instanciamos el socket servidor con la clase asociada de callback + server = socketserver.TCPServer((HOST, PORT), TCPStreamHandler) + + # activamos el servidor (ponemos a la escucha) + # podomos porvocar una excepción con Ctrl-C + try: + server.serve_forever() + except KeyboardInterrupt: + print ("servidor finalizado") + + + diff --git a/04.Servicios/P4.07-TcpServerThreding.py b/04.Servicios/P4.07-TcpServerThreding.py new file mode 100644 index 0000000..d077333 --- /dev/null +++ b/04.Servicios/P4.07-TcpServerThreding.py @@ -0,0 +1,29 @@ +import threading +import socketserver + +class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler): + + def handle(self): + data = self.rfile.readline().strip().decode('ascii') + cur_thread = threading.currentThread() + print (threading.current_thread().name) + response = bytes("{}: {}".format(cur_thread.name, data), 'ascii') + self.request.sendall(response) + +class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): + pass + +if __name__ == "__main__": + HOST, PORT = "", 2000 + server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler) + + # activamos el servidor (ponemos a la escucha) + # podomos porvocar una excepción con Ctrl-C + try: + server_thread = threading.Thread(target=server.serve_forever) + server_thread.start() + while True: + pass + except KeyboardInterrupt: + print ("servidor finalizado") + \ No newline at end of file diff --git a/04.Servicios/P4.08-TcpServerAsyncio.py b/04.Servicios/P4.08-TcpServerAsyncio.py new file mode 100644 index 0000000..8d166b1 --- /dev/null +++ b/04.Servicios/P4.08-TcpServerAsyncio.py @@ -0,0 +1,27 @@ +import asyncio + +async def handle_echo(reader, writer): + data = await reader.readline() + mensaje = data.decode() + addr = writer.get_extra_info('peername') + + print(f"Recibido {mensaje!r} de {addr!r}") + + print(f"Enviado: {mensaje!r}") + writer.write(data) + await writer.drain() + + print("Cliente cerrado") + writer.close() + +async def main(): + server = await asyncio.start_server( + handle_echo, '127.0.0.1', 2000) + + addrs = ', '.join(str(sock.getsockname()) for sock in server.sockets) + print(f'Servidor en {addrs}') + + async with server: + await server.serve_forever() + +asyncio.run(main()) \ No newline at end of file diff --git a/04.Servicios/P4.09-AdivinaNumero.py b/04.Servicios/P4.09-AdivinaNumero.py new file mode 100644 index 0000000..a1e28a7 --- /dev/null +++ b/04.Servicios/P4.09-AdivinaNumero.py @@ -0,0 +1,28 @@ +import socket +import random + +IP = '' +PORT = 2000 +adivinar = random.randrange(1,9) + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((IP,PORT)) + s.listen() + print("Servidor escuchando") + print("Número a adivinar: " +str(adivinar)) + + (cli,add) = s.accept() + print("Cliente conectado en:",add) + + cli.send(b"Intenta adivinar mi numero! ") + while True : + data = cli.recv(64).decode() + print (data) + if int(data) == adivinar: + cli.send(b"HAS ACERTADO") + break + elif int(data) > adivinar: + cli.send(b"Mi numero es menor ") + else: + cli.send(b"Mi numero es mayor") + cli.close() \ No newline at end of file diff --git a/04.Servicios/P4.10-AdivinaNumeroMulti.py b/04.Servicios/P4.10-AdivinaNumeroMulti.py new file mode 100644 index 0000000..aeae32c --- /dev/null +++ b/04.Servicios/P4.10-AdivinaNumeroMulti.py @@ -0,0 +1,36 @@ +import socket +import random +import threading + +adivinar = random.randrange(1,9) + +def ManejaCliente(c,a): + c.send(b"Intenta adivinar mi numero! ") + while True : + data = c.recv(64).decode() + print (data) + if int(data) == adivinar: + c.send(b"HAS ACERTADO") + break + elif int(data) > adivinar: + c.send(b"Mi numero es menor ") + else: + c.send(b"Mi numero es mayor") + c.close() + +if __name__ == '__main__': + IP = '' + PORT = 2000 + + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((IP,PORT)) + s.listen() + print("Servidor escuchando") + print("Número a adivinar: " +str(adivinar)) + + while True: + (cli,addr) = s.accept() + print("Cliente conectado en:",addr) + #con cada conexión iniciamos un nuevo hilo que lo atienda + t = threading.Thread(target=ManejaCliente, args=(cli,addr)) + t.start() \ No newline at end of file diff --git a/04.Servicios/P4.11-PPTServidorBase.py b/04.Servicios/P4.11-PPTServidorBase.py new file mode 100644 index 0000000..bc4e465 --- /dev/null +++ b/04.Servicios/P4.11-PPTServidorBase.py @@ -0,0 +1,157 @@ +import socket +import threading +import time + +class Jugador: + def __init__(self): + self._nick = "" #nombre del jugador + self._addr = "" #dirección del cliente + self._jugada= "" #piedra/papel/tijera + self._puntos= 0 + + @property + def nick(self): + return self._nick + + @property + def addr(self): + return self._addr + + @property + def jugada(self): + return self._jugada + + @property + def puntos(self): + return self._puntos + + @property + def libre(self): + if self._nick == "": + return True + else: + return False + + @property + def sinJugar(self): + if self._jugada == "": + return True + else: + return False + + @nick.setter + def nick(self, nombre): + self._nick = nombre + + @addr.setter + def addr(self, nombre): + self._addr = nombre + + @jugada.setter + def jugada(self, nombre): + if nombre in ["piedra","papel","tijera",""]: + self._jugada = nombre + + @puntos.setter + def puntos(self, nombre): + self._puntos = nombre + + def puntuar(self): + print (self._puntos) + self._puntos += 1 + + def arbitrar(self,otroJugador): + #devuelve 0 si empate, -1 si gana otroJugador, 1 si gana el objeto actual + if ((self.jugada =="piedra" and otroJugador.jugada =="tijera") or + (self.jugada =="tijera" and otroJugador.jugada =="papel") or + (self.jugada =="papel" and otroJugador.jugada =="piedra")): + return 1 + elif ((otroJugador.jugada =="piedra" and self.jugada =="tijera") or + (otroJugador.jugada =="tijera" and self.jugada =="papel") or + (otroJugador.jugada =="papel" and self.jugada =="piedra")): + return -1 + else: + return 0 + +class ManejoCliente(threading.Thread): + def __init__(self,clientAddress,clientsocket): + threading.Thread.__init__(self) + self.csocket = clientsocket + self.cAddress = clientAddress + print ("Cliente conectado desde: ", self.cAddress) + + def run(self): + print ("Escuchando a peticiones de cliente: ", self.cAddress) + #mensaje de bienvenida con el protocolo + bienvenida ="#INSCRIBIR#nombre#\n#JUGADA#{piedra|papel|tijera}#\n#PUNTUACION#" + self.csocket.send(bytes(bienvenida,'UTF-8')) + + while True: + data = self.csocket.recv(512).decode("utf_8") + print ("Enviado desde cliente:<",data,">") + subdatos = data.split("#") + respuesta="#OK#" + if subdatos[1] == "INSCRIBIR": + if jugador1.libre: + jugador1.nick = subdatos[2] + jugador1.addr = self.cAddress + elif jugador2.libre: + jugador2.nick = subdatos[2] + jugador2.addr = self.cAddress + else: + respuesta="#NOK#ya hay dos jugadores#" + elif subdatos[1] == "JUGADA": + #comprobra valor válido + if subdatos[2] not in ["piedra","papel","tijera"]: + respuesta="#NOK#valores válidos: piedra/papel/tijera#" + #comprobar autenticidad IP+puerto registrados + elif self.cAddress in [jugador1.addr, jugador2.addr]: + #estamos con el jugador 1 + if self.cAddress == jugador1.addr: + jugador1.jugada = subdatos[2] + #pausar hasta que jugador 2 no haga su jugada + while (jugador2.sinJugar): + time.sleep(0.1) + #estamos con el jugador 2 + else: + jugador2.jugada = subdatos[2] + #pausar hasta que jugador 1 no haga su jugada + while (jugador1.sinJugar): + print (jugador1.sinJugar) + time.sleep(0.1) + #gana el 1 + if jugador1.arbitrar(jugador2)>0: + jugador1.puntuar() + respuesta="#OK#GANADOR:" + jugador1.nick + "#" + #gana el 2 + elif jugador1.arbitrar(jugador2)<0: + jugador2.puntuar() + respuesta="#OK#GANADOR:" + jugador2.nick + "#" + else: + respuesta ="#OK#EMPATE#" + #pequeña trampilla para que no colapsen los hilos + time.sleep(0.5) + jugador1.jugada ="" + jugador2.jugada ="" + #la ip+puerto del jugador no estaba en la partida + else: + respuesta="#NOK#el jugador no está en la partida#" + elif subdatos[1] == "PUNTUACION": + respuesta="#OK#" + jugador1.nick + ":" + str(jugador1.puntos) + "#" + jugador2.nick + ":" + str(jugador2.puntos) + "#" + + self.csocket.send(bytes(respuesta,'UTF-8')) + +if __name__ == '__main__': + jugador1 = Jugador() + jugador2 = Jugador() + HOST = "" + PORT = 2000 + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind((HOST, PORT)) + print("Servidor iniciado. Esperando clientes...") + while True: + server.listen(1) + clientsock, clientAddress = server.accept() + t = ManejoCliente(clientAddress, clientsock) + t.start() \ No newline at end of file diff --git a/04.Servicios/P4.12-PPTClienteBasico.py b/04.Servicios/P4.12-PPTClienteBasico.py new file mode 100644 index 0000000..97092fd --- /dev/null +++ b/04.Servicios/P4.12-PPTClienteBasico.py @@ -0,0 +1,14 @@ +import socket + +SERVER = "127.0.0.1" +PORT = 2000 +client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +client.connect((SERVER, PORT)) +while True: + in_data = client.recv(1024) + print("From Server :" ,in_data.decode()) + out_data = input() + client.sendall(bytes(out_data,'UTF-8')) + if out_data=='fin': + break +client.close() \ No newline at end of file diff --git a/04.Servicios/P4.13-PPTClienteGUI.py b/04.Servicios/P4.13-PPTClienteGUI.py new file mode 100644 index 0000000..b9b0370 --- /dev/null +++ b/04.Servicios/P4.13-PPTClienteGUI.py @@ -0,0 +1,50 @@ +import socket +from tkinter import * + +def conectarServidor(): + global client + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((SERVER, PORT)) + lblInfo["text"]= client.recv(1024).decode() + +def inscribir (): + global client + client.sendall(bytes("#INSCRIBIR#"+entNombre.get() +"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def enviarJugada(jugada): + global client + client.sendall(bytes("#JUGADA#"+jugada+"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def consultaPuntos(): + global client + client.sendall(bytes("#PUNTUACION#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +#if __name__ == '__main__': +SERVER = "127.0.0.1" +PORT = 2000 +client = None +informacion ="" +fPPT = Tk() +fPPT.title("Piedra-Papel-Tijera") +fPPT.geometry("300x300") +fPPT.resizable(True, True) +lblInfo = Label(fPPT, text=informacion) +lblInfo.place(x=0,y=230) +btnConn = Button(fPPT, text = 'Conectar', command = conectarServidor) +btnConn.place(x = 150,y = 50) +entNombre = Entry(fPPT) +entNombre.place(x = 20,y=100) +btnInscribir = Button(fPPT, text = 'Inscribir', command = inscribir) +btnInscribir.place(x = 150,y = 100) +btnPiedra = Button(fPPT, text = 'piedra', command = lambda: enviarJugada("piedra")) +btnPiedra.place(x = 50,y = 150) +btnPapel = Button(fPPT, text = 'papel', command = lambda: enviarJugada("papel")) +btnPapel.place(x = 100,y = 150) +btnTijera = Button(fPPT, text = 'tijera', command = lambda: enviarJugada("tijera")) +btnTijera.place(x = 150,y = 150) +btnPuntos = Button(fPPT, text = 'Puntuación', command =consultaPuntos) +btnPuntos.place(x = 150,y = 200) +fPPT.mainloop() \ No newline at end of file diff --git a/04.Servicios/P4.13a-PPTClienteGUI-asincrono.py b/04.Servicios/P4.13a-PPTClienteGUI-asincrono.py new file mode 100644 index 0000000..61452ed --- /dev/null +++ b/04.Servicios/P4.13a-PPTClienteGUI-asincrono.py @@ -0,0 +1,55 @@ +import socket +import threading +from tkinter import * + +def conectarServidor(): + global client + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((SERVER, PORT)) + lblInfo["text"]= client.recv(1024).decode() + +def inscribir (): + global client + client.sendall(bytes("#INSCRIBIR#"+entNombre.get() +"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def hiloJugada(cli, jug): + cli.sendall(bytes("#JUGADA#"+jug+"#",'UTF-8')) + lblInfo["text"]= cli.recv(1024).decode() + +def enviarJugada(jugada): + global client + h = threading.Thread(target=hiloJugada, args=[client,jugada]) + h.start() + +def consultaPuntos(): + global client + client.sendall(bytes("#PUNTUACION#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +#if __name__ == '__main__': +SERVER = "127.0.0.1" +PORT = 2000 +client = None +informacion ="" +fPPT = Tk() +fPPT.title("Piedra-Papel-Tijera") +fPPT.geometry("300x300") +fPPT.resizable(True, True) +lblInfo = Label(fPPT, text=informacion) +lblInfo.place(x=0,y=230) +btnConn = Button(fPPT, text = 'Conectar', command = conectarServidor) +btnConn.place(x = 150,y = 50) +entNombre = Entry(fPPT) +entNombre.place(x = 20,y=100) +btnInscribir = Button(fPPT, text = 'Inscribir', command = inscribir) +btnInscribir.place(x = 150,y = 100) +btnPiedra = Button(fPPT, text = 'piedra', command = lambda: enviarJugada("piedra")) +btnPiedra.place(x = 50,y = 150) +btnPapel = Button(fPPT, text = 'papel', command = lambda: enviarJugada("papel")) +btnPapel.place(x = 100,y = 150) +btnTijera = Button(fPPT, text = 'tijera', command = lambda: enviarJugada("tijera")) +btnTijera.place(x = 150,y = 150) +btnPuntos = Button(fPPT, text = 'Puntuación', command =consultaPuntos) +btnPuntos.place(x = 150,y = 200) +fPPT.mainloop() \ No newline at end of file diff --git a/04.Servicios/P4.14-PPTServidorSondeo.py b/04.Servicios/P4.14-PPTServidorSondeo.py new file mode 100644 index 0000000..04b1a27 --- /dev/null +++ b/04.Servicios/P4.14-PPTServidorSondeo.py @@ -0,0 +1,160 @@ +import socket +import threading + +class Jugador: + def __init__(self): + self._nick = "" #nombre del jugador + self._addr = "" #dirección del cliente + self._jugada= "" #piedra/papel/tijera + self._puntos= 0 + + @property + def nick(self): + return self._nick + + @property + def addr(self): + return self._addr + + @property + def jugada(self): + return self._jugada + + @property + def puntos(self): + return self._puntos + + @property + def libre(self): + if self._nick == "": + return True + else: + return False + + @property + def sinJugar(self): + if self._jugada == "": + return True + else: + return False + + @nick.setter + def nick(self, nombre): + self._nick = nombre + + @addr.setter + def addr(self, nombre): + self._addr = nombre + + @jugada.setter + def jugada(self, nombre): + if nombre in ["piedra","papel","tijera",""]: + self._jugada = nombre + + @puntos.setter + def puntos(self, nombre): + self._puntos = nombre + + def puntuar(self): + print (self._puntos) + self._puntos += 1 + + def arbitrar(self,otroJugador): + #devuelve 0 si empate, -1 si gana otroJugador, 1 si gana el objeto actual + if ((self.jugada =="piedra" and otroJugador.jugada =="tijera") or + (self.jugada =="tijera" and otroJugador.jugada =="papel") or + (self.jugada =="papel" and otroJugador.jugada =="piedra")): + return 1 + elif ((otroJugador.jugada =="piedra" and self.jugada =="tijera") or + (otroJugador.jugada =="tijera" and self.jugada =="papel") or + (otroJugador.jugada =="papel" and self.jugada =="piedra")): + return -1 + else: + return 0 + +class ManejoCliente(threading.Thread): + def __init__(self,clientAddress,clientsocket): + threading.Thread.__init__(self) + self.csocket = clientsocket + self.cAddress = clientAddress + print ("Cliente conectado desde: ", self.cAddress) + + def run(self): + global numJugadaActual + print ("Escuchando a peticiones de cliente: ", self.cAddress) + #mensaje de bienvenida con el protocolo + bienvenida ="#INSCRIBIR#nombre#\n#JUGADA#{piedra|papel|tijera}#\n#RESULTADOJUGADA#numeroJugada#\n#PUNTUACION#" + self.csocket.send(bytes(bienvenida,'UTF-8')) + + while True: + data = self.csocket.recv(512).decode("utf_8") + print ("Enviado desde cliente:<",data,">") + subdatos = data.split("#") + respuesta="#OK#" + if subdatos[1] == "INSCRIBIR": + if jugador1.libre: + jugador1.nick = subdatos[2] + jugador1.addr = self.cAddress + elif jugador2.libre: + jugador2.nick = subdatos[2] + jugador2.addr = self.cAddress + else: + respuesta="#NOK#ya hay dos jugadores#" + elif subdatos[1] == "JUGADA": + #comprobra valor válido + if subdatos[2] not in ["piedra","papel","tijera"]: + respuesta="#NOK#valores válidos: piedra/papel/tijera#" + #comprobar autenticidad IP+puerto registrados + elif self.cAddress in [jugador1.addr, jugador2.addr]: + #estamos con el jugador 1 + if self.cAddress == jugador1.addr: + jugador1.jugada = subdatos[2] + #estamos con el jugador 2 + else: + jugador2.jugada = subdatos[2] + respuesta="#OK#" +str(numJugadaActual+1) +"#" + #comprobamos si podemos arbitrar jugada + with lock: + if (not jugador1.sinJugar and not jugador2.sinJugar): + #gana el 1 + if jugador1.arbitrar(jugador2)>0: + jugador1.puntuar() + historicoJug[len(historicoJug):] =["#OK#GANADOR:" + jugador1.nick + "#"] + #gana el 2 + elif jugador1.arbitrar(jugador2)<0: + jugador2.puntuar() + historicoJug[len(historicoJug):] =["#OK#GANADOR:" + jugador2.nick + "#"] + else: + historicoJug[len(historicoJug):] =["#OK#EMPATE#"] + jugador1.jugada ="" + jugador2.jugada ="" + numJugadaActual +=1 + #la ip+puerto del jugador no estaba en la partida + else: + respuesta="#NOK#el jugador no está en la partida#" + elif subdatos[1] == "RESULTADOJUGADA": + if int(subdatos[2])>0 and int(subdatos[2]) <= numJugadaActual: + respuesta =historicoJug[int(subdatos[2])-1] + else: + respuesta = "#NOK#número de jugada no válido#" + elif subdatos[1] == "PUNTUACION": + respuesta="#OK#" + jugador1.nick + ":" + str(jugador1.puntos) + "#" + jugador2.nick + ":" + str(jugador2.puntos) + "#" + + self.csocket.send(bytes(respuesta,'UTF-8')) + +if __name__ == '__main__': + jugador1 = Jugador() + jugador2 = Jugador() + numJugadaActual = 0 #almacena el número actual de jugada + historicoJug = [] #mensajes de ganador para cada jugada + lock = threading.Lock() + HOST = "" + PORT = 2000 + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.bind((HOST, PORT)) + print("Servidor iniciado. Esperando clientes...") + while True: + server.listen(1) + clientsock, clientAddress = server.accept() + t = ManejoCliente(clientAddress, clientsock) + t.start() \ No newline at end of file diff --git a/04.Servicios/P4.15-PPTClienteSondeoGUI.py b/04.Servicios/P4.15-PPTClienteSondeoGUI.py new file mode 100644 index 0000000..7f466ca --- /dev/null +++ b/04.Servicios/P4.15-PPTClienteSondeoGUI.py @@ -0,0 +1,59 @@ +import socket +from tkinter import * + +def conectarServidor(): + global client + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((SERVER, PORT)) + lblInfo["text"]= client.recv(1024).decode() + +def inscribir (): + global client + client.sendall(bytes("#INSCRIBIR#"+entNombre.get() +"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def enviarJugada(jugada): + global client + client.sendall(bytes("#JUGADA#"+jugada+"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def consultaPuntos(): + global client + client.sendall(bytes("#PUNTUACION#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def resultadoJugada(numJ): + global client + client.sendall(bytes("#RESULTADOJUGADA#"+numJ+"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +#if __name__ == '__main__': +SERVER = "127.0.0.1" +PORT = 2000 +client = None +informacion ="" +fPPT = Tk() +fPPT.title("Piedra-Papel-Tijera") +fPPT.geometry("300x300") +fPPT.resizable(True, True) +lblInfo = Label(fPPT, text=informacion) +lblInfo.place(x=0,y=230) +btnConn = Button(fPPT, text = 'Conectar', command = conectarServidor) +btnConn.place(x = 150,y = 50) +entNombre = Entry(fPPT) +entNombre.place(x = 20,y=100) +btnInscribir = Button(fPPT, text = 'Inscribir', command = inscribir) +btnInscribir.place(x = 150,y = 100) +btnPiedra = Button(fPPT, text = 'piedra', command = lambda: enviarJugada("piedra")) +btnPiedra.place(x = 50,y = 150) +btnPapel = Button(fPPT, text = 'papel', command = lambda: enviarJugada("papel")) +btnPapel.place(x = 100,y = 150) +btnTijera = Button(fPPT, text = 'tijera', command = lambda: enviarJugada("tijera")) +btnTijera.place(x = 150,y = 150) +spnNumJugada = Spinbox(fPPT, from_=1, to=99,increment=1) +spnNumJugada.place(x = 30,y=200, width=50) +btnResultado = Button(fPPT, text = 'Resultado', command = lambda: resultadoJugada(spnNumJugada.get())) +btnResultado.place(x = 80,y = 200) +btnPuntos = Button(fPPT, text = 'Puntuación', command =consultaPuntos) +btnPuntos.place(x = 150,y = 200) +fPPT.mainloop() \ No newline at end of file diff --git a/04.Servicios/P4.16-PPTServidorFullDuplex.py b/04.Servicios/P4.16-PPTServidorFullDuplex.py new file mode 100644 index 0000000..2265fde --- /dev/null +++ b/04.Servicios/P4.16-PPTServidorFullDuplex.py @@ -0,0 +1,180 @@ +import socket +import threading + +class Jugador: + def __init__(self): + self._nick = "" #nombre del jugador + self._addr = "" #dirección del cliente + self._jugada= "" #piedra/papel/tijera + self._puntos= 0 + self._puertoCallback = 0 + + @property + def nick(self): + return self._nick + + @property + def addr(self): + return self._addr + + @property + def jugada(self): + return self._jugada + + @property + def puntos(self): + return self._puntos + + @property + def puertoCallback(self): + return self._puertoCallback + + @property + def libre(self): + if self._nick == "": + return True + else: + return False + + @property + def sinJugar(self): + if self._jugada == "": + return True + else: + return False + + @nick.setter + def nick(self, nombre): + self._nick = nombre + + @addr.setter + def addr(self, nombre): + self._addr = nombre + + @jugada.setter + def jugada(self, nombre): + if nombre in ["piedra","papel","tijera",""]: + self._jugada = nombre + + @puntos.setter + def puntos(self, nombre): + self._puntos = nombre + + @puertoCallback.setter + def puertoCallback(self, puerto): + self._puertoCallback= int(puerto) + + def puntuar(self): + print (self._puntos) + self._puntos += 1 + + def arbitrar(self,otroJugador): + #devuelve 0 si empate, -1 si gana otroJugador, 1 si gana el objeto actual + if ((self.jugada =="piedra" and otroJugador.jugada =="tijera") or + (self.jugada =="tijera" and otroJugador.jugada =="papel") or + (self.jugada =="papel" and otroJugador.jugada =="piedra")): + return 1 + elif ((otroJugador.jugada =="piedra" and self.jugada =="tijera") or + (otroJugador.jugada =="tijera" and self.jugada =="papel") or + (otroJugador.jugada =="papel" and self.jugada =="piedra")): + return -1 + else: + return 0 + +class ManejoCliente(threading.Thread): + def __init__(self,clientAddress,clientsocket): + threading.Thread.__init__(self) + self.csocket = clientsocket + self.cAddress = clientAddress + self.puertoEscuchaCli = 0 + print ("Cliente conectado desde: ", self.cAddress) + + def run(self): + global numJugadaActual + print ("Escuchando a peticiones de cliente: ", self.cAddress) + #mensaje de bienvenida con el protocolo + bienvenida ="#INSCRIBIR#nombre#puertoEscucha#\n#JUGADA#{piedra|papel|tijera}#\n#PUNTUACION#" + self.csocket.send(bytes(bienvenida,'UTF-8')) + + while True: + data = self.csocket.recv(512).decode("utf_8") + print ("Enviado desde cliente:<",data,">") + subdatos = data.split("#") + respuesta="#OK#" + if subdatos[1] == "INSCRIBIR": + if jugador1.libre: + jugador1.nick = subdatos[2] + jugador1.addr = self.cAddress + jugador1.puertoCallback = subdatos[3] + elif jugador2.libre: + jugador2.nick = subdatos[2] + jugador2.addr = self.cAddress + jugador2.puertoCallback = subdatos[3] + else: + respuesta="#NOK#ya hay dos jugadores#" + elif subdatos[1] == "JUGADA": + #comprobra valor válido + if subdatos[2] not in ["piedra","papel","tijera"]: + respuesta="#NOK#valores válidos: piedra/papel/tijera#" + #comprobar autenticidad IP+puerto registrados + elif self.cAddress in [jugador1.addr, jugador2.addr]: + #estamos con el jugador 1 + if self.cAddress == jugador1.addr: + jugador1.jugada = subdatos[2] + #estamos con el jugador 2 + else: + jugador2.jugada = subdatos[2] + respuesta="#OK#" + #comprobamos si podemos arbitrar jugada + with lock: + if (not jugador1.sinJugar and not jugador2.sinJugar): + #gana el 1 + if jugador1.arbitrar(jugador2)>0: + jugador1.puntuar() + resultado ="#OK#GANADOR:" + jugador1.nick + "#" + #gana el 2 + elif jugador1.arbitrar(jugador2)<0: + jugador2.puntuar() + resultado ="#OK#GANADOR:" + jugador2.nick + "#" + else: + resultado ="#OK#EMPATE#" + jugador1.jugada ="" + jugador2.jugada ="" + t1 = threading.Thread(target=self.comunicaResultadoClientes, args=(resultado,1)) + t2 = threading.Thread(target=self.comunicaResultadoClientes, args=(resultado,2)) + t1.start() + t2.start() + + #la ip+puerto del jugador no estaba en la partida + else: + respuesta="#NOK#el jugador no está en la partida#" + elif subdatos[1] == "PUNTUACION": + respuesta="#OK#" + jugador1.nick + ":" + str(jugador1.puntos) + "#" + jugador2.nick + ":" + str(jugador2.puntos) + "#" + + self.csocket.send(bytes(respuesta,'UTF-8')) + + def comunicaResultadoClientes(self, resultado, numJugador): + + if numJugador == 1: + puerto = jugador1.puertoCallback + elif numJugador ==2: + puerto = jugador2.puertoCallback + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as cli: + cli.connect((self.cAddress[0], puerto)) + cli.sendall(bytes(resultado,'UTF-8')) + print ("resultado enviado a"+self.cAddress[0]+ ":"+cli.recv(1024).decode()) + +if __name__ == '__main__': + jugador1 = Jugador() + jugador2 = Jugador() + lock = threading.Lock() + HOST = "" + PORT = 2000 + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.bind((HOST, PORT)) + print("Servidor iniciado. Esperando clientes...") + while True: + server.listen(1) + clientsock, clientAddress = server.accept() + t = ManejoCliente(clientAddress, clientsock) + t.start() \ No newline at end of file diff --git a/04.Servicios/P4.17-PPTClienteFullDuplexGUI.py b/04.Servicios/P4.17-PPTClienteFullDuplexGUI.py new file mode 100644 index 0000000..95c602c --- /dev/null +++ b/04.Servicios/P4.17-PPTClienteFullDuplexGUI.py @@ -0,0 +1,80 @@ +import socket +import threading +from tkinter import * + +def conectarServidor(puertoCallback): + global client + client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client.connect((SERVER, PORT)) + lblInfo["text"]= client.recv(1024).decode() + t = threading.Thread(target=escucharRespuestas, args=(puertoCallback,)) + t.daemon=True + t.start() + + +def inscribir (puerto): + global client + client.sendall(bytes("#INSCRIBIR#"+entNombre.get() +"#"+puerto+"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def enviarJugada(jugada): + global client + client.sendall(bytes("#JUGADA#"+jugada+"#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def consultaPuntos(): + global client + client.sendall(bytes("#PUNTUACION#",'UTF-8')) + lblInfo["text"]= client.recv(1024).decode() + +def escucharRespuestas(puertoCallBack): + global informacion + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind((SERVER, puertoCallBack)) + print("Escucha en callback") + s.listen() + while True: + (cli,add) = s.accept() + with cli: + data = cli.recv(512).decode("utf_8") + print ("Enviado desde cliente:<",data,">") + informacion = data + #lblInfo["text"]=informacion esta línea no funcionaría ya que no se puede modificar el label desde este hilo + fPPT.event_generate("<>",when="tail") + cli.send(bytes("#OK#",'UTF-8')) + +def escribirResolucion(*args): + lblInfo["text"]=informacion + + +#if __name__ == '__main__': +SERVER = "127.0.0.1" +PORT = 2000 +client = None +informacion ="" +fPPT = Tk() +fPPT.title("Piedra-Papel-Tijera") +fPPT.geometry("300x300") +fPPT.resizable(True, True) +lblInfo = Label(fPPT, text=informacion ) +lblInfo.place(x=0,y=230) +lblPuerto = Label(fPPT, text="Puerto de escucha:") +lblPuerto.place(x=0,y=50) +entPuerto = Entry(fPPT,) +entPuerto.place(x = 110,y=50, width=30) +btnConn = Button(fPPT, text = 'Conectar', command = lambda: conectarServidor(int(entPuerto.get()))) +btnConn.place(x = 150,y = 50) +entNombre = Entry(fPPT) +entNombre.place(x = 20,y=100) +btnInscribir = Button(fPPT, text = 'Inscribir', command = lambda: inscribir(entPuerto.get())) +btnInscribir.place(x = 150,y = 100) +btnPiedra = Button(fPPT, text = 'piedra', command = lambda: enviarJugada("piedra")) +btnPiedra.place(x = 50,y = 150) +btnPapel = Button(fPPT, text = 'papel', command = lambda: enviarJugada("papel")) +btnPapel.place(x = 100,y = 150) +btnTijera = Button(fPPT, text = 'tijera', command = lambda: enviarJugada("tijera")) +btnTijera.place(x = 150,y = 150) +btnPuntos = Button(fPPT, text = 'Puntuación', command =consultaPuntos) +btnPuntos.place(x = 150,y = 200) +fPPT.bind("<>",escribirResolucion) +fPPT.mainloop() \ No newline at end of file diff --git a/04.Servicios/P4.18-Ftp-list.py b/04.Servicios/P4.18-Ftp-list.py new file mode 100644 index 0000000..6651c4f --- /dev/null +++ b/04.Servicios/P4.18-Ftp-list.py @@ -0,0 +1,25 @@ +import ftplib +#creadenciales FTP, la contraseña la cambian cada cierto tiempo +FTP_HOST = "ftp.dlptest.com" +FTP_USER = "dlpuser" +FTP_PASS = "rNrKYTX9g7z3RgJRmxWuGHbeu" + +def listCallback(line): + print(line) + +# conexión al servidor de FTP +ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS) +#forzar codificación UNICODE +ftp.encoding = "utf-8" +welcomeMessage = ftp.getwelcome() +print(welcomeMessage) + +#se puede obtener la respuesta a través de una función de callback +respMessage = ftp.retrlines("LIST", listCallback) +#también se puede obtener el resultado de forma directa +#respMessage = ftp.retrlines("LIST") +print ("-----------------------------") +print(respMessage) + +#cerrar la conexión +ftp.quit() \ No newline at end of file diff --git a/04.Servicios/P4.19-Ftp-Upload.py b/04.Servicios/P4.19-Ftp-Upload.py new file mode 100644 index 0000000..b2d3819 --- /dev/null +++ b/04.Servicios/P4.19-Ftp-Upload.py @@ -0,0 +1,29 @@ +import ftplib +#creadenciales FTP, la contraseña la cambian cada cierto tiempo +FTP_HOST = "ftp.dlptest.com" +FTP_USER = "dlpuser" +FTP_PASS = "rNrKYTX9g7z3RgJRmxWuGHbeu" + +def listCallback(line): + print(line) + +try: + # conexión al servidor de FTP + ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS) + #forzar codificación UNICODE + ftp.encoding = "utf-8" + welcomeMessage = ftp.getwelcome() + print(welcomeMessage) + + #fichero a subir + filename = "subido.txt" + with open(filename, "rb") as file: + # Usamos comando STOR para subirlo + ftp.storbinary(f"STOR {filename}", file) + # listamos el contenido para comprobar + ftp.dir(listCallback) + + #cerrar la conexión + ftp.quit() +except ftplib.all_errors as e: + errorcode_string = str(e).split(None, 1)[0] \ No newline at end of file diff --git a/04.Servicios/P4.20-Ftp-Download.py b/04.Servicios/P4.20-Ftp-Download.py new file mode 100644 index 0000000..64f8494 --- /dev/null +++ b/04.Servicios/P4.20-Ftp-Download.py @@ -0,0 +1,25 @@ +import ftplib +#creadenciales FTP, la contraseña la cambian cada cierto tiempo +FTP_HOST = "ftp.dlptest.com" +FTP_USER = "dlpuser" +FTP_PASS = "rNrKYTX9g7z3RgJRmxWuGHbeu" + +try: + # conexión al servidor de FTP + ftp = ftplib.FTP(FTP_HOST, FTP_USER, FTP_PASS) + #forzar codificación UNICODE + ftp.encoding = "utf-8" + welcomeMessage = ftp.getwelcome() + print(welcomeMessage) + + #bajamos el fichero subido anteriormente y lo renombramos a bajado.txt + fichero_enServidor = "subido.txt" + fichero_local = "bajado.txt" + with open(fichero_local, "wb") as file: + # usamos el comando RETR para descargar + ftp.retrbinary(f"RETR {fichero_enServidor}", file.write) + + #cerrar la conexión + ftp.quit() +except ftplib.all_errors as e: + errorcode_string = str(e).split(None, 1)[0] \ No newline at end of file diff --git a/04.Servicios/P4.21-email-smtp.py b/04.Servicios/P4.21-email-smtp.py new file mode 100644 index 0000000..a22527c --- /dev/null +++ b/04.Servicios/P4.21-email-smtp.py @@ -0,0 +1,32 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText + +body = '''Hola desde el manual de threads&sockets +Este es un correo simple para probar que podemos enviar emails a graves mediante python +Saludos +''' +#direccion, contraseña y destinatario +enviado_por = 'jlcarnerosobrino@gmail.com' +password = 'poner aqui la contraseña' +to = 'jlcarnerosobrino@gmail.com' + +#Establecimiento MIME +mensaje = MIMEMultipart() +mensaje['From'] = enviado_por +mensaje['To'] = to +mensaje['Subject'] = 'Prueba' + +#Cuerpo y adjuntos para el correo +mensaje.attach(MIMEText(body, 'plain')) +#Sesión SMTP para el envío del correo +try: + session = smtplib.SMTP('smtp.gmail.com', 587) #use gmail with port + session.starttls() #enable security + session.login(enviado_por, password) #login with mail_id and password + text = mensaje.as_string() + session.sendmail(enviado_por, to, text) + session.quit() + print('Mensaje enviado') +except: + print ('Algo fue incorrecto...') \ No newline at end of file diff --git a/04.Servicios/P4.22-email-imap.py b/04.Servicios/P4.22-email-imap.py new file mode 100644 index 0000000..c59e188 --- /dev/null +++ b/04.Servicios/P4.22-email-imap.py @@ -0,0 +1,36 @@ +import imaplib + +user = 'jlcarnerosobrino@gmail.com' +password = 'poner aqui la contraseña' +imap_url = 'imap.gmail.com' + + +def search(key, value, con): + result, data = con.search(None, key, '"{}"'.format(value)) + return data + +def get_emails(result_bytes): + msgs = [] + for num in result_bytes[0].split(): + typ, data = con.fetch(num, '(RFC822)') + msgs.append(data) + return msgs + +con = imaplib.IMAP4_SSL(imap_url) +con.login(user, password) +con.select('Inbox') +msgs = get_emails(search('FROM', 'jlcarnerosobrino@gmail.com', con)) + +for msg in msgs[::-1]: + for sent in msg: + if type(sent) is tuple: + content = str(sent[1], 'utf-8') + data = str(content) + try: + indexstart = data.find("ltr") + data2 = data[indexstart + 5: len(data)] + indexend = data2.find("") + print(data2[0: indexend]) + + except UnicodeEncodeError as e: + pass \ No newline at end of file diff --git a/04.Servicios/P4.23-Webbrowser.py b/04.Servicios/P4.23-Webbrowser.py new file mode 100644 index 0000000..b50d2ee --- /dev/null +++ b/04.Servicios/P4.23-Webbrowser.py @@ -0,0 +1,15 @@ +import webbrowser + +url1 = 'https://www.marcombo.com/' +url2 = 'https://www.amazon.es/' +url3 = 'https://www.google.com/' + +# Abrir la URL en una nueva pestaña si ya existe una ventana del navegador abierta +webbrowser.open_new_tab(url1) + +# Abrir la URL en una nueva ventana, si es posible +webbrowser.open_new(url2) + +edge_path = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" +webbrowser.register('edge', None, webbrowser.BackgroundBrowser(edge_path)) +webbrowser.get('edge').open_new(url3) diff --git a/04.Servicios/P4.24-leerUrl.py b/04.Servicios/P4.24-leerUrl.py new file mode 100644 index 0000000..f78741d --- /dev/null +++ b/04.Servicios/P4.24-leerUrl.py @@ -0,0 +1,19 @@ +from urllib import request + +response = request.urlopen('http://www.python.org') + +print('RESPONSE:', response) +print('CODIGO HTTP: ', response.getcode()) +print('URL :', response.geturl()) + +headers = response.info() +print('DATE :', headers['date']) +print('HEADERS :') +print('---------') +print(headers) + +print('BODY :') +print('---------') +for _ in range (20): + linea = response.readline().decode('utf-8') + print(linea) \ No newline at end of file diff --git a/04.Servicios/P4.25-ApiREST-OpenWeather.py b/04.Servicios/P4.25-ApiREST-OpenWeather.py new file mode 100644 index 0000000..810e937 --- /dev/null +++ b/04.Servicios/P4.25-ApiREST-OpenWeather.py @@ -0,0 +1,19 @@ +from urllib import request +import json +ciudad="Ourense" +api_key = "obtener una api-key!!!!!" +unidades ="metric" +url = "https://api.openweathermap.org/data/2.5/weather?q=%s&units=%s&appid=%s" % (ciudad, unidades, api_key) +#print (url) +response = request.urlopen(url) +http_body = response.readline().decode('utf-8') +print (http_body) +#codificar la respuesta a json +data = json.loads(http_body) +#print(data) +#acceso al bloque main +main = data["main"] +#acceso a temperatura +temperatura = main["temp"] +#data = response.json()["main"]["temp"] +print ("La temperatura de " + ciudad + "es:" + str(temperatura)) \ No newline at end of file diff --git a/04.Servicios/P4.26-ConsumirAPIExterna.py b/04.Servicios/P4.26-ConsumirAPIExterna.py new file mode 100644 index 0000000..04d92c7 --- /dev/null +++ b/04.Servicios/P4.26-ConsumirAPIExterna.py @@ -0,0 +1,16 @@ +from urllib import parse +from urllib import request +import json + +#diccionario de datos a enviar +datos_consulta = {'cod-persona':245} +#codificación de los datos +encoded_args = parse.urlencode(datos_consulta).encode('utf-8') +#endpoint de nuestra petición +url = 'https://psp-api.free.beeceptor.com/persona' +#composición de la petición con la url y los datos POST a insertar en el header +response = request.urlopen(url, encoded_args).read().decode('utf-8') +#print(response) +datos = json.loads(response) +print ("Nombre: ", datos["nombre"]) +print ("Edad: ", datos["edad"]) diff --git a/04.Servicios/bajado.txt b/04.Servicios/bajado.txt new file mode 100644 index 0000000..d7d0960 --- /dev/null +++ b/04.Servicios/bajado.txt @@ -0,0 +1 @@ +hola, estoy haciendo pruebas con FTP \ No newline at end of file diff --git a/04.Servicios/subido.txt b/04.Servicios/subido.txt new file mode 100644 index 0000000..d7d0960 --- /dev/null +++ b/04.Servicios/subido.txt @@ -0,0 +1 @@ +hola, estoy haciendo pruebas con FTP \ No newline at end of file diff --git a/05.Seguridad/Datos_Encriptados.bin b/05.Seguridad/Datos_Encriptados.bin new file mode 100644 index 0000000000000000000000000000000000000000..21c1f8b5be8ee01e4abcf207f662b083065f8282 GIT binary patch literal 369 zcmV-%0gnDr;*L{ek_oC1_m1vZeqzhxs~?uQEs?m* z_7NvpdGEL;es2`vgYKrxS{8s=Oe5d+GN?k=zdI>^v4iq8G;op6L+Y^14X5YpX)q0; zZ=J-AVn|xP5vf^KoTjP>l9}Z9Kaj_qsU1cg>mf(o)P`%SUuO2#WC(cKXF7G+`bwKl z+2=%OT_e2xQnnBaxigrn0#xt6{3B)nUkB;$Q#0VOiP8{I5-aC_y8^-@aggdDmUu`E za!l!!V&k*zhD9#h6l)oFTi_VO2=|&fEU0K{kSocAF{VaE%P*aBlsUud3XGGQzg3)w zt`pF4z>Hk0b_vE%HFn|-O&(bMKt#}tI@$e~r!mYP&hq%+n|_7MOoIba?p}5MwoqEY P|E?gcX@u4_;oc3i<_o)0 literal 0 HcmV?d00001 diff --git a/05.Seguridad/P5.01-Hash-MD5.py b/05.Seguridad/P5.01-Hash-MD5.py new file mode 100644 index 0000000..0b67d0f --- /dev/null +++ b/05.Seguridad/P5.01-Hash-MD5.py @@ -0,0 +1,13 @@ +import hashlib + +#hash co md5 de un texto +Texto = "Texto de prueba".encode("utf-8") +HashCode= hashlib.md5(Texto).hexdigest() +print("El hash de %s es: %s" % (Texto , HashCode)) + +#hash con md5 de un fichero +filename = input("Nombre de fichero: ") +with open(filename,"rb") as f: + bytes = f.read() + HashCode = hashlib.md5(bytes).hexdigest(); + print("El hash del fichero: %s es:\n%s" % (filename ,HashCode)) \ No newline at end of file diff --git a/05.Seguridad/P5.02-CifradoCesar.py b/05.Seguridad/P5.02-CifradoCesar.py new file mode 100644 index 0000000..1e0576f --- /dev/null +++ b/05.Seguridad/P5.02-CifradoCesar.py @@ -0,0 +1,28 @@ +alfabeto = 'ABCDEFGHIJKLMNÑOPQRSTUVWXYZ0123456789' +alfabetoCifrado = 'KLMNÑOPQRSTUVWXYZ0123456789ABCDEFGHIJ' + +def cifrarCesar(men): + mensajeCifrado= "" + men = men.upper() + for caracter in men: + if caracter in alfabeto: + index = alfabeto.index(caracter) + mensajeCifrado += alfabetoCifrado[index] + else: #si el carácter no existeen el alfabeto + mensajeCifrado += caracter + return mensajeCifrado + +def descifrarCesar (menCif): + mensajeDescifrado= "" + for caracter in menCif: + if caracter in alfabeto: + index = alfabetoCifrado.index(caracter) + mensajeDescifrado += alfabeto[index] + else: #si el carácter no existeen el alfabeto + mensajeDescifrado += caracter + return (mensajeDescifrado) + +print (cifrarCesar('En un lugar de la Mancha, vivía!')) +print (descifrarCesar('ÑW 4W U4PK1 NÑ UK VKWMQK, 5R5ÍK!')) + +assert (cifrarCesar("I love you!")=="R UY5Ñ 8Y4!") \ No newline at end of file diff --git a/05.Seguridad/P5.03-DES-key-iv.py b/05.Seguridad/P5.03-DES-key-iv.py new file mode 100644 index 0000000..8c6441c --- /dev/null +++ b/05.Seguridad/P5.03-DES-key-iv.py @@ -0,0 +1,28 @@ +from Crypto.Cipher import DES +import base64 +import os + +mensajeOriginal = "Visita GALICIA: el paraíso".encode("utf-8") +print ("Mensaja original:", mensajeOriginal.decode("utf-8")) + +key = b"abc123.." #establecemos una clave +iv = os.urandom(8) #generamos aleatoriamente un iv + +print (iv) + +#instanciamos un nuevo objeto DES +cipher = DES.new(key, DES.MODE_OFB,iv=iv) +#ciframos los datos +bytesCifrados = cipher.encrypt(mensajeOriginal) +print ("Bytes cifrados: ", bytesCifrados) +#para imprimir una mejor representación +mensajeCifrado = base64.b64encode(bytesCifrados).decode("utf-8") +print ("Mensaje Cifrado:", mensajeCifrado) + +#es necesario un nuevo objeto para descifrar +cipher = DES.new(key, DES.MODE_OFB,iv = iv) +#desciframos usando la misma key e iv +mensajeDescifrado = cipher.decrypt(bytesCifrados) +print ("Mensaje: ", mensajeDescifrado.decode()) + +assert (mensajeOriginal == mensajeDescifrado) \ No newline at end of file diff --git a/05.Seguridad/P5.04-3DES.py b/05.Seguridad/P5.04-3DES.py new file mode 100644 index 0000000..1912aed --- /dev/null +++ b/05.Seguridad/P5.04-3DES.py @@ -0,0 +1,32 @@ +from Crypto.Cipher import DES3 +from Crypto.Random import get_random_bytes +import base64 +import os + +mensajeOriginal = "Visita GALICIA: el paraíso".encode("utf-8") +print ("Mensaja original:", mensajeOriginal.decode("utf-8")) + +while True: + try: + key = DES3.adjust_key_parity(get_random_bytes(24)) + break + except ValueError: + pass +iv = os.urandom(8) #generamos aleatoriamente un iv + +#instanciamos un nuevo objeto DES +cipher = DES3.new(key, DES3.MODE_CFB,iv=iv) +#ciframos los datos +bytesCifrados = cipher.encrypt(mensajeOriginal) +print ("Bytes cifrados: ", bytesCifrados) +#para imprimir una mejor representación +mensajeCifrado = base64.b64encode(bytesCifrados).decode("utf-8") +print ("Mensaje Cifrado:", mensajeCifrado) + +#es necesario un nuevo objeto para descifrar +cipher = DES3.new(key, DES3.MODE_CFB,iv = iv) +#desciframos usando la misma key e iv +mensajeDescifrado = cipher.decrypt(bytesCifrados) +print ("Mensaje: ", mensajeDescifrado.decode()) + +assert (mensajeOriginal == mensajeDescifrado) \ No newline at end of file diff --git a/05.Seguridad/P5.05-DES3-file.py b/05.Seguridad/P5.05-DES3-file.py new file mode 100644 index 0000000..cc533de --- /dev/null +++ b/05.Seguridad/P5.05-DES3-file.py @@ -0,0 +1,29 @@ +from Crypto.Cipher import DES3 +from hashlib import md5 + +def encriptar (fileOrigen, fileDestino, key): + cipher = DES3.new(key, DES3.MODE_EAX, nonce=b'0') + with open(fileOrigen, 'rb') as input_file: + file_bytes = input_file.read() + enc_file_bytes = cipher.encrypt(file_bytes) + + with open(fileDestino, 'wb') as output_file: + output_file.write(enc_file_bytes) + +def desencriptar (fileOrigen, fileDestino, key): + cipher = DES3.new(key, DES3.MODE_EAX, nonce=b'0') + with open(fileOrigen, 'rb') as input_file: + file_bytes = input_file.read() + dec_file_bytes = cipher.decrypt(file_bytes) + + with open(fileDestino, 'wb') as output_file: + output_file.write(dec_file_bytes) + + +key = "abc123." + +key_hash = md5(key.encode('ascii')).digest() # 16-byte key +tdes_key = DES3.adjust_key_parity(key_hash) + +encriptar("textoPlano.txt","textoEncriptado.txt",tdes_key) +desencriptar("textoEncriptado.txt", "textoDesencriptado.txt",tdes_key) \ No newline at end of file diff --git a/05.Seguridad/P5.06-AES.py b/05.Seguridad/P5.06-AES.py new file mode 100644 index 0000000..73b7d79 --- /dev/null +++ b/05.Seguridad/P5.06-AES.py @@ -0,0 +1,21 @@ +from Crypto.Cipher import AES + +mensajeOriginal = b"Hola, algoritmo AES" +key = b'Clave de 16 Byte' +print ("Original: ", mensajeOriginal) + +#cifrar +cipher = AES.new(key, AES.MODE_EAX) #sin iv +mensajeCifrado, tag = cipher.encrypt_and_digest(mensajeOriginal) +print ("Cifrado: ", mensajeCifrado) + +#descifrar +cipher = AES.new(key, AES.MODE_EAX, nonce=cipher.nonce) +mensajeDescifrado = cipher.decrypt(mensajeCifrado) +try: + cipher.verify(tag) + print("Descifrado: ", mensajeDescifrado) +except ValueError: + print("Clave incorrecta o mensaje corrupto") + +assert (mensajeDescifrado == mensajeDescifrado) \ No newline at end of file diff --git a/05.Seguridad/P5.07-AES-iv.py b/05.Seguridad/P5.07-AES-iv.py new file mode 100644 index 0000000..44de652 --- /dev/null +++ b/05.Seguridad/P5.07-AES-iv.py @@ -0,0 +1,33 @@ +from Crypto.Cipher import AES +from Crypto.Random import get_random_bytes + +key = get_random_bytes(32) # Use a stored / generated key +MensajeOriginal = 'Pureba de encriptación con AES+key+iv' # This is your data +print ("Mensaje original: ", MensajeOriginal) + +#Convertir un string a un objeto de bytes codificado UNICODE +data = MensajeOriginal.encode('utf-8') + +# Encriptación +cipher_encrypt = AES.new(key, AES.MODE_CFB) +BytesEncriptados = cipher_encrypt.encrypt(data) + +# Nuestro datos y vector de inicialización +iv = cipher_encrypt.iv +MensajeEncriptado = BytesEncriptados +print ("Mensaje encriptado: ", MensajeEncriptado) +print ("Vector de inicialización: ", iv) + + +# Desencriptación +cipher_decrypt = AES.new(key, AES.MODE_CFB, iv=iv) +BytesDesncriptados = cipher_decrypt.decrypt(MensajeEncriptado) + +# Conversión de bytes a string +MensajeDesencriptado = BytesDesncriptados.decode('utf-8') +print ("Mensaje desencriptado: ", MensajeDesencriptado) + +#probamos coindidencia original-encriptado-desencriptado +assert MensajeOriginal == MensajeDesencriptado, 'El mensaje original no coincide con la encrptación-desencriptación' + +#https://nitratine.net/blog/post/python-encryption-and-decryption-with-pycryptodome/ \ No newline at end of file diff --git a/05.Seguridad/P5.08-Generar-clave-RSA.py b/05.Seguridad/P5.08-Generar-clave-RSA.py new file mode 100644 index 0000000..28a548a --- /dev/null +++ b/05.Seguridad/P5.08-Generar-clave-RSA.py @@ -0,0 +1,23 @@ +from Crypto.PublicKey import RSA + +codigoClave = "Unguessable" #conraseña +key = RSA.generate(2048) +#obtener clave privada +encrypted_key = key.export_key(passphrase=codigoClave, pkcs=8, + protection="scryptAndAES128-CBC") + +file_out = open("rsa_key_privada.bin", "wb") +file_out.write(encrypted_key) +file_out.close() + +print ("Clave privada:\n",encrypted_key) + +#obtener clave pública +print("Clave pública:\n",key.publickey().export_key()) + +#obtener la clave pública en base a la lectura del fichero de clave privada +encoded_key = open("rsa_key_privada.bin", "rb").read() +key = RSA.import_key(encoded_key, passphrase=codigoClave) + +print("Clave pública:\n",key.publickey().export_key()) +print(key) \ No newline at end of file diff --git a/05.Seguridad/P5.09-Generar-par-claves-RSA.py b/05.Seguridad/P5.09-Generar-par-claves-RSA.py new file mode 100644 index 0000000..a0582e9 --- /dev/null +++ b/05.Seguridad/P5.09-Generar-par-claves-RSA.py @@ -0,0 +1,12 @@ +from Crypto.PublicKey import RSA + +key = RSA.generate(2048) +private_key = key.export_key() +file_out = open("privada_usuario_A.pem", "wb") +file_out.write(private_key) +file_out.close() + +public_key = key.publickey().export_key() +file_out = open("publica_usuario_A.pem", "wb") +file_out.write(public_key) +file_out.close() \ No newline at end of file diff --git a/05.Seguridad/P5.10-RSA-encriptar.py b/05.Seguridad/P5.10-RSA-encriptar.py new file mode 100644 index 0000000..94354ac --- /dev/null +++ b/05.Seguridad/P5.10-RSA-encriptar.py @@ -0,0 +1,19 @@ +from Crypto.PublicKey import RSA +from Crypto.Random import get_random_bytes +from Crypto.Cipher import AES, PKCS1_OAEP + +data = "La criptografía a través en Python a través de <> es consistente".encode("utf-8") +file_out = open("Datos_Encriptados.bin", "wb") + +recipient_key = RSA.import_key(open("publica_usuario_A.pem").read()) +session_key = get_random_bytes(16) + +# Encriptar la sesión con la clave pública del usuario_A +cipher_rsa = PKCS1_OAEP.new(recipient_key) +enc_session_key = cipher_rsa.encrypt(session_key) + +# Encriptar los datos con la sesión de AES +cipher_aes = AES.new(session_key, AES.MODE_EAX) +ciphertext, tag = cipher_aes.encrypt_and_digest(data) +[ file_out.write(x) for x in (enc_session_key, cipher_aes.nonce, tag, ciphertext) ] +file_out.close() \ No newline at end of file diff --git a/05.Seguridad/P5.11-RSA-desencriptar.py b/05.Seguridad/P5.11-RSA-desencriptar.py new file mode 100644 index 0000000..15b04bc --- /dev/null +++ b/05.Seguridad/P5.11-RSA-desencriptar.py @@ -0,0 +1,18 @@ +from Crypto.PublicKey import RSA +from Crypto.Cipher import AES, PKCS1_OAEP + +file_in = open("Datos_Encriptados.bin", "rb") + +private_key = RSA.import_key(open("privada_usuario_A.pem").read()) + +enc_session_key, nonce, tag, ciphertext = \ + [ file_in.read(x) for x in (private_key.size_in_bytes(), 16, 16, -1) ] + +# Desencriptar la desión RSA con la clave privada del usuario_A +cipher_rsa = PKCS1_OAEP.new(private_key) +session_key = cipher_rsa.decrypt(enc_session_key) + +# Desencptar los datos ocn la sesión AES +cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce) +data = cipher_aes.decrypt_and_verify(ciphertext, tag) +print(data.decode("utf-8")) \ No newline at end of file diff --git a/05.Seguridad/P5.12-GenerarPar-DSA.py b/05.Seguridad/P5.12-GenerarPar-DSA.py new file mode 100644 index 0000000..e0e3ad4 --- /dev/null +++ b/05.Seguridad/P5.12-GenerarPar-DSA.py @@ -0,0 +1,15 @@ +from Crypto.PublicKey import DSA + +# Creación de clave DSA +key = DSA.generate(2048) +f = open("public_key.pem", "wb") +#grabación clave pública +f.write(key.publickey().export_key()) +f.close() +print ("Clave pública:\n", key.public_key().export_key()) + +f = open("private_key.pem", "wb") +#grabación clave privada +f.write(key.export_key()) +f.close() +print ("Clave privada:\n", key.export_key()) \ No newline at end of file diff --git a/05.Seguridad/P5.13-DSA-firmar.py b/05.Seguridad/P5.13-DSA-firmar.py new file mode 100644 index 0000000..9e97966 --- /dev/null +++ b/05.Seguridad/P5.13-DSA-firmar.py @@ -0,0 +1,21 @@ +from Crypto.PublicKey import DSA +from Crypto.Signature import DSS +from Crypto.Hash import SHA256 +import json + +f = open("private_key_firma.pem", "r") +key =DSA.import_key(f.read()) + +# Firmar un mensaje con la clave privada +mensaje = b"Comprobamos quien firma este mensaje" +hash_obj = SHA256.new(mensaje) +firmador = DSS.new(key, 'fips-186-3') +firma = firmador.sign(hash_obj) + +#creamos un fichero JSON con el texto y la firma +#lo codificamos en dos caracteres hexadecimales cada byte +mensajeFirmado = json.dumps({'mensaje':mensaje.hex(), 'firma':firma.hex()}) +#print (mensajeFirmado) +f = open("mensajefirmado.txt", "w") +f.write(mensajeFirmado) +f.close() \ No newline at end of file diff --git a/05.Seguridad/P5.14-DSA-verificar.py b/05.Seguridad/P5.14-DSA-verificar.py new file mode 100644 index 0000000..ff581d7 --- /dev/null +++ b/05.Seguridad/P5.14-DSA-verificar.py @@ -0,0 +1,26 @@ +from Crypto.PublicKey import DSA +from Crypto.Signature import DSS +from Crypto.Hash import SHA256 +import json + +#abrimos un fichero JSON con el texto y la firma +#viene codificamos en dos caracteres hexadecimales cada byte +f = open("mensajefirmado.txt", "r") +mensajeFirmado =f.read() +print (mensajeFirmado) +mensajeRecibido = json.loads(mensajeFirmado) + +#creamos un verificador con la firma leida en el fichero JSON +#usamos la clave pública del remitente +f = open("public_key_firma.pem", "r") +hash_obj = SHA256.new(bytes.fromhex(mensajeRecibido["mensaje"])) +pub_key = DSA.import_key(f.read()) +verificador = DSS.new(pub_key, 'fips-186-3') + +# Verificar la autenticidad del mensaje recibido +# en realidad lo hacemos del hash de todo el mensaje +try: + verificador.verify(hash_obj, bytes.fromhex(mensajeRecibido["firma"])) + print ("El mensaje es AUTENTICO") +except ValueError: + print ("Este mensaje no ha sido firmado de forma válida") \ No newline at end of file diff --git a/05.Seguridad/P5.15-SocketServer-SSL.py b/05.Seguridad/P5.15-SocketServer-SSL.py new file mode 100644 index 0000000..4e31de5 --- /dev/null +++ b/05.Seguridad/P5.15-SocketServer-SSL.py @@ -0,0 +1,24 @@ +import socket +import ssl + +HOST ='localhost' +PORT = 4444 + +context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) +context.load_cert_chain('cert-ssl\certificado.pem', 'cert-ssl\clave-privada.key') + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock: + sock.bind((HOST, PORT)) + sock.listen(5) + print (f"Servidor correiendo en: {HOST},{PORT}") + #solapaar el socket sobre SSL + with context.wrap_socket(sock, server_side=True) as ssock: + while True: + conn, addr = ssock.accept() + print (f"Conexión desde: {addr}") + #enviar datos + data = "Bienvenido al servidor SSL ("+HOST+":"+str(PORT)+")" + conn.sendall (data.encode("utf-8")) + #recibir datos + data =conn.recv() + print (data) \ No newline at end of file diff --git a/05.Seguridad/P5.16-SocketCliente-SSL.py b/05.Seguridad/P5.16-SocketCliente-SSL.py new file mode 100644 index 0000000..952779e --- /dev/null +++ b/05.Seguridad/P5.16-SocketCliente-SSL.py @@ -0,0 +1,20 @@ +import socket +import ssl + +HOST = 'localhost' +PORT = 4444 + +context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) +context.load_verify_locations('cert-ssl\certificado.pem') + +with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock: + #solapaar el socket sobre SSL + with context.wrap_socket(sock, server_hostname=HOST) as ssock: + print(ssock.version()) + ssock.connect((HOST, PORT)) + print ("Conexión con éxito") + #recibir datos + data = ssock.recv(1024) + print(f"Recibido: {data!r}") + #enviar datos + ssock.sendall("hola, soy un cliente SSL".encode("utf-8")) \ No newline at end of file diff --git a/05.Seguridad/cert-ssl/certificado.pem b/05.Seguridad/cert-ssl/certificado.pem new file mode 100644 index 0000000..3a8ba88 --- /dev/null +++ b/05.Seguridad/cert-ssl/certificado.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCzCCAfOgAwIBAgIUe0slvpJCoeeUc7PtK+dCV1sWsyQwDQYJKoZIhvcNAQEF +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MCAXDTIyMDMyMzA5MDE1OFoYDzIxMjIw +MjI3MDkwMTU4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCzYif3Rjbi3W4Hg1d5NmDYgZZLBOE6RUkaM2q8sMRl +/FGORmfjTslHTT4CqCTjfKNAXjgxx2zpDrzCuSJiI1Nxv2QIET9jqY2JzAvLrvci +nSe6fdWEnhDabXQDWpD/jAS5KCOakFulN5IIgFNAq6mYXo5/pWZbFH0Tt2cZW9wX +EjaVak+Dzcm6DNJs3o5itcLY85QeaZ9lI48LUyTd9TP5KewmwANLvHl1ycc9mo9C +xOEZwTgD7OKp6ZCRnEshsX75UCwCAVapXc6UP1Yfv2TFxHDLjVnb8M23qIAlL8Jw +vP0i21Mk2xjvxEimffSqRiNCMb24ZpIDATs4W6Mtf44hAgMBAAGjUzBRMB0GA1Ud +DgQWBBSMp0nB9rQ+o+MzzTjAT2bR1XDQ4zAfBgNVHSMEGDAWgBSMp0nB9rQ+o+Mz +zTjAT2bR1XDQ4zAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBN +3RmNFQE41gty/c3Fmpha/YZ23Cn9QuMlwHhArT4ccXXmu/2xs0DCF4cUTZiiaqw9 +lvHG19G9UWwq+U6I1LGlTpoEuSrHWr22u+WhybVCJySf6tuTyUQMvc+yerNwMQAI +2gIg8mxZftqFRslcglS/tkklWaKy4MY4uQZgaZNhOqwzv/FehlydAhP1IyI6icSq +gFQvOZjcqBOeRWUiUQAvpTkmmTSeH0doY/zrZSClOOgoNR27PCzyQVAmQY/UMoYY +QW0vjZ7mWvbW5+7N9NtFgBHhYDdX40J50yc2DBjBdLMXt9lKP+Uk2b84t1mpm3G6 +lcimjfvCyoyF/l6V5CkF +-----END CERTIFICATE----- diff --git a/05.Seguridad/cert-ssl/clave-privada.key b/05.Seguridad/cert-ssl/clave-privada.key new file mode 100644 index 0000000..498e17f --- /dev/null +++ b/05.Seguridad/cert-ssl/clave-privada.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCzYif3Rjbi3W4H +g1d5NmDYgZZLBOE6RUkaM2q8sMRl/FGORmfjTslHTT4CqCTjfKNAXjgxx2zpDrzC +uSJiI1Nxv2QIET9jqY2JzAvLrvcinSe6fdWEnhDabXQDWpD/jAS5KCOakFulN5II +gFNAq6mYXo5/pWZbFH0Tt2cZW9wXEjaVak+Dzcm6DNJs3o5itcLY85QeaZ9lI48L +UyTd9TP5KewmwANLvHl1ycc9mo9CxOEZwTgD7OKp6ZCRnEshsX75UCwCAVapXc6U +P1Yfv2TFxHDLjVnb8M23qIAlL8JwvP0i21Mk2xjvxEimffSqRiNCMb24ZpIDATs4 +W6Mtf44hAgMBAAECggEAALEQy7K/gIwkC1JLHogRaOF6kDK5D1LNSJojTuDjaUoA +0nX1uP/CY1VRKDx3IUfQJowQLpAXiK10/z1yx4pOwtqWStR0jE1eA269lAA7Q+QC +gsPHnys8MLbehbcaFZLy6/Ldd2qaRkqVsHm9zSSD1XND4tNjHa1Ra3BYoFWDWZbo +Ov9GXus12oDTHQr/gRqtjJfIxNOotPnsltVgcfmY+4DXRTsUU6UPIju1szFy/Mxf +AryPmENJKFgE2yl0FSdtsmOi5cCF421hVzditevQfP8u46K2h/rxy3rxuOZu5AVN +wOkOyR3PmfGVrjkQ3US+FnpNNAI+6qFAG5bTvyvN0wKBgQDow3TacfuouGCRqt64 +YnHDag/1TqvtI0Iwg7qFaKCJrFPjVvUDy8KAV+S8IPzIwWVFAuHGkVobC0gbqRAE +m3QNRYpQY2Wo+GniMPY+7/uTaU+NuhxwIzzj7hgqz1SjNVEtNajO10Gcfm2op9yN +f5DYv00Bot6pEPvLXlQfl3wLCwKBgQDFSoJg8I4yD+TWT30IKe9UgV9nuYvaRpq0 +nku23pjzrPgXqDd3x281KHtiQU2rMxfBdsi9iYSye2GtwVmkZKDgZsT7QdVcZc1z ++36FEpGnM7C+e6uuXr4X0cYZZ2CsIs0WZXzMkkgUl8kqTi9u34DWEAqce8s+7zXN +ywnJLA5nAwKBgQDOO5gGkKWMuUh+6GmL71Wi8g+PpxP3+ZyExcJ2v9w1/2UYcgyH +P3tnIfk9ovC2o3wp6ELJIDI48gcC0wmpO19Y/vts/JSvYOLYEc+stg8ubkmZZoQZ +627g+S2aiLcSIIR7TSbzlY/Bq9dXbtug150sHluJjphALhca+soIb7ztPwKBgBny +SaFMIbdNxc+1loD7WuFnPk/a5BypynDUnKqJLd5mMh6SXfEfxm1cTJXIdtl8F7S2 +1YGv00bR2S/LzOlE3q+EdIWCy/eh39pQCfygS42My8LRauu8xA1H5mCy6tDYptY6 +NKaG2nny2F7691wCguQkKfEYistVFGNjP384jxBXAoGAfbVG0D/GOL8j0NCIkU0g +I2I1VVVd3qwoSx0KudB67VXXuoeJ2EPJqrBLIm9q1j2l+aNmTw2YZ4/UY5Mbs4+F +898BfQgOUZrihpEcEeZf+2mkASKtyF4JAB0/ckDuo690rqlsfwKQMsniAujPxA6+ +nx7o+irdpcTTeY3K5n3fMDM= +-----END PRIVATE KEY----- diff --git a/05.Seguridad/mensajefirmado.txt b/05.Seguridad/mensajefirmado.txt new file mode 100644 index 0000000..6bf6f50 --- /dev/null +++ b/05.Seguridad/mensajefirmado.txt @@ -0,0 +1 @@ +{"mensaje": "436f6d70726f62616d6f7320717569656e206669726d612065737465206d656e73616a65", "firma": "6cb45bb9de23b130c4722ab3b3cd26c90010187c27aba71aba415e7167db477df6a4fbbbc01dc1970567dbc999ae184d9953bc296e89c67d"} \ No newline at end of file diff --git a/05.Seguridad/privada_usuario_A.pem b/05.Seguridad/privada_usuario_A.pem new file mode 100644 index 0000000..d82eee5 --- /dev/null +++ b/05.Seguridad/privada_usuario_A.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyB4APiFRaxPZ03EDLbxDuihdEa9Bj8gxJHF3OjSDtefRNYDI +M3wf4gSzzNINyHwVqogPTOo/fBoaV4eMIUF+odkAbTuytrSqZc9nYnwBUy3nDiiT +oQ/s1EM1vdPJ5aEl3onfAeRcR+xDonuK+MGwQcBVQEZQK393MPJ0tNyHjfoUZjmz +PaM7OTD6vegcmEx2bD3watNuQ0cibwceQa9cmlZklQXv12wvDRKIbl1eJAsHY0Yw +yXLZtDfNVMh8+1hmCLNiPefQnDU89AwarZiXmFo41e1wfkWuX9G/kga7Ms0F0LRg +/ZYBWhK7eByooTF10ZFwdoyqJpGXYwos6geGbQIDAQABAoIBABePLKEj5AVyp1TU +u7aRMPoEEXzpAJNwUpTDRPGVyCHMwl0Fpcwl400FF7PX0OaW8SgL750zALlYC1zd +qNorb0CXnwy9F/uZhmwJAFdgWPmVcFvC3Cp8iPmuVe/ctKqzj5VE7vu2ikSvZIEH +AWPqzZjYD1FLVdnhHkOlb6bxRYYpws0uhKF8zDw+2lXeydKl+AxOMFQjQsHpFAHA +1335p6F7fRpSINC2+iajy6yV955yM0AQh83izWIRiHJNYHF68u9oWg+2xUZCq1NY +w690yjVAJYqdQ5uFYnEMGjMkjH9lV38AHlP6ixHuNSA996bli4RN87pnpduZS8bg +R7zic4ECgYEAyyqtQPlkUa3hq/OWTcj93NWBJBOZ91s7QBSkGBRb8XG0CsrFFloJ +c6eva/kYFs4Rz4Pna8GnaIaxUH1kyG4oMOJ/mkwdMEpKmt8y45jcm1YRIflCb9yT +XtBb5fJwuBXyG0oWyshyWsC/GI+jrVm3nGlGu58OC4HwtkKfJEr0/K0CgYEA/ChP +awQ+dPtFzI0yrbtBs/lwpU9smSi4OUHJ9PI1/ASUh0k98RpANnTmiw0DtVBORMER +TH2L/jrqjtSTNfmGQz0nPXL7t2VHZsWP3qpo8nPxsz9AiYXPHLsfrGyUg6/ZAwjT +l8OJDBBqvox2IbCEZ0G4nSwwyZHcLRYhYxCCKMECgYEAltqLZpFHUfbAeEMYOY2i +IhVOHJGJY0eUiRJKfa6wTmjU/KZvDRexqPS7pnqAzn4Yb3NLpJFYUp6gjlltOf3B +TfsAMHuPuMmQ98n/KUvZkmWntwgzsoefzWj8s5L+61EwQd8TaWLItmYj/oK5UAAr ++7GX2bxSW20SZOK28+XMXskCgYAXcHfRbtePcDwQw59OXvXAkRNTioqBwjM9v0vS +pSE3iAV2fexwGQyXA07a5h7OH+TmpzvAbHsy6q8bD8+PWN6OKYUYRTP43EVC3GJ/ +RD/1KanyC5MoNXQHK63KDV3Qz+vQSGXC2b5HjM8fX1cr0oi8QHO0yILQeeqJlmEM +IGC5AQKBgDcEbjmei8/hqc0NHt3ZFP/vboabeREXkAt4ipHul9XeTEp3hojQIjPY +ElB4B+qnanHAM7QmYIc9ObQiKPyedZlMkpETm45HBHfzysU7SeHa8GyfbmsGFXgC +Cjx97XRgRo/15PKzMlD/yez9x83IAYHLcRp7eM1V5K9bZLcgOehd +-----END RSA PRIVATE KEY----- \ No newline at end of file diff --git a/05.Seguridad/private_key.pem b/05.Seguridad/private_key.pem new file mode 100644 index 0000000..0aab409 --- /dev/null +++ b/05.Seguridad/private_key.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICXAIBADCCAjUGByqGSM44BAEwggIoAoIBAQCLpc67EBigSTJEBVjl8atX2wIj +tf814ieCCHxvzkLMmq50GUsp3kUlmlZI5SvDkHvfRl85sTDiN2j0CUqsw4Ncebok +4FB4DDEMxf00QLbSgaDywAg7S9Q5eEAP6ENZIRGTCBZ38/JUgw2iiMt3meStySdR +N1EIz1otIB6tgsuQZvwlMURfqf9mg+yiiTqz6JwLynA45EcjyOrM0n9XAIdJz1lz +aKzGzXP+0fDQ1Ke3bh6f4DW2Da/w+hVNpLYta6bj1UiuGOkRhsU8LDo/Sgg0fW/B +yUeAjg2NlMqrO3uhxpUA6ul6q6IIU4TqRWI/98i0qO/krrg9r1IiyZr/0ZiTAh0A +6ZXrZC/1gW6yMVLEEgSpSs1a8zcv9eymC5xdDQKCAQA1SVK03Kq6V+E3yrMXmBYH +7QoP0xYHH6pyHEuQw4RfEoNk0y3AXnpBg92JsjO8ey3rULgWB/wkI/95OofgeQAJ +tlCgsGWOXr0ppJJkvwg9GgsU/genjiNfnXPZ5xvFi9jCYpD14pmH0BmCpHp51Esk +0lLGxWjHE0RZjxYfRNH2hVh3rTrBW/d5nu+c5WyW/aOrHpzg9VrAz7L5g+sxMP2T +yn/wVeMTXAgTcxndBbBQKrV/MZhuJrfN4nS7k4gAckE2NCjIUAnryaiO1ZNwswHd +V2zDPZCN1CTu0HUTJSJxZZhtbw5GW2Cbec6fWk7G2g509OCzHNkIK6LoQK5B+5cW +BB4CHFNvzw3Q5P3tNtrsYuCgw/l3zuLQep9BcP5hxc0= +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/05.Seguridad/private_key_firma.pem b/05.Seguridad/private_key_firma.pem new file mode 100644 index 0000000..5e0ef78 --- /dev/null +++ b/05.Seguridad/private_key_firma.pem @@ -0,0 +1,15 @@ +-----BEGIN PRIVATE KEY----- +MIICXQIBADCCAjYGByqGSM44BAEwggIpAoIBAQC8iI7cae2ByUPzEaHhO2BH+7XS +ioFbY6lPPjX5CXZfw1TVVQE90XEhOUNPuA0t4DaUvMIgrPto1Br78Gyy4mRnEFiA +SXAX2r/MlijGm9f5WcTzW//duOakZ7ffAoEbO39teRK23whSqNLOPJkE/JqcKx5M +dKNF5oa2KXa3kJU6KgRhMcfLHumCVaoF3b1DrVZ4d9y9bSJvWO0gV868d2B+AfjI +3Nrd6WDc7ArD6GM+LqoRCskoZymCoyUZO3d3P56X5yLtOmcbsJtUh+gbChBPJMGX +042sV8gwydro+iQsijo3eHc9p1ufJXlryh9ZG3+XCKoh4mMf7DZYLOEiXYatAh0A +05JYNHr0zvUc4DvJyRDPFylPks3f2PY+REpSewKCAQEAuGHFyGBuAONFn3RP0+Bj +3BFHLsF1KHvkKKbhA8nQCN+u/nyoVzvJp6FYqsGtHffSVa/8vOibIEDkmI7IP/9k +9hhVSmmrF8Jx61b6bHBZHxPmHNZyzKP4UeqL85Y1aGo90u5VcPtYXRjdW2hHuDbJ ++XmmGp0MVqJSmPi96EqGRD51+5xOWpna/z4waxKw33yDLd/yqopGjZx1Qx6lPUUg +EafQ7EqRzzqcKdAfhFIciKiSXruJt1TE/TDBngcAdrGzuc8ULlWDe3YBRpKQxtvG +osShwnJW+z+6rDIQ5W8MAeCwZgM5R0YH/30aHMpEQg5L+t1ON4s9slNM7Q0s0E4k +iwQeAhwgIUzsTphLxt4RemvzXSdQHGhg4c1mgMs9UF8U +-----END PRIVATE KEY----- \ No newline at end of file diff --git a/05.Seguridad/public_key.pem b/05.Seguridad/public_key.pem new file mode 100644 index 0000000..e69de29 diff --git a/05.Seguridad/public_key_firma.pem b/05.Seguridad/public_key_firma.pem new file mode 100644 index 0000000..f78d3f3 --- /dev/null +++ b/05.Seguridad/public_key_firma.pem @@ -0,0 +1,20 @@ +-----BEGIN PUBLIC KEY----- +MIIDRDCCAjYGByqGSM44BAEwggIpAoIBAQC8iI7cae2ByUPzEaHhO2BH+7XSioFb +Y6lPPjX5CXZfw1TVVQE90XEhOUNPuA0t4DaUvMIgrPto1Br78Gyy4mRnEFiASXAX +2r/MlijGm9f5WcTzW//duOakZ7ffAoEbO39teRK23whSqNLOPJkE/JqcKx5MdKNF +5oa2KXa3kJU6KgRhMcfLHumCVaoF3b1DrVZ4d9y9bSJvWO0gV868d2B+AfjI3Nrd +6WDc7ArD6GM+LqoRCskoZymCoyUZO3d3P56X5yLtOmcbsJtUh+gbChBPJMGX042s +V8gwydro+iQsijo3eHc9p1ufJXlryh9ZG3+XCKoh4mMf7DZYLOEiXYatAh0A05JY +NHr0zvUc4DvJyRDPFylPks3f2PY+REpSewKCAQEAuGHFyGBuAONFn3RP0+Bj3BFH +LsF1KHvkKKbhA8nQCN+u/nyoVzvJp6FYqsGtHffSVa/8vOibIEDkmI7IP/9k9hhV +SmmrF8Jx61b6bHBZHxPmHNZyzKP4UeqL85Y1aGo90u5VcPtYXRjdW2hHuDbJ+Xmm +Gp0MVqJSmPi96EqGRD51+5xOWpna/z4waxKw33yDLd/yqopGjZx1Qx6lPUUgEafQ +7EqRzzqcKdAfhFIciKiSXruJt1TE/TDBngcAdrGzuc8ULlWDe3YBRpKQxtvGosSh +wnJW+z+6rDIQ5W8MAeCwZgM5R0YH/30aHMpEQg5L+t1ON4s9slNM7Q0s0E4kiwOC +AQYAAoIBAQCNy1C19UKzD+yxI8Q2yzhgRGoqpm/P43Rfz+fJKgdMDlhcxJMDmuJU +MBmjC+UZEzrIbsYmzKKH9EA6TEDTDrfTh1K/ixwrj9mHouC7qVPP2Lz5qJB074Lh +Nhkj06D+cSZfbXJgZKVbQ42CXl51suYoZaSKnX6ueHz2Wi0QPLmyrst7jLl2kwfR +yBXWw2c9thVDR3m3RIAMsfWfDDzx99quNMozuaM+prjrXT2A+bm5znVty1G2+m91 +RlQPGIhseE3VF5L4P+vswOwe8fdg/noT+eQyK8FVjPypVM5Y51sF7jAwdOPfpkGT +w9dKJALhoz0cUsNbuduYOlc+RF+oZPJj +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/05.Seguridad/publica_usuario_A.pem b/05.Seguridad/publica_usuario_A.pem new file mode 100644 index 0000000..69b4fcb --- /dev/null +++ b/05.Seguridad/publica_usuario_A.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyB4APiFRaxPZ03EDLbxD +uihdEa9Bj8gxJHF3OjSDtefRNYDIM3wf4gSzzNINyHwVqogPTOo/fBoaV4eMIUF+ +odkAbTuytrSqZc9nYnwBUy3nDiiToQ/s1EM1vdPJ5aEl3onfAeRcR+xDonuK+MGw +QcBVQEZQK393MPJ0tNyHjfoUZjmzPaM7OTD6vegcmEx2bD3watNuQ0cibwceQa9c +mlZklQXv12wvDRKIbl1eJAsHY0YwyXLZtDfNVMh8+1hmCLNiPefQnDU89AwarZiX +mFo41e1wfkWuX9G/kga7Ms0F0LRg/ZYBWhK7eByooTF10ZFwdoyqJpGXYwos6geG +bQIDAQAB +-----END PUBLIC KEY----- \ No newline at end of file diff --git a/05.Seguridad/rsa_key_privada.bin b/05.Seguridad/rsa_key_privada.bin new file mode 100644 index 0000000..addb49c --- /dev/null +++ b/05.Seguridad/rsa_key_privada.bin @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFJTBPBgkqhkiG9w0BBQ0wQjAhBgkrBgEEAdpHBAswFAQIXp6qCe+GuiQCAkAA +AgEIAgEBMB0GCWCGSAFlAwQBAgQQCq0d/vmjm8RTvQio4exl1ASCBNA2v3+Pp3e3 +mdrDTPTwR6MaWpAmHyQ1B994AHjHIkC1nnqEukUN6uu6B477dsRyNybDXdrXxHVj +PFZaZrSh95wJM65s0yxMew0YzdZaTCuoYhGM2usbcgdHmII/mD+eDrEawHH/mlAj +0JO30BUmTGtfECAOyXjXQIJDYGUE5Pm/t3s6H3XQ6Fxg4MSYqH8AZziVea+tlFlK +8H+ItCHEFN23deS5woIcYeBwjd/iYmuh7cu2sSq4ilqq09Qi1dTCke0BPhwCxaRy +dfmIQrV9+GO3elM1qKY7Wgpoxz5Isq+VrzTKTgomSKPsd7JmCmwF+lUhBF197/4h +RqU1vCC+dWzWhEgPQSDJswk5P61pzrGGukKncCj8CQsKU4xbdQN/cqmcVGf0VBVd ++Da6geTUUBBI39f1kDV2ZeVhj96xNIBAd6MIkDeB74dQuC62PGxtH8gLdQk4/+kV +bBqGhfmGJ+N93LWhPSJ8b+4PnSUOj9mqMJUyl69prh/WuzAFEPvC8tidmq7k1zAA +gAbclVZKvBByHnSFU2xXbN8OtyCYrsbYTjm/j95tvXdBde/DpxR7S5819pDEM9Ad +2x9bFavSDPj+ofwqUuPCr8nIVQARmtjWvWm7ncKlMi1ic2myYS5O9S+UhNYlJIuc +P5NlOcaGg1rPAaeCfy+ss+rE7P/qaTHLS8g/xo+V1FyhnTyJNydRhuBIQXp1CzGD +ONC6lrTvTzHw4vW94F8Y16xV7Dtov9+anh5WlhJUXVxgG7geojfYSVfWy5MLOLQg +pZoOTCzTAxKs1AXyxYlUjK1I5NPFp7MUiOblef6rKSPaw4WSl60P31otYWyoXQE5 ++ilCr+v1qbWqhexOJRNBCj4gbvD1a1fykHYEOX9gjohMd+Ih272CZD+5aC7ukQYg +OqfDkEC/n3zDQQE5k3N/BCw8g5SCfFBl2NsoHePcRXNxwGH1/pjr28LHaGeNvS3A +IoUTBNpJD0o8YW3l8QV0dsbwLfrSbQbjZ9r4WsWF43SyAStfewHBbFjgj0k4oCcl +vfFUom4MWBQd/abUgXruVEdEM0NtjWUFkNx2fd4VNC2OvKRGxVRG/1j0FCCkmqDK +icSs8fhtCYsvmmE2eXr0rTsCeO8RlV6tddWVX599QTop1cLsJn5dB6xx5Dh1uJqy +VU424RcP/Z9QdLk82wRT/qvXZbfY98cec0yp7++/KKTmUm07STBtIuMUSN5ucuxh +tJetJgdqwujypYFxG/7elJfvfylxjB/o38rS5QOU2833Ei/MO8nqVZiaPUwHtqEE +DDu5kwnWflMMwFGNQlxYrehlLwbbUn4MZeJkDgCSuDC+xzRIk5Cjheaj8cetatPD +1jRCZB/MGB9V09REehSC42puBj2K0T7+9ezKUU5SBnO0BW5mg96O+Dpt7BIqpnbt +m4LzSmKU5ar0bnuyqP01dx8HjCj3KpnZ7AZYoe4vZgFgt+Z+aDo+tWaePYb525mu +UtiJMIaG0o69UPF3G6Cd46PF7ZU0N0qZFci9sjZSrvdVHiF7Z4/8sYKKXtksp/ue +EMbBejbt47vqgZxUuvlv3s/P50Oac8HTXVlSiYDMtLid3FqR/pZ+fC1eFjcBn20W +rP4bKoSLavjAaqW3EcaISBebk6PrMoGJ4A== +-----END ENCRYPTED PRIVATE KEY----- \ No newline at end of file diff --git a/05.Seguridad/textoDesencriptado.txt b/05.Seguridad/textoDesencriptado.txt new file mode 100644 index 0000000..9480086 --- /dev/null +++ b/05.Seguridad/textoDesencriptado.txt @@ -0,0 +1,4 @@ +Hola +Esto es una prueba +Camión +España \ No newline at end of file diff --git a/05.Seguridad/textoEncriptado.txt b/05.Seguridad/textoEncriptado.txt new file mode 100644 index 0000000..2e0aec7 --- /dev/null +++ b/05.Seguridad/textoEncriptado.txt @@ -0,0 +1 @@ +^oqx9Vi)Ug>Ԅ7|cupXr \ No newline at end of file diff --git a/05.Seguridad/textoPlano.txt b/05.Seguridad/textoPlano.txt new file mode 100644 index 0000000..9480086 --- /dev/null +++ b/05.Seguridad/textoPlano.txt @@ -0,0 +1,4 @@ +Hola +Esto es una prueba +Camión +España \ No newline at end of file diff --git a/PROTOCOLO PPT V 1.pdf b/PROTOCOLO PPT V 1.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c23676ed70562345c64f194a7721c5d509dfbd80 GIT binary patch literal 116485 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 Cv%vQN literal 0 HcmV?d00001 diff --git a/README.md b/README.md new file mode 100644 index 0000000..08d0e1e --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Programacion_de_servicios_y_procesos_en_Python +Libro Programación de servicios y procesos en Python + +Jose Luis Carnero Sobrino +E-mail: jlcarnerosobrino@gmail.com