{
 "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'> Modificado en 2019-1 al 2026-1 por Equipo Docente IIC2233. </font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Cliente UDP](#Cliente-UDP)\n",
    "2. [Servidor UDP](#Servidor-UDP)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ejemplo: Sockets UDP\n",
    "Como recordatorio, para crear un *socket*, debemos crear una instancia de la clase `socket(family, type)`, donde `family` es el tipo de dirección IP que usaremos, y `type` es el protocolo de transporte. En este caso, usaremos el `type` `SOCK_DGRAM` para el protocolo UDP.\n",
    "\n",
    "#### Cliente UDP\n",
    "\n",
    "Dado que el protocolo UDP no establece una conexión, la comunicación UDP es mucho más simple de implementar. No requiere solicitar una conexión previa (no hay un `connect`) con una dirección IP ni puerto específico antes de transmitir datos. Por lo mismo, cada vez que se envía un mensaje, mediante el método `sendto`, se debe especificar la dirección y puerto del servidor, a diferencia de `sendall` y `recv` de TCP.\n",
    "\n",
    "Por ejemplo, para enviar un mensaje como cliente a un servidor ocupamos el método `sendto(mensaje, (host_destino, puerto_destino))` como se muestra a continuación."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "import socket\n",
    "\n",
    "server_host = socket.gethostname()\n",
    "server_port = 15000\n",
    "\n",
    "sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n",
    "mensaje = \"Hola, simplemente te estoy enviando un mensaje.\".encode('utf-8')\n",
    "sock.sendto(mensaje, (server_host, server_port))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Opcionalmente, podemos recibir información enviada de vuelta. El método `recvfrom(buffer)` retorna, además de los datos, la dirección y puerto desde donde fueron enviados. El parámetro `buffer` es la cantidad máxima de *bytes* que será leída, que se recomienda que sea una potencia de 2 pequeña."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "data, (host_origen, puerto_origen) = sock.recvfrom(4096)\n",
    "print(data.decode('utf-8'))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Cuando terminemos de ocupar el *socket*, debemos cerrarlo."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "sock.close()\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Servidor UDP\n",
    "\n",
    "Si de forma similar queremos implementar un servidor que envía mensajes usando el protocolo UDP, simplemente debemos preocuparnos de responder a la misma dirección desde donde se nos ha enviado algún mensaje. Dado que un _socket_ UDP no establece una conexión previa, cada vez que ejecutamos `recvfrom`, el método retorna los datos leídos y la información de dirección IP y puerto del emisor. \n",
    "\n",
    "Por ejemplo, el siguiente código podría representar al servidor que se comunica con el cliente implementado anteriormente."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```python\n",
    "## NO ejecutar esto como celda de código en este jupyter\n",
    "## Corre este código desde servidor_udp ubicado en codigo/UDP (Bonus)\n",
    "\n",
    "import socket\n",
    "\n",
    "sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)\n",
    "\n",
    "# Con un string vacío como argumento del bind, indicamos que el socket es \n",
    "# alcanzable desde cualquier dirección que pueda tener el servidor\n",
    "sock.bind((\"\", 15000))\n",
    "\n",
    "while True:\n",
    "    data, (host_cliente, puerto_cliente) = sock.recvfrom(4096)\n",
    "    print(data.decode('ascii'))\n",
    "    respuesta = f\"Aquí va mi respuesta para {host_cliente}.\"\n",
    "    sock.sendto(respuesta.encode('utf-8'), (host_cliente, puerto_cliente))\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**NOTA: Junto a este archivo se encuentra la carpeta `codigo/UDP (Bonus)`, en donde se encuentran los archivos para el cliente y el servidor utilizando UDP.**"
   ]
  }
 ],
 "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.7"
  },
  "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
}
