- Publicado en
Zed (IDE hecho en Rust)
Es un IDE fuertemente minimalista.
Para instalarlo en Linux:
curl https://zed.dev/install.sh | sh
Es un IDE fuertemente minimalista.
Para instalarlo en Linux:
curl https://zed.dev/install.sh | sh
Dependiendo la version de Windows, puede ya estar instalado wls o bien hay que descargarsela desde microsoft: Podemos ver la version de windows: Tecla Windows Logo + R, teclear winver
Si tecelamos wsl.es y no esta, tendremos que instalar con la version antigua.
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
y habilitar el Servicio de la Maquina Virtual:
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
Nota: para la instalacion es necesario Inicializar un PowerShell como administrador Si ya esta y podemos teclear wsl pasamos al siguiente paso. Ver las distribuciones disponible
wls --list --online
Teniendo un resultado parecido a este: A continuación, se muestra una lista de las distribuciones válidas que se pueden instalar. Instalar con 'wsl --install -d <Distribución>'.
NAME FRIENDLY NAME
Ubuntu Ubuntu
Debian Debian GNU/Linux
kali-linux Kali Linux Rolling
Ubuntu-18.04 Ubuntu 18.04 LTS
Ubuntu-20.04 Ubuntu 20.04 LTS
Ubuntu-22.04 Ubuntu 22.04 LTS
Ubuntu-24.04 Ubuntu 24.04 LTS
OracleLinux_7_9 Oracle Linux 7.9
OracleLinux_8_7 Oracle Linux 8.7
OracleLinux_9_1 Oracle Linux 9.1
openSUSE-Leap-15.5 openSUSE Leap 15.5
SUSE-Linux-Enterprise-Server-15-SP4 SUSE Linux Enterprise Server 15 SP4
SUSE-Linux-Enterprise-15-SP5 SUSE Linux Enterprise 15 SP5
openSUSE-Tumbleweed openSUSE Tumbleweed
wsl --install -d openSUSE-Leap-15.5
Y ya es una instalacion de linux
Para entornos de trabajo estables, es preferible una distribucion solida y que solo haya sido probada y dada de alta como estable.
wsl --install -d openSUSE-Leap-15.5
Una vez instalado, lo actualizamos con
sudo zypper update
salimos con exit.
Y para entrar, lo podemos hacer de tres manera WindowsKey +R -> wsl y enter En buscar wsl. O desde un shell de PowewShell: wls
Ya una vez entraso el el Subsistema, podemos instalar softwarde linux como si estubieramos en linux. Ejemplo para instalar rsync :
sudo zypper install rsync
Dependiendo la version de Windows, puede ya estar instalado wls o bien hay que descargarsela desde microsoft: Podemos ver la version de windows: Tecla Windows Logo + R, teclear winver
Si tecelamos wsl.es y no esta, tendremos que instalar con la version antigua.
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
y habilitar el Servicio de la Maquina Virtual:
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
Nota: para la instalacion es necesario Inicializar un PowerShell como administrador Si ya esta y podemos teclear wsl pasamos al siguiente paso. Ver las distribuciones disponible
wls --list --online
Teniendo un resultado parecido a este: A continuación, se muestra una lista de las distribuciones válidas que se pueden instalar. Instalar con 'wsl --install -d <Distribución>'.
NAME FRIENDLY NAME Ubuntu Ubuntu Debian Debian GNU/Linux kali-linux Kali Linux Rolling Ubuntu-18.04 Ubuntu 18.04 LTS Ubuntu-20.04 Ubuntu 20.04 LTS Ubuntu-22.04 Ubuntu 22.04 LTS Ubuntu-24.04 Ubuntu 24.04 LTS OracleLinux_7_9 Oracle Linux 7.9 OracleLinux_8_7 Oracle Linux 8.7 OracleLinux_9_1 Oracle Linux 9.1 openSUSE-Leap-15.5 openSUSE Leap 15.5 SUSE-Linux-Enterprise-Server-15-SP4 SUSE Linux Enterprise Server 15 SP4 SUSE-Linux-Enterprise-15-SP5 SUSE Linux Enterprise 15 SP5 openSUSE-Tumbleweed openSUSE Tumbleweed
wsl --install -d openSUSE-Leap-15.5
Y ya es una instalacion de linux
Para entornos de trabajo estables, es preferible una distribucion solida y que solo haya sido probada y dada de alta como estable.
wsl --install -d openSUSE-Leap-15.5
Una vez instalado, salimos con exit.
Y para entrar, lo podemos hacer de tres manera WindowsKey +R -> wsl y enter En buscar wsl. O desde un shell de PowewShell: wls
El desarrollo de esta script es para hacer copias de seguridad que ademas cada cierto tiempo haga una copia completa y guarde un numero especifico de completas, eliminando las mas antiguas., pero también hacer como git en control de versiones pero sin ramificaciones.
0.- Creamos una variable del path que queremos hacer la copia de seguridad. printenv para ver variables printenv HOME devuelve el path home
1.- Creamos un directorio para la copia de seguridad:
mkdir /mnt/backup
dentro de ella creamos la main para la copia maestra
mkdir /mnt/backup/main
y otra para las incrementales
mkdir /mnt/backup/incrementals
2.- Utilizaremos varios parámetros de rsync para hacer una copia de seguridad distinta. Usa el comando rsync con las siguientes opciones clave:
Preparando el directorio para las incrementales:
mkdir $(date +'%Y-%m-%d:%H%M')
Comando date para generar el directorio con la fecha y la hora como versión. Luego inyectar al comando mkdir para crear con la fecha: Quedaría algo parecido a: 2024-07-05:0914
Para respaldar el origen de la copia de seguridad. Podemos utilizar una variable para guardar la extensión del archivo y del directorio:
dateincr=$(date +'%Y-%m-%d:%H%M')
Para respaldar el directorio /home/origen/ al directorio /mnt/backup/ y almacenar versiones anteriores en un subdirectorio con la fecha:
rsync -ab --backup-dir=/mnt/backup/`date + %F%H%M`
-u --delete
--exclude=/mnt/backup/ /home/origen/
/mnt/backup/incrementales/
Script final en zsh:
#!/bin/zsh
#
# Black 0;30 Dark Gray 1;30
# Red 0;31 Light Red 1;31
# Green 0;32 Light Green 1;32
# Brown/Orange 0;33 Yellow 1;33
# Blue 0;34 Light Blue 1;34
# Purple 0;35 Light Purple 1;35
# Cyan 0;36 Light Cyan 1;36
# Light Gray 0;37 White 1;37
# Regular Colors
Black='\033[0;30m' # Black
Red='\033[0;31m' # Red
Green='\033[0;32m' # Green
Yellow='\033[0;33m' # Yellow
Blue='\033[0;34m' # Blue
Purple='\033[0;35m' # Purple
Cyan='\033[0;36m' # Cyan
White='\033[0;37m' # White
Orange='\033[1;33m' #Yellow 1;33
clear
BackupSource=$(printenv PWD)/Source/;
BackupMain=$(printenv PWD)/main/;
BackupIncrementals=$(printenv PWD)/incrementals/;
BackupDateIncrementals=$(date +'%Y-%m-%d:%H%M')
echo "La mecaniva de este script es la siguiente:";
echo "Del archivo Fuente (Source) hace previamente una copia inicial de diferencianasnteriores al archivo de incrementales de los archivos que se han detectado como Modificado ";
echo "Luego hace una actualizacion el archivo main con los nuevos cambios que se han detectado en el Source";
echo "la primera copia se denomina Full Backup";
echo '***************************************************';
echo '* *';
echo '* *';
echo "* ${Green} Sistema de copia de Seguridad Incremental ${White} *";
echo '* *';
echo "* ${Purple}El directorio Origen: *";
echo "* ${Cyan}$BackupSource *";
echo '* *';
echo '* *';
echo '* El directorio Main: *';
echo "* $BackupMain *";
echo '* *';
echo '* *';
echo '* El directorio de Incrementales: *';
echo "* ${Orange}$BackupIncrementals *";
echo '* *';
echo '* *';
echo '***************************************************';
echo 'Archivos en el directrio Fuente';
eza --color $BackupSource
echo 'Archivos en la Copia Main del Backup';
eza --color $BackupMain;
echo '______________________________________________________';
echo '\n';
echo 'Variable de generacion del la version de la Incremental';
echo 'BackupDateIncrementals: '$BackupDateIncrementals;
echo '\n';
echo 'Ejemplo de posible script hecho con la devolucion de variables:';
echo 'Crea el directorio para la Incremental:'
mkdir $BackupIncrementals/Backup-$BackupDateIncrementals;
rsync -avhb --delete --backup-dir=$BackupIncrementals/Backup-$BackupDateIncrementals $BackupSource $BackupMain;
# rsync -avhb --delete --backup-dir=/ruta/destino/copia_$(date +%d%m%Y%H%M) /ruta/origen/ /ruta/destino/
echo '\n';
echo 'Listado de directorio creados'
eza -al $BackupIncrementals;
echo
echo '***************************************************';
echo '* *';
echo '* *';
echo '* *';
echo "* ${Purple}Ficheros en Origen: *";
echo "* ${Cyan}$BackupSource *";
echo "* $(eza $BackupSource) *";
echo '* *';
echo '* *';
echo '* Ficheros en la main del Backup: *';
echo "* $(eza $BackupMain) *";
echo '* *';
echo '* *';
echo '* Los ficheros Incrementados: *';
echo "* $(eza $BackupIncrementals"Backup"-$BackupDateIncrementals ) *";
echo '* *';
echo '* *';
echo '***********************
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:
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/
sudo zypper install 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.
sudo grub2-install /dev/sda
Este comando instalará el gestor de arranque GRUB2 en el disco especificado.
Reinicia el sistema y deberías ver el menú de arranque de GRUB2 con las entradas para cada sistema operativo detectado.
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
" 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>
Para montar un sistema de archivos LVM, Hay que seguir estos pasos:
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
Listar los volúmenes lógicos:
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--- 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
--- 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
Montar el volumen lógico:
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>bash
$ sudo mount /dev/vgtest/lvdata2 /mnt
En mi caso voy a montar la rootmount /dev/almalinux/root /mnt/almalinux/root
Pero tambien la home:
mount /dev/almalinux/root /mnt/almalinux/root
Verificar el estado del montaje:
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
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:
Si ejecuta node index.js, debería ver lo siguiente:
Obteniendo en el navegador...localhost:8080 y no el 3000

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í:

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):

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.

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.

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); });
});
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);
});

Antes de continuar, hagamos un recorrido rápido por la API proporcionada por Socket.IO:
Los siguientes métodos están disponibles tanto para el cliente como para el servidor.
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 ] }
}); `

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:
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'
});
});
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'
});
});
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 ] } ]
});
Como hemos visto en el paso 5, puedes transmitir un evento a todos los clientes conectados con io.emit():
io.emit('hello', 'world');

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:

¡Eso es básicamente todo! Para referencia futura, toda la API se puede encontrar aquí (servidor) y aquí (cliente).
Ahora, resaltemos dos propiedades realmente importantes de Socket.IO:
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:
Veremos en los próximos pasos cómo podemos mejorar esto.
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:
Debe estar habilitado en el lado del servidor: Incorporar en el index.js:
const io = new Server(server, {
connectionStateRecovery: {}
});
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.
Hay dos formas comunes de sincronizar el estado del cliente tras la reconexión:
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).
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.
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:
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.
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.
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".
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:
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:
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:
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!
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();
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>
Aquí tienes algunas ideas para mejorar la aplicación:
Puedes encontrarlo en GitHub aquí.
git clone https://github.com/socketio/chat-example.git
cat ~/.mysql_history | sed 's/\040/ /g' > ~/.mysql_history.sed