{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC-2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'> Equipo Docente IIC2233 2026</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Ejemplos de _networking_ con PyQt\n",
    "\n",
    "**Recuerda que al leer este documento directamente desde GitHub, no estás aprovechando su contenido al 100%.\n",
    "Editando el código puedes aprender más que leyéndolo. Te recomendamos descargarlo y ejecutarlo desde tu computador para que puedas hacer modificaciones e interactuar con el código.**\n",
    "\n",
    "**IMPORTANTE: El siguiente material incluye ejemplos que deberían ser ejecutados en programas separados, donde uno actúa como el cliente, y otro actúa como el servidor. Para ello, deberás tener, como mínimo, 2 terminales. Una posicionada en la carpeta del cliente y otra en el servidor, y ejecutar sus códigos por separado.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Networking e Interfaces gráficas](#Networking-e-Interfaces-gráficas)\n",
    "    1. [Servidor como función (`1_ejemplo_simple`)](#Servidor-como-función-(1_ejemplo_simple))\n",
    "    2. [Servidor como clase (`2_ejemplo_avanzado`)](#Servidor-como-clase-(2_ejemplo_avanzado))\n",
    "    3. [Acotación](#Acotación)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Networking e Interfaces gráficas\n",
    "\n",
    "Vimos en semanas anteriores cómo desarrollar programas que sigan una arquitectura **cliente-servidor**. Además, vimos en semanas anteriores, en específico las que trataban de desarrollo de interfaces gráficas y en esta el modelo **frontend-backend** de desarrollo. Ahora, si quisiéramos desarrollar un programa que haga uso de Networking e Interfaces gráficas al mismo tiempo, ¿cómo mezclamos ambos conceptos de desarrollo en un solo proyecto?\n",
    "\n",
    "Para lograr lo anterior debemos tomar en cuenta que, en la mayoría de los casos, el servidor **no tiene necesidad de poseer una interfaz gráfica**, dado que su rol principal es procesar solicitudes de los clientes, en vez de ser un programa con el que se interactúe de forma directa. Por lo tanto, solo nuestro cliente es el que requiere una interfaz. Si bien esto ocurre en la mayoría de los casos, puede existir algunos donde el servidor disponga de una interfaz. Por ejemplo, una interfaz para poder monitorear el consumo de recursos (memoria y procesos) de un servidor mientras los clientes se conectan e interactúan.\n",
    "\n",
    "Finalmente, dado que las funcionalidades de Networking están enfocadas en la comunicación entre dos programas y en el traspaso de datos, es posible inferir que todas las funciones que involucren la creación y uso de *sockets* corresponderá a controladores de la interfaz y, por ende, son parte del **backend** del programa. Es por esto que al modelar nuestro programa, deberíamos tener un esquema similar al siguiente:\n",
    "\n",
    "![](img/networking-interfaces.png)\n",
    "\n",
    "A continuación, se van a presentar 2 ejemplos (uno simple y otro más avanzado) donde se aplicó el esquema anterior para integrar estos 2 contenidos. En ambos casos, será necesario tener 2 o más terminales abiertas, una posicionada dentro de la carpeta \"servidor\" del ejemplo a trabajar y las demás en la carpeta \"cliente\". Finalmente, ejecutar el archivo `main.py` de cada carpeta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "### Servidor como función (`1_ejemplo_simple`)\n",
    "\n",
    "Este código es un programa básico que levanta una interfaz, desde la cual se puede pedir información sobre una palabra o color al servidor. Luego, el servidor responde con un valor aleatorio de la categoría pedida y este es mostrado en la interfaz. Nótese que en el controlador lógico del *backend* levantamos un hilo cuya función exclusiva será escuchar mensajes del servidor y transmitirlos al controlador, permitiendo que nuestra interfaz pueda realizar otras acciones mientras espera por algún mensaje del servidor, en vez de quedarse pegada esperando respuesta.\n",
    "\n",
    "Para fines de comunicación, el cliente solo codifica y envía un _string_, mientras que el servidor responde con otro _string_ codificado.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Servidor como clase (`2_ejemplo_avanzado`)\n",
    "\n",
    "Este código es un programa más avanzado donde hay interacción entre los diferentes clientes gracias al servidor que sirve como puente. En particular, al momento de conectarse un cliente, le solicita al servidor la hora de Chile, pero también le puede solicitar la hora de Japón. Este comportamiento es muy similar al ejemplo anterior. No obstante, el cliente tiene 2 funcionalidades más, puede pedir adelantar el tiempo en 1 hora o retrasarlo en 1 hora, y esta solicitud afecta **a todos los clientes que están conectados**, es decir, la acción de un cliente repercutirá en todos los demás. En particular, si pide adelantar la hora, todos los clientes verán cómo su reloj se adelanta en 1 hora. Dado lo anterior, se recomienda probar este ejemplo con al menos 2 clientes conectados.\n",
    "\n",
    "Para fines de comunicación, el cliente codifica y envía un diccionario que contiene 2 elementos:\n",
    "- `\"key\"` que contiene el comando a realizar: pedir la hora de un país o cambiar el tiempo.\n",
    "- `\"arg\"` que contiene el argumento del comando. En particular, si `\"key\"` es pedir la hora, `\"arg\"` contiene el nombre del país (chile o japón), mientras que si `\"key\"` es cambiar el tiempo, `\"arg\"` contiene hacia donde moverlo (adelantar o retrasar 1 hora). \n",
    "\n",
    "En cambio, el servidor responde con _string_ el cual puede ser: `\"adelantar\"`, `\"retroceder\"` o un número transformado en _string_, por ejemplo, `\"1237444123\"`. En función de esas 3 posibles respuestas, el cliente la procesa y actualiza la hora según corresponda.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Acotación\n",
    "\n",
    "Para ambos ejemplos se publicaron 2 _backends_ del cliente. Uno confeccionado con `QThread` y otro utilizando `threading`. Por defecto se utiliza `QThread`, pero solo es necesario actualizar la línea número 5 del `main.py` para que el cliente ocupe el backend con `threading`. \n",
    "\n",
    "Este es otro ejemplo de que una correcta modelación permite cambiar 1 archivo, y el programa seguirá funcionando sin necesidad de alterar el _frontend_. Del mismo modo, podrán notar que con `threading` o `QThread` se logró el mismo resultado. Por lo cual, queda a decisión de ustedes determinar cuál utilizar."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
