Blog Tinux Net

Trabajos Frontens, Backed y Servidores Linux

En este Blog se trabajara sobre el trabajo diario de Desarrollo con Servidores y Web Developer
  • Publicado en

    Para buscar una palabra en documentos PDF desde la terminal en Linux, puedes utilizar la herramienta pdfgrep[1][2][4]. Aquí te muestro algunos ejemplos de cómo usarla:

    Buscar una palabra específica en un archivo PDF:

    pdfgrep palabra archivo.pdf
    

    Buscar una frase en un archivo PDF, ignorando mayúsculas y minúsculas y mostrando el número de línea:

    pdfgrep -ni "frase a buscar" archivo.pdf  
    

    Buscar una palabra en varios archivos PDF del directorio actual:

    pdfgrep -ni palabra *.pdf
    

    Buscar recursivamente en todos los archivos PDF de un directorio y subdirectorios:

    pdfgrep -r --include "*.pdf" palabra /ruta/directorio
    

    Contar cuántas veces aparece una palabra en varios archivos PDF:

    pdfgrep -c palabra *.pdf
    

    Algunas características adicionales de pdfgrep:

    • Es compatible con expresiones regulares para búsquedas más avanzadas
    • Permite excluir ciertos archivos de la búsqueda con --exclude
    • Tiene más opciones que puedes consultar con pdfgrep --help

    Para instalar pdfgrep en Linux, está disponible en los repositorios de la mayoría de distribuciones[2]. Por ejemplo en Ubuntu/Debian:

    sudo apt install pdfgrep
    

    Y en Arch Linux:

    sudo pacman -S pdfgrep
    

    En resumen, pdfgrep es una herramienta muy útil para buscar texto en archivos PDF directamente desde la terminal en Linux, con opciones avanzadas y fácil de instalar.

    Citations: [1] https://platzi.com/tutoriales/1748-terminal-2019/5574-pdfgrep-buscar-palabras-de-un-archivo-pdf-en-la-terminal-gnulinux/ [2] https://lamiradadelreplicante.com/2017/03/10/buscar-texto-en-archivos-pdf-con-pdfgrep/ [3] https://fortinux.gitbooks.io/humble_tips/content/usando_la_linea_de_comandos/tutorial_usar_grep_para_buscar_texto_dentro_de_archivos_en_gnulinux.html [4] https://platzi.com/tutoriales/1276-terminal-2018/5389-buscar-palabras-de-un-archivo-pdf-en-la-terminal-gnulinux-usando-pdfgrep/ [5] https://atareao.es/tutorial/terminal/buscar-archivos-en-el-terminal/

  • Publicado en

    Grub2

    Instalar GRUB2

    1. Instalar el paquete grub2 en OpenSUSE:
    sudo zypper install grub2
    
    1. Identificar el disco donde se quiere instalar el gestor de arranque GRUB2. Supongamos que es /dev/sdb.

    Configurar GRUB2

    1. Edita el archivo de configuración principal de GRUB2:

    ```sudo nvim /etc/default/grub


    2. Modifica las siguientes opciones: - `GRUB_DEFAULT=0`: Establece el índice de la entrada de arranque predeterminada (0 para la primera). - `GRUB_TIMEOUT=5`: Establece el tiempo de espera en segundos antes de arrancar la entrada predeterminada. - `GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/SuSE-release)"`: Establece el nombre del distribuidor. - `GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"`: Establece los parámetros adicionales del kernel. 3. Guarda los cambios y cierra el editor. ## Detectar sistemas operativos 1. Ejecuta el siguiente comando para detectar los sistemas operativos instalados: ```sudo grub2-mkconfig -o /boot/grub2/grub.cfg

    Este comando generará automáticamente la configuración de GRUB2 basándose en los sistemas operativos detectados.

    Instalar GRUB2 en el disco

    1. Instala GRUB2 en el disco seleccionado (/dev/sdb en este ejemplo):

    sudo grub2-install /dev/sda

    Este comando instalará el gestor de arranque GRUB2 en el disco especificado.

    Reiniciar y probar

    1. Reinicia el sistema y deberías ver el menú de arranque de GRUB2 con las entradas para cada sistema operativo detectado.

    2. Selecciona cada entrada para asegurarte de que los sistemas operativos arrancan correctamente.

    Recuerda que si tienes problemas específicos con algún sistema operativo, puede ser necesario ajustar la configuración de GRUB2 manualmente editando el archivo /boot/grub2/grub.cfg. Sin embargo, este archivo se genera automáticamente, así que cualquier cambio manual se perderá después de ejecutar grub2-mkconfig.

    Si necesitas hacer cambios permanentes, es mejor modificar el archivo /etc/default/grub y luego volver a generar la configuración con grub2-mkconfig.

    Citations: [1] https://forums.opensuse.org/t/recuperar-el-inicio-de-win-en-el-boot/150751 [2] https://doc.opensuse.org/documentation/leap/reference/html/book-reference/cha-grub2.html [3] https://es.opensuse.org/GRUB2 [4] https://victorhckinthefreeworld.com/2011/11/26/modificar-las-opciones-de-arranque-del-grub-en-opensuse/ [5] https://discussion.fedoraproject.org/t/configurar-grub2/72830

  • Publicado en

    " Directorio de plugins

    call plug#begin('~/.local/share/nvim/plugged')  
    
    " Aquí irán los plugins a instalar  
    Plug 'https://github.com/vim-airline/vim-airline' "Vim Airline  
    Plug 'https://github.com/preservim/nerdtree' " Nerdtree, Arboiles de navegacion  
    Plug 'neoclide/coc.nvim', {'branch': 'release'}  
    Plug 'tpope/vim-surround'  " Plugin que permite contarer o expandir entre etiquetas  
    Plug 'nvim-telescope/telescope.nvim', { 'tag': '0.1.0' }  
    Plug 'nvim-treesitter/nvim-treesitter'  
    Plug 'nvim-lua/plenary.nvim'  
    Plug 'nvim-tree/nvim-web-devicons' " optional, for file icons  
    Plug 'nvim-tree/nvim-tree.lua'  
    Plug 'hrsh7th/nvim-cmp'  
    Plug 'lukas-reineke/indent-blankline.nvim'  
    Plug 'mattn/emmet-vim' "Funciones Emmet de Autocompletado html, CSS, y Javascript  
    
    Plug 'https://github.com/wolandark/vim-loremipsum.git' 
    " Plug 'neoclide/coc.nvim'  
    Plug 'rafi/awesome-vim-colorschemes'  
    Plug 'ap/vim-css-color'  
    Plug 'SirVer/ultisnips'  
    Plug 'honza/vim-snippets'  
    Plug 'preservim/nerdtree'  
    Plug 'jiangmiao/auto-pairs  '
    Plug 'tpope/vim-unimpaired'  
    
    
    
    call plug#end()  
    
    function HighlightsTabsAndSpace ()  
    call feedkeys(":set listchars=eol:¬,tab:\\|_,trail:~,extends:>,precedes:<,space:\\|\<CR>")  
    call feedkeys(":set list\<CR>")  
    endfunction  
    
    nmap <leader>t :call HighlightsTabsAndSpace()<CR>  
    nmap <leader>tt :set nolist<CR>  
    
    set tabstop=4  
    set shiftwidth=4  
    set expandtab  
    set relativenumber  
    set number  
    let g:python3_host_prog = '/usr/bin/python3'  
    let g:airline_powerline_fonts=1   
    let g:airline#extensions#tabline#enabled = 1  
    
    let g:fzf_preview_window = 'right:50%'  
    let g:fzf_layout = { 'window': { 'width': 0.9, 'height': 0.6  }  }   
    
    let g:loaded_node_provider = 0  
    let mapleader = " "  
    
    
    
    map <F5> :NERDTreeToggle<CR>  
    map <leader>se :%s/  
    map <leader>ne :NERDTree  
    map <leader>cc 0^i//<TAB><Esc>  
    map <leader>cu 0dw<CR><ESC>  
    map <leader>cch 0^i/*<C-$>$0^i */<Esc>  
    map <F5> :NERDTreeToggle<CR>  
    
    :inoremap <C-J> o<Esc>  
    
  • Publicado en

    Para montar un sistema de archivos LVM, Hay que seguir estos pasos:

    1. Identificar los dispositivos LVM:

      • Ejecutar el comando pvs para obtener la lista de volúmenes físicos (PV) y grupos de volúmenes (VG) disponibles:

        $ sudo pvs
        

        En mi caso:

        PV VG Fmt Attr PSize PFree
        /dev/sda3 almalinux lvm2 a-- 221,98g 0 VG: almalinux

    • Identificar el nombre del grupo de volúmenes (VG) que contiene el volumen lógico (LV) que deseas montar.
    1. Listar los volúmenes lógicos:

      • Ejecutar el comando lvdisplay con el nombre del grupo de volúmenes (VG) para obtener la lista de volúmenes lógicos (LV) disponibles: bash $ sudo lvdisplay <nombre-del-grupo-de-volumenes> $ sudo lvdisplay almalinux LV Path /dev/almalinux/swap
        LV Name swap
        VG Name almalinux
        LV UUID U6IxH2-WRlu-fe7s-PNSV-BenF-xmtG-7BCvhd
        LV Write Access read/write
        LV Creation host, time localhost.localdomain, 2024-06-13
        21:00:45 +0200 LV Status available
        LV Size 7,64 GiB
        Current LE 1957
        Segments 1
        Allocation inherit
        Read ahead sectors auto
      • currently set to 1024
        Block device 254:0

      --- Logical volume ---
      LV Path /dev/almalinux/home
      LV Name home
      VG Name almalinux
      LV UUID z8lIGf-pef1-87Zu-0Sec-T31N-Zb3e- TQwGdq LV Write Access read/write
      LV Creation host, time localhost.localdomain, 2024-06-13 21:00:45 +0200
      LV Status available

      LV Size 144,34 GiB
      Current LE 36950
      Segments 1
      Allocation inherit
      Read ahead sectors auto

      • currently set to 1024
        Block device 254:1

      --- Logical volume ---
      LV Path /dev/almalinux/root
      LV Name root
      VG Name almalinux
      LV UUID fGyrxc-MnrI-T8hh-9iTk-fOO2-ObQg-tsINqj
      LV Write Access read/write
      LV Creation host, time localhost.localdomain, 2024-06-13 21:00:47 +0200
      LV Status available

      LV Size 70,00 GiB
      Current LE 17920
      Segments 1
      Allocation inherit
      Read ahead sectors auto

      • currently set to 1024
        Block device 254:2
    • Identifica el nombre del volumen lógico (LV) que deseas montar.
    1. Montar el volumen lógico:

      • Utiliza el comando mount con el nombre del volumen lógico (LV) y la ruta de montaje deseada: bash $ sudo mount /dev/<nombre-del-volumen-lógico> <ruta-de-montaje>
      • Por ejemplo: bash $ sudo mount /dev/vgtest/lvdata2 /mnt En mi caso voy a montar la root

      mount /dev/almalinux/root /mnt/almalinux/root

    Pero tambien la home:

    mount /dev/almalinux/root /mnt/almalinux/root 
    
    1. Verificar el estado del montaje:

      • Ejecuta el comando mount sin argumentos para verificar la lista de sistemas de archivos montados: bash $ mount

      /dev/mapper/almalinux-root on /mnt/almalinux/root type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)
      /dev/mapper/almalinux-home on /mnt/almalinux/home type xfs (rw,relatime,attr2,inode64,logbufs=8,logbsize=32k,noquota)

    Estos pasos te permitirán montar un sistema de archivos LVM de manera efectiva

  • Publicado en

    Inicialización del proyecto

    El primer objetivo es configurar una página web HTML sencilla que proporcione un formulario y una lista de mensajes. Vamos a utilizar el marco web Node.JS express con este fin. Asegurarse tener Node.JS esta instalado.

    Primero creemos un package.json archivo de manifiesto que describe nuestro proyecto. Te recomiendo colocarlo en un directorio vacío dedicado (llamaré al mío) socket.io). package.json

    {
    

    "name": "socket.app",
    "version": "1.0.0",
    "main": "index.js",
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "keywords": [],
    "author": "",
    "license": "ISC",
    "description": "",
    "dependencies": {
    "socket.io": "^4.7.5"
    }
    }

    Ahora, para poder completar fácilmente la propiedad de dependencias con las cosas que necesitamos, usaremos npm install:

    npm install express@4
    

    Una vez instalado, podemos crear un archivo index.js que configurará nuestra aplicación.

    index.js

        const express = require('express');
        const { createServer } = require('node:http');
    
        const app = express();
        const server = createServer(app);
    
        app.get('/', (req, res) => {
          res.send('<h1>Hello world</h1>');
        });
    
    server.listen(8080, () => {
      console.log('server running at http://localhost:8080');
    });
    

    This means that: Esto significa que:

    • Express inicializa la app para que sea un controlador de funciones que puede proporcionar a un servidor HTTP (como se ve en la línea 5).
    • Definimos un controlador de ruta / al que se llama cuando llegamos a nuestro sitio web.
    • Hacemos que el servidor http escuche en el puerto 8080.

    Si ejecuta node index.js, debería ver lo siguiente: enter image description here Obteniendo en el navegador...localhost:8080 y no el 3000

    enter image description here

    Sirviendo HTML

    Hasta ahora en index.js llamamos a res.send y le pasamos una cadena de HTML. Nuestro código se vería muy confuso si simplemente colocáramos el HTML de toda nuestra aplicación allí, por lo que crearemos un fichero index.html y lo serviremos en su lugar.

    Refactoricemos nuestro controlador de rutas para usar sendFile en su lugar.

    const express = require('express');
    const { createServer } = require('node:http');
    const { join } = require('node:path');
    
    const app = express();
    const server = createServer(app);
    
    app.get('/', (req, res) => {
      res.sendFile(join(__dirname, 'index.html'));
    });
    
    server.listen(3000, () => {
      console.log('server running at http://localhost:3000');
    });
    

    Poniendo lo siguiente en el archivo index.html:

    <ul id="messages"></ul>
    <form id="form" action="">
      <input id="input" autocomplete="off" /><button>Send</button>
    </form>
    

    Si se reinicia el proceso (al presionar Control+C y ejecutar node index.js nuevamente) y actualiza la página, debería verse así:

    enter image description here

    Integración de Socket.IO

    Socket.IO se compone de dos partes:

    -Un servidor que se integra (o se monta en) el servidor HTTP Node.JS (el paquete socket.io) - Una biblioteca cliente que se carga en el lado del navegador (el paquete socket.io-client)

    Durante el desarrollo, socket.io nos sirve el cliente automáticamente, como veremos, por lo que por ahora sólo tenemos que instalar un módulo:

    npm install socket.io
    

    Eso instalará el módulo y agregará la dependencia al paquete.json. Ahora editemos index.js para agregarlo:

    const express = require('express');
    const { createServer } = require('node:http');
    const { join } = require('node:path');
    const { Server } = require('socket.io');
    
    const app = express();
    const server = createServer(app);
    const io = new Server(server);
    
    app.get('/', (req, res) => {
      res.sendFile(join(__dirname, 'index.html'));
    });
    
    io.on('connection', (socket) => {
      console.log('a user connected');
    });
    
    server.listen(8080, () => {
      console.log('server running at http://localhost:8080');
    });
    

    Observe que inicializo una nueva instancia de socket.io pasando el objeto servidor (el servidor HTTP). Luego escucho el evento de conexión de los connection entrantes y lo registro en la consola.

    Ahora en index.html agregue el siguiente fragmento antes de (etiqueta final del cuerpo): enter image description here

    Eso es todo lo que se necesita para cargar el cliente socket.io, que expone un global io (y el punto final (endpoint) GET /socket.io/socket.io.js), y luego conectarse.

    Si desea utilizar la versión local del archivo JS del lado del cliente, puede encontrarla en node_modules/socket.io/client-dist/socket.io.js.

    Truco: También puede utilizar un CDN en lugar de los archivos locales (e.g. ).

    Note que no se esta especificando ninguna URL cuando se llamo a io(), ya que de forma predeterminada intenta conectarme al host que sirve a la página.

    Si ahora reinicia el proceso (presionando Control+C y ejecutando node index.js nuevamente) y luego actualiza la página web, debería ver la consola imprimir “a user connected”.

    Intenta abrir varias pestañas y verás varios mensajes. enter image description here

    Cada socket también activa un evento de desconexión especial:

    io.on('connection', (socket) => {
      console.log('a user connected');
      socket.on('disconnect', () => {
        console.log('user disconnected');
      });
    });
    

    Luego, si actualiza una pestaña varias veces, podrá verla en acción. enter image description here

    Emitir eventos

    La idea principal detrás de Socket.IO es que puedes enviar y recibir cualquier evento que desees, con cualquier dato que desees. Cualquier objeto que pueda codificarse como lo hará JSON y también se admiten datos binarios.

    Hagámoslo de modo que cuando el usuario escribe un mensaje, el servidor lo recibe como un evento de mensaje de chat. La sección de script en index.html ahora debería verse de la siguiente manera: Y en index.js imprimimos el evento de mensaje de chat:

    io.on('connection', (socket) => {
       ,socket.on('chat message', (msg) => {
    enter code here
    console.log('message: ' + msg); });
    });
    

    Broadcasting

    El próximo objetivo es que emitamos el evento desde el servidor al resto de usuarios.

    Para enviar un evento a todos, Socket.IO nos brinda el método io.emit().

    // this will emit the event to all connected sockets
    io.emit('hello', 'world'); 
    

    Si desea enviar un mensaje a todos excepto a un determinado socket emisor, tenemos el indicador de transmisión para emitir desde ese socket:

    io.on('connection', (socket) => 
    {socket.broadcast.emit('hi');});
    

    En este caso, por simplicidad, enviaremos el mensaje a todos, incluido el remitente.

    io.on('connection', (socket) => {
      socket.on('chat message', (msg) => io.emit('chat message', msg);
      });
    });
    

    Y en el lado del cliente, cuando captamos un mensaje de chat, lo incluimos en la página.

    io.on('connection', (socket) => {
      socket.on('chat message', (msg) => io.emit('chat message', msg);
      });
    });
    

    Y en el lado del cliente, cuando captamos un mensaje de chat, lo incluimos en la página.

    const messages = document.getElementById('messages');
    
      socket.on('chat message', (msg) => {
    const item = document.createElement('li');
    item.textContent = msg;
    messages.appendChild(item);
    window.scrollTo(0, document.body.scrollHeight);
    

    });

    enter image description here

    Descripción general de la API

    Antes de continuar, hagamos un recorrido rápido por la API proporcionada por Socket.IO:

    Common API

    Los siguientes métodos están disponibles tanto para el cliente como para el servidor.

    Basic emit

    Como hemos visto en el paso 4, puedes enviar cualquier dato al otro lado con socket.emit():

    Desde el Cliente al Servidor:

    lado Cliente -> socket.emit('hello', 'world'); lado Servidor -> io.on('connection', (socket) => { socket.on('hello', (arg) => { console.log(arg); // 'world' }); }); y a la inversa Desde el Servidor al Cliente: lado Servidor: io.on('connection', (socket) => { socket.emit('hello', 'world'); }); lado Cliente: socket.on('hello', (arg) => { console.log(arg); // 'world' });

    Puede enviar cualquier cantidad de argumentos y se admiten todas las estructuras de datos serializables, incluidos objetos binarios como ArrayBuffer, TypedArray o Buffer (solo Node.js):

    From client to server: Client: socket.emit('hello', 1, '2', { 3: '4', 5: Uint8Array.from([6]) }); Server: io.on('connection', (socket) => { socket.on('hello', (arg1, arg2, arg3) => { console.log(arg1); // 1 console.log(arg2); // '2' console.log(arg3); // { 3: '4', 5: <Buffer 06> } }); }); From server to client Server: io.on('connection', (socket) => { socket.emit('hello', 1, '2', { 3: '4', 5: Buffer.from([6]) }); }); Client: `socket.on('hello', (arg1, arg2, arg3) => {

    console.log(arg1); // 1
    console.log(arg2); // '2'
    console.log(arg3); // { 3: '4', 5: ArrayBuffer (1) [ 6 ] }

    }); `

    No es necesario llamar a JSON.stringify() a objetos:

    Agradecimientos

    Los eventos son geniales, pero en algunos casos es posible que desees una API de solicitud y respuesta más clásica. En Socket.IO, esta función se denomina "reconocimientos".

    Viene en dos sabores:

    Con función de devolución de llamada

    Puede agregar una devolución de llamada como último argumento de la emisión (), y esta devolución de llamada se llamará una vez que la otra parte haya reconocido el evento:

    From client to server Client socket.timeout(5000).emit('request', { foo: 'bar' }, 'baz', (err, response) => { if (err) { // the server did not acknowledge the event in the given delay } else { console.log(response.status); // 'ok' } }); Server:

    io.on('connection', (socket) => {
      socket.on('request', (arg1, arg2, callback) => {
        console.log(arg1); // { foo: 'bar' }
        console.log(arg2); // 'baz'
        callback({
          status: 'ok'
        });
      });
    });
    

    From server to client Server:

    io.on('connection', (socket) => {
      socket.timeout(5000).emit('request', { foo: 'bar' }, 'baz', (err, response) => {
        if (err) {
          // the client did not acknowledge the event in the given delay
        } else {
          console.log(response.status); // 'ok'
        }
      });
    });
    

    Client:

    socket.on('request', (arg1, arg2, callback) => {
      console.log(arg1); // { foo: 'bar' }
      console.log(arg2); // 'baz'
      callback({
        status: 'ok'
      });
    });
    
    Con una promesa

    El método emitWithAck() proporciona la misma funcionalidad, pero devuelve una promesa que se resolverá una vez que la otra parte reconozca el evento:

    From client to server Client:

    try {
      const response = await socket.timeout(5000).emitWithAck('request', { foo: 'bar' }, 'baz');
      console.log(response.status); // 'ok'
    } catch (e) {
      // the server did not acknowledge the event in the given delay
    }
    

    Server:

    io.on('connection', (socket) => {
      socket.on('request', (arg1, arg2, callback) => {
        console.log(arg1); // { foo: 'bar' }
        console.log(arg2); // 'baz'
        callback({
          status: 'ok'
        });
      });
    });
    

    From server to client Server:

    io.on('connection', async (socket) => {
      try {
        const response = await socket.timeout(5000).emitWithAck('request', { foo: 'bar' }, 'baz');
        console.log(response.status); // 'ok'
      } catch (e) {
        // the client did not acknowledge the event in the given delay
      }
    });
    

    Client:

    socket.on('request', (arg1, arg2, callback) => {
      console.log(arg1); // { foo: 'bar' }
      console.log(arg2); // 'baz'
      callback({
        status: 'ok'
      });
    });
    

    Catch-all listeners (Escuchadores Generales)

    Un oyente general es un oyente al que se llamará para cualquier evento entrante. Esto es útil para depurar su aplicación: Sender (Emisor):

    socket.emit('hello', 1, '2', { 3: '4', 5: Uint8Array.from([6]) });
    

    Receiver (Receptor):

    socket.onAny((eventName, ...args) => {
      console.log(eventName); // 'hello'
      console.log(args); // [ 1, '2', { 3: '4', 5: ArrayBuffer (1) [ 6 ] } ]
    });
    

    De manera similar, para paquetes salientes:

    socket.onAnyOutgoing((eventName, ...args) => {
      console.log(eventName); // 'hello'
      console.log(args); // [ 1, '2', { 3: '4', 5: ArrayBuffer (1) [ 6 ] } ]
    });
    

    Server API

    Broadcasting

    Como hemos visto en el paso 5, puedes transmitir un evento a todos los clientes conectados con io.emit():

    io.emit('hello', 'world');
    

    enter image description here

    Rooms

    En la jerga de Socket.IO, una habitación (room) es un canal arbitrario al que los sockets pueden unirse y salir. Se puede utilizar para transmitir eventos a un subconjunto de clientes conectados:

    enter image description here

    ¡Eso es básicamente todo! Para referencia futura, toda la API se puede encontrar aquí (servidor) y aquí (cliente).

    Handling disconnections

    Ahora, resaltemos dos propiedades realmente importantes de Socket.IO:

    1. un cliente Socket.IO no siempre está conectado
    2. un servidor Socket.IO no almacena ningún evento

    Lo que significa que su aplicación debe poder sincronizar el estado local del cliente con el estado global del servidor después de una desconexión temporal.

    Nota: Lo que significa que su aplicación debe poder sincronizar el estado local del cliente con el estado global del servidor después de una desconexión temporal.

    En el contexto de nuestra aplicación de chat, esto implica que un cliente desconectado podría perderse algunos mensajes:

    enter image description here Veremos en los próximos pasos cómo podemos mejorar esto.

    Recuperación del estado de conexión

    Primero, manejemos las desconexiones fingiendo que no hubo desconexión: esta característica se llama "recuperación del estado de conexión" -> "Connection state recovery".

    Esta función almacenará temporalmente todos los eventos que envía el servidor e intentará restaurar el estado de un cliente cuando se vuelva a conectar:

    • restaurar sus habitaciones
    • envía cualquier evento perdido

    Debe estar habilitado en el lado del servidor: Incorporar en el index.js:

    const io = new Server(server, {
      connectionStateRecovery: {}
    });
    

    Video de Desconexion

    Como puede ver en el vídeo de arriba, el mensaje "en tiempo real" finalmente se entrega cuando se restablece la conexión.

    Nota: El botón "Desconectar" se agregó con fines de demostración.

    <form id="form" action="">
      <input id="input" autocomplete="off" /><button>Send</button>
      <button id="toggle-btn">Disconnect</button>
    </form>
    
    <script>
      const toggleButton = document.getElementById('toggle-btn');
    
      toggleButton.addEventListener('click', (e) => {
        e.preventDefault();
        if (socket.connected) {
          toggleButton.innerText = 'Connect';
          socket.disconnect();
        } else {
          toggleButton.innerText = 'Disconnect';
          socket.connect();
        }
      });
    </script>
    

    ¡Excelente! Ahora puedes preguntar:

    Pero esta es una característica increíble, ¿por qué no está habilitada de forma predeterminada?

    Hay varias razones para esto:

    no siempre funciona; por ejemplo, si el servidor falla abruptamente o se reinicia, es posible que el estado del cliente no se guarde no siempre es posible habilitar esta función al ampliarla

    Truco: Dicho esto, es realmente una gran característica ya que no es necesario sincronizar el estado del cliente después de una desconexión temporal (por ejemplo, cuando el usuario cambia de WiFi a 4G).

    Exploraremos una solución más general en el siguiente paso.

    Entrega del servidor

    Hay dos formas comunes de sincronizar el estado del cliente tras la reconexión:

    • o el servidor envía el estado completo
    • o el cliente realiza un seguimiento del último evento que ha procesado y el servidor envía las piezas faltantes

    Ambas son soluciones totalmente válidas y elegir una dependerá de su caso de uso. En este tutorial iremos con este último.

    Primero, persistamos los mensajes de nuestra aplicación de chat. Hoy en día hay muchas opciones excelentes, usaremos SQLite aquí. Instalemos los paquetes necesarios:

    npm install sqlite sqlite3
    

    Simplemente almacenaremos cada mensaje en una tabla SQL: index.js:

    const express = require('express');
    const { createServer } = require('node:http');
    const { join } = require('node:path');
    const { Server } = require('socket.io');
    const sqlite3 = require('sqlite3');
    const { open } = require('sqlite');
    
    async function main() {
      // open the database file
      const db = await open({
        filename: 'chat.db',
        driver: sqlite3.Database
      });
    
      // create our 'messages' table (you can ignore the 'client_offset' column for now)
      await db.exec(`
        CREATE TABLE IF NOT EXISTS messages (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            client_offset TEXT UNIQUE,
            content TEXT
        );
      `);
    
      const app = express();
      const server = createServer(app);
      const io = new Server(server, {
        connectionStateRecovery: {}
      });
    
      app.get('/', (req, res) => {
        res.sendFile(join(__dirname, 'index.html'));
      });
    
      io.on('connection', (socket) => {
        socket.on('chat message', async (msg) => {
          let result;
          try {
            // store the message in the database
            result = await db.run('INSERT INTO messages (content) VALUES (?)', msg);
          } catch (e) {
            // TODO handle the failure
            return;
          }
          // include the offset with the message
          io.emit('chat message', msg, result.lastID);
        });
      });
    
      server.listen(3000, () => {
        console.log('server running at http://localhost:3000');
      });
    }
    
    main();
    

    Luego, el cliente realizará un seguimiento del desplazamiento:

    index.html:

    <script>
      const socket = io({
        auth: {
          serverOffset: 0
        }
      });
    
      const form = document.getElementById('form');
      const input = document.getElementById('input');
      const messages = document.getElementById('messages');
    
      form.addEventListener('submit', (e) => {
        e.preventDefault();
        if (input.value) {
          socket.emit('chat message', input.value);
          input.value = '';
        }
      });
    
      socket.on('chat message', (msg, serverOffset) => {
        const item = document.createElement('li');
        item.textContent = msg;
        messages.appendChild(item);
        window.scrollTo(0, document.body.scrollHeight);
        socket.auth.serverOffset = serverOffset;
      });
    </script>
    

    Y finalmente el servidor enviará los mensajes faltantes al (re)conexión:

    index.js

    // [...]
    
    io.on('connection', async (socket) => {
      socket.on('chat message', async (msg) => {
        let result;
        try {
          result = await db.run('INSERT INTO messages (content) VALUES (?)', msg);
        } catch (e) {
          // TODO handle the failure
          return;
        }
        io.emit('chat message', msg, result.lastID);
      });
    
      if (!socket.recovered) {
        // if the connection state recovery was not successful
        try {
          await db.each('SELECT id, content FROM messages WHERE id > ?',
            [socket.handshake.auth.serverOffset || 0],
            (_err, row) => {
              socket.emit('chat message', row.content, row.id);
            }
          )
        } catch (e) {
          // something went wrong
        }
      }
    });
    
    // [...]
    

    Video de recuperacion desde una base de datos

    Como puede ver en el video de arriba, funciona tanto después de una desconexión temporal como de una actualización de página completa. Truco: La diferencia con la función "Recuperación del estado de conexión" es que es posible que una recuperación exitosa no necesite llegar a su base de datos principal (puede recuperar los mensajes de una secuencia Redis, por ejemplo).

    Bien, ahora hablemos de la entrega al cliente (client delivery).

    Client delivery

    Veamos cómo podemos asegurarnos de que el servidor siempre reciba los mensajes enviados por los clientes. INFO: De forma predeterminada, Socket.IO proporciona una garantía de entrega "como máximo una vez" (también conocida como "disparar y olvidar"), lo que significa que no habrá reintento en caso de que el mensaje no llegue al servidor.

    Buffered events

    Cuando se desconecta un cliente, cualquier llamada a socket.emit() se almacena en búfer hasta la reconexión: Buffered events En el vídeo de arriba, el mensaje "en tiempo real" se almacena en el búfer hasta que se restablece la conexión.

    Este comportamiento podría ser totalmente suficiente para su aplicación. Sin embargo, hay algunos casos en los que se podría perder un mensaje:

    • la conexión se corta mientras se envía el evento
    • el servidor falla o se reinicia mientras se procesa - el evento la base de datos no está disponible temporalmente

    At least once

    Podemos implementar una garantía de "al menos una vez:

    • manualmente con acuse de recibo:

      function emit(socket, event, arg) { socket.timeout(5000).emit(event, arg, (err) => { if (err) { // no ack from the server, let's retry emit(socket, event, arg); } }); }

      emit(socket, 'hello', 'world');

    • o con la opción retries reintentos:

      const socket = io({
        ackTimeout: 10000,
        retries: 3
      });
      

      socket.emit('hello', 'world');

    En ambos casos, el cliente volverá a intentar enviar el mensaje hasta obtener un acuse de recibo del servidor:

    io.on('connection', (socket) => {
      socket.on('hello', (value, callback) => {
        // once the event is successfully handled
        callback();
      });
    })
    

    Truco: Con la opción de reintentos (retries) se garantiza el orden de los mensajes, ya que los mensajes se ponen en cola y se envían uno por uno. Este no es el caso de la primera opción.

    Exactly once

    El problema con los reintentos es que el servidor ahora podría recibir el mismo mensaje varias veces, por lo que necesita una forma de identificar de forma única cada mensaje y almacenarlo solo una vez en la base de datos.

    Veamos cómo podemos implementar una garantía "exactamente una vez" en nuestra aplicación de chat.

    Comenzaremos asignando un identificador único a cada mensaje del lado del cliente:

    index.html:

    <script>
      let counter = 0;
    
      const socket = io({
        auth: {
          serverOffset: 0
        },
        // enable retries
        ackTimeout: 10000,
        retries: 3,
      });
    
      const form = document.getElementById('form');
      const input = document.getElementById('input');
      const messages = document.getElementById('messages');
    
      form.addEventListener('submit', (e) => {
        e.preventDefault();
        if (input.value) {
          // compute a unique offset
          const clientOffset = `${socket.id}-${counter++}`;
          socket.emit('chat message', input.value, clientOffset);
          input.value = '';
        }
      });
    
      socket.on('chat message', (msg, serverOffset) => {
        const item = document.createElement('li');
        item.textContent = msg;
        messages.appendChild(item);
        window.scrollTo(0, document.body.scrollHeight);
        socket.auth.serverOffset = serverOffset;
      });
    </script>
    

    Nota: El atributo socket.id es un identificador aleatorio de 20 caracteres que se asigna a cada conexión.

    También podríamos haber usado getRandomValues() para generar un desplazamiento único.

    Y luego almacenamos este desplazamiento junto con el mensaje en el lado del servidor:

    index.js

    // [...]
    
    io.on('connection', async (socket) => {
      socket.on('chat message', async (msg, clientOffset, callback) => {
        let result;
        try {
          result = await db.run('INSERT INTO messages (content, client_offset) VALUES (?, ?)', msg, clientOffset);
        } catch (e) {
          if (e.errno === 19 /* SQLITE_CONSTRAINT */ ) {
            // the message was already inserted, so we notify the client
            callback();
          } else {
            // nothing to do, just let the client retry
          }
          return;
        }
        io.emit('chat message', msg, result.lastID);
        // acknowledge the event
        callback();
      });
    
      if (!socket.recovered) {
        try {
          await db.each('SELECT id, content FROM messages WHERE id > ?',
            [socket.handshake.auth.serverOffset || 0],
            (_err, row) => {
              socket.emit('chat message', row.content, row.id);
            }
          )
        } catch (e) {
          // something went wrong
        }
      }
    });
    
    // [...]
    

    De esta manera, la restricción UNIQUE en la columna cliente_offset evita la duplicación del mensaje. Precaucion: No olvides reconocer el evento, de lo contrario el cliente seguirá intentándolo (hasta tiempos de reintento).

    socket.on('chat message', async (msg, clientOffset, callback) => {
      // ... and finally
      callback();
    });
    

    Info:

    Nuevamente, la garantía predeterminada ("como máximo una vez") -> ("at most once") puede ser suficiente para su aplicación, pero ahora sabe cómo hacerla más confiable.

    Scaling horizontally (Escalado horizontalmente)

    Ahora que nuestra aplicación es resistente a interrupciones temporales de la red, veamos cómo podemos escalarla horizontalmente para poder admitir miles de clientes simultáneos. Nota: - El escalado horizontal (también conocido como "escalado") significa agregar nuevos servidores a su infraestructura para hacer frente a nuevas demandas - El escalado vertical (también conocido como "escalado") significa agregar más recursos (potencia de procesamiento, memoria, almacenamiento, ...) a su infraestructura existente

    Primer paso: usemos todos los núcleos disponibles del host. De forma predeterminada, Node.js ejecuta su código Javascript en un solo subproceso, lo que significa que incluso con una CPU de 32 núcleos, solo se utilizará un núcleo. Afortunadamente, el módulo de clúster Node.js proporciona una manera conveniente de crear un subproceso de trabajador por núcleo.

    También necesitaremos una forma de reenviar eventos entre los servidores Socket.IO. A este componente lo llamamos "Adaptador".

    enter image description here Entonces, instalemos el adaptador del clúster:

    npm install @socket.io/cluster-adapter
    

    Ahora lo conectamos: index.js

    const express = require('express');
    const { createServer } = require('node:http');
    const { join } = require('node:path');
    const { Server } = require('socket.io');
    const sqlite3 = require('sqlite3');
    const { open } = require('sqlite');
    const { availableParallelism } = require('node:os');
    const cluster = require('node:cluster');
    const { createAdapter, setupPrimary } = require('@socket.io/cluster-adapter');
    
    if (cluster.isPrimary) {
      const numCPUs = availableParallelism();
      // create one worker per available core
      for (let i = 0; i < numCPUs; i++) {
        cluster.fork({
          PORT: 3000 + i
        });
      }
    
      // set up the adapter on the primary thread
      return setupPrimary();
    }
    
    async function main() {
      const app = express();
      const server = createServer(app);
      const io = new Server(server, {
        connectionStateRecovery: {},
        // set up the adapter on each worker thread
        adapter: createAdapter()
      });
    
      // [...]
    
      // each worker will listen on a distinct port
      const port = process.env.PORT;
    
      server.listen(port, () => {
        console.log(`server running at http://localhost:${port}`);
      });
    }
    
    main();
    

    ¡Eso es todo! Esto generará un subproceso de trabajador por CPU disponible en su máquina. Veámoslo en acción:

    Escalado del Cluster

    Como puede ver en la barra de direcciones, cada pestaña del navegador está conectada a un servidor Socket.IO diferente y el adaptador simplemente reenvía los eventos del mensaje de chat entre ellos. Truco: Actualmente existen 5 implementaciones oficiales de adaptadores:

    • el adaptador Redis
    • el adaptador Redis Streams
    • el adaptador MongoDB
    • el adaptador Postgres
    • el adaptador de clúster

    Para que puedas elegir el que mejor se adapta a tus necesidades. Sin embargo, tenga en cuenta que algunas implementaciones no admiten la función de recuperación del estado de conexión; puede encontrar la matriz de compatibilidad aquí.

    Nota: en la mayoría de los casos, también deberá asegurarse de que todas las solicitudes HTTP de una sesión de Socket.IO lleguen al mismo servidor (también conocido como "sesión adhesiva"). Sin embargo, esto no es necesario aquí, ya que cada servidor Socket.IO tiene su propio puerto.

    ¡Y eso finalmente completa nuestra aplicación de chat! En este tutorial hemos visto cómo:

    • enviar un evento entre el cliente y el servidor
    • transmitir un evento a todos o a un subconjunto de clientes conectados
    • manejar desconexiones temporales
    • ampliar

    Ahora debería tener una mejor descripción general de las funciones proporcionadas por Socket.IO. ¡Ahora es su momento de crear su propia aplicación en tiempo real!

    Notas finales

    Código final del servidor

    index.js

    const express = require('express');
    const { createServer } = require('node:http');
    const { join } = require('node:path');
    const { Server } = require('socket.io');
    const sqlite3 = require('sqlite3');
    const { open } = require('sqlite');
    const { availableParallelism } = require('node:os');
    const cluster = require('node:cluster');
    const { createAdapter, setupPrimary } = require('@socket.io/cluster-adapter');
    
    if (cluster.isPrimary) {
      const numCPUs = availableParallelism();
      for (let i = 0; i < numCPUs; i++) {
        cluster.fork({
          PORT: 3000 + i
        });
      }
    
      return setupPrimary();
    }
    
    async function main() {
      const db = await open({
        filename: 'chat.db',
        driver: sqlite3.Database
      });
    
      await db.exec(`
        CREATE TABLE IF NOT EXISTS messages (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          client_offset TEXT UNIQUE,
          content TEXT
        );
      `);
    
      const app = express();
      const server = createServer(app);
      const io = new Server(server, {
        connectionStateRecovery: {},
        adapter: createAdapter()
      });
    
      app.get('/', (req, res) => {
        res.sendFile(join(__dirname, 'index.html'));
      });
    
      io.on('connection', async (socket) => {
        socket.on('chat message', async (msg, clientOffset, callback) => {
          let result;
          try {
            result = await db.run('INSERT INTO messages (content, client_offset) VALUES (?, ?)', msg, clientOffset);
          } catch (e) {
            if (e.errno === 19 /* SQLITE_CONSTRAINT */ ) {
              callback();
            } else {
              // nothing to do, just let the client retry
            }
            return;
          }
          io.emit('chat message', msg, result.lastID);
          callback();
        });
    
        if (!socket.recovered) {
          try {
            await db.each('SELECT id, content FROM messages WHERE id > ?',
              [socket.handshake.auth.serverOffset || 0],
              (_err, row) => {
                socket.emit('chat message', row.content, row.id);
              }
            )
          } catch (e) {
            // something went wrong
          }
        }
      });
    
      const port = process.env.PORT;
    
      server.listen(port, () => {
        console.log(`server running at http://localhost:${port}`);
      });
    }
    
    main();
    

    Código de cliente final

    index.html

        <!DOCTYPE html>
    <html>
      <head>
        <meta name="viewport" content="width=device-width,initial-scale=1.0">
        <title>Socket.IO chat</title>
        <style>
          body { margin: 0; padding-bottom: 3rem; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; }
    
          #form { background: rgba(0, 0, 0, 0.15); padding: 0.25rem; position: fixed; bottom: 0; left: 0; right: 0; display: flex; height: 3rem; box-sizing: border-box; backdrop-filter: blur(10px); }
          #input { border: none; padding: 0 1rem; flex-grow: 1; border-radius: 2rem; margin: 0.25rem; }
          #input:focus { outline: none; }
          #form > button { background: #333; border: none; padding: 0 1rem; margin: 0.25rem; border-radius: 3px; outline: none; color: #fff; }
    
          #messages { list-style-type: none; margin: 0; padding: 0; }
          #messages > li { padding: 0.5rem 1rem; }
          #messages > li:nth-child(odd) { background: #efefef; }
        </style>
      </head>
      <body>
        <ul id="messages"></ul>
        <form id="form" action="">
          <input id="input" autocomplete="off" /><button>Send</button>
        </form>
        <script src="/socket.io/socket.io.js"></script>
        <script>
          let counter = 0;
    
          const socket = io({
            ackTimeout: 10000,
            retries: 3,
            auth: {
              serverOffset: 0
            }
          });
    
          const form = document.getElementById('form');
          const input = document.getElementById('input');
          const messages = document.getElementById('messages');
    
          form.addEventListener('submit', (e) => {
            e.preventDefault();
            if (input.value) {
              const clientOffset = `${socket.id}-${counter++}`;
              socket.emit('chat message', input.value, clientOffset);
              input.value = '';
            }
          });
    
          socket.on('chat message', (msg, serverOffset) => {
            const item = document.createElement('li');
            item.textContent = msg;
            messages.appendChild(item);
            window.scrollTo(0, document.body.scrollHeight);
            socket.auth.serverOffset = serverOffset;
          });
        </script>
      </body>
    </html>
    

    Tarea

    Aquí tienes algunas ideas para mejorar la aplicación:

    • Transmita un mensaje a los usuarios conectados cuando alguien se conecta o desconecta.
    • Agregue soporte para apodos.
    • No envíes el mismo mensaje al usuario que lo envió. En su lugar, agregue el mensaje directamente tan pronto como presione enter.
    • Agregar “{user} es escribir” funcionalidad.
    • Muestra quién está en línea.
    • Agregue mensajería privada.
    • Comparte tus mejoras

    Obteniendo este ejemplo

    Puedes encontrarlo en GitHub aquí.

    git clone https://github.com/socketio/chat-example.git
    
  • Publicado en

    Un grupo y un círculo son dos conceptos relacionados con la gestión de usuarios y permisos, pero con funciones y propósitos ligeramente diferentes: En resumen, usar grupos es preferible cuando se requiere una gestión de usuarios y permisos más estructurada y ampliamente integrada en Nextcloud, mientras que los círculos son más apropiados para colaboraciones informales con opciones de configuración más flexibles pero limitadas a la aplicación de Contactos

    Grupos

    • Funcionalidad: Un grupo es una estructura de organización para usuarios en Nextcloud. Permite asignar permisos y derechos a varios usuarios de manera colectiva.
    • Uso: Los grupos son útiles para simular departamentos o equipos de trabajo, permitiendo la gestión de permisos y acceso a recursos compartidos de manera eficiente.
    • Creación: Los grupos se crean en la sección de usuarios del panel de administración de Nextcloud.

    Círculos

    • Funcionalidad: Un círculo es una característica de la aplicación de Contactos en Nextcloud. Es una forma de colaboración informal que permite a los usuarios crear y gestionar grupos de contactos, compartir archivos y carpetas, y participar en conversaciones de Talk.
    • Uso: Los círculos son ideales para colaboraciones informales, como eventos, sesiones de ideas o talleres, donde se requiere una estructura más flexible que los grupos tradicionales.
    • Creación: Los círculos se crean en la aplicación de Contactos, donde se pueden agregar miembros, configurar roles y opciones de visibilidad.

    En resumen, los grupos son estructuras de organización para usuarios con permisos y derechos definidos, mientras que los círculos son grupos de colaboración informales con roles y opciones de configuración específicas para compartir archivos y carpetas.

  • Publicado en

    Instalacion del Archivo de Ratings Elo en Vega

    Hay que ser muy cuidadoso para poder instalar el Archivo de la FADA para que funciones en el Vega

    Para ello vamos a la FADA

    [Ficheros Vega ELO FIDE y FIDE FADA1]

    Tendremos que descargarnos ambos el Fide-Fada y el FADA, y guardandolos con el Mes en curso y FIDE-FADA o FADA. Hay que tener cuidado porque uno sobreescribiria el otro y son distintos.

    Son xls y abrirlo con LibreOffice Calc Seleccionaremos Guardar como ... Formato csv y como ejemplo: Elo-Fide-Fada-05-2024.csv

    Nos saldra en que formato guardarlo, odc o csv y elegiremos CSV. Nos saldra un cuadro de dialogo indicando el Delimitador de campos: y pondremos ;

    En el delimitador de cadena, lo dejamos vacio sin ningun espacio dentro.. Y le damos as Aceptar

    Una vez guardado nos vamos a Vega y en la sección inferior donde pone archivo. Elegimos que es un Archivo Vega y buscamos el archivo que hicimos anterior.

    Para comprobar que esta bien hecho, abrimos o creamos un Torneo e para añadir jugadores abajo a la derecha hay un campo de búsqueda, y con solo tre letras ya aparecen jugadores, señalamos uno, o bien pulsamos enter en ese jugador y se debería añadir con todos sus datos.

    Si hemos convertido ambos ficheros anteriores con FADA -FIDE y FADA, lo tenemos tener separados y comprobar que en uno solo añade FADA y en otro ambos.