Vamos a crear una aplicación React Native que actuará como cliente Websockets y será capaz de comunicarse con un servidor remoto. WebSockets es un protocolo de comunicación web popular, simple y robusto que permite la comunicación en tiempo real entre cliente y servidor.
Hardware
- Dispositivo Android
- Ordenador para programar
- Un cable USB para conectar el dispositivo Android al PC
Configuración del proyecto React Native
Para comunicarnos a través de Websocket, vamos a utilizar la librería Websocket, react-use-websocket
npm install --save react-use-websocket
Uso de la biblioteca
De la biblioteca, importamos los objetos y funciones que nos interesan
import useWebSocket, { ReadyState } from 'react-use-websocket';
- ReadyState estado de la conexión con el servidor
- useWebScoket se utiliza para inicializar una conexión websockets
A continuación, creamos un componente funcional con los estados deseados
const [ipAddress, setIpAddress] = useState(''); const [ipServer, setIpServer] = useState('ws://192.168.1.52:8765'); const [messageText, setMessageText] = useState(""); const [messageHistory, setMessageHistory] = useState([]); const { sendMessage, sendJsonMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(ipServer); const connectionStatus = { [ReadyState.CONNECTING]: 'Connecting', [ReadyState.OPEN]: 'Open', [ReadyState.CLOSING]: 'Closing', [ReadyState.CLOSED]: 'Closed', [ReadyState.UNINSTANTIATED]: 'Uninstantiated', }[readyState]; const paramCmd = { type: 'setParam', param1: 30, param2: 2.3, }
En los estados, recuperamos las funciones y estados de useWebSokcet
- sendMessage la función para enviar mensajes en formato String
- sendJsonMessage para enviar mensajes en formato JSON
- lastMessage contiene la respuesta del servidor en formato String
- lastJsonMessage contiene el último mensaje del servidor en formato JSON
- readyState contiene el estado de la conexión
También definimos una constante en formato Json paramCmd.
Utilizamos un hook useEffect para recuperar la dirección IP del dispositivo y gestionar los mensajes recibidos del servidor.
useEffect(() => { const fetchIpAddress = async () => { const ip = await NetworkInfo.getIPV4Address(); setIpAddress(ip); console.log("ip adresses ; ", ip) }; fetchIpAddress(); if (lastMessage !== null) { setMessageHistory((prev) => prev.concat(lastMessage)); } if (lastJsonMessage !== null) { console.log(JSON.stringify(lastJsonMessage)); } return () => { }; }, [lastMessage, setMessageHistory,lastJsonMessage]);
Por último, creamos el renderizado de la aplicación con los siguientes elementos
- un texte pour afficher l’adresse IP de l’appareil
- un botón para enviar un mensaje JSON
- cuadro de texto para escribir el mensaje en formato de texto
- Botón Enviar para enviar el mensaje
- para introducir la dirección del servidor
- cuadro de texto para mostrar las respuestas del servidor
return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC Websocket Terminal </Text> <ScrollView> <View style={styles.deviceItem}> <View style={{flex:2}}> <Text style={styles.deviceName}>{ipAddress}</Text> <Text style={styles.deviceInfo}>{connectionStatus}</Text> </View> <TouchableOpacity onPress={() => sendJSON()} disabled={readyState !== ReadyState.OPEN} style={styles.deviceButton}> <Text style={styles.buttonText}> Send JSON </Text> </TouchableOpacity> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Enter a message" value={messageText} onChangeText={(text) => setMessageText(text) } /> <TouchableOpacity onPress={() => sendMessage(messageText)} disabled={readyState !== ReadyState.OPEN} style={[styles.sendButton]}> <Text style={styles.buttonText}> SEND </Text> </TouchableOpacity> </View> <TextInput placeholder="Server IP" onChangeText={setIpServer} value={ipServer} /> <View style={{flex:1,minHeight:200}}> <Text>Received Message:</Text> <ScrollView style={styles.textOutput}> {lastMessage ? <Text>last message : {lastMessage.data}</Text> : null} {messageHistory.map((message, idx) => ( <Text key={idx}>{message ? message.data : null}</Text> ))} </ScrollView> </View> </ScrollView> </View> );
Creación de un servidor WebSockets con Python
Para probar nuestra aplicación, creamos un servidor websockets en el PC
#!/usr/bin/env python # python3 -m pip install websockets import json import asyncio from websockets.server import serve def getIpAddress(): import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) ipaddress = s.getsockname()[0] s.close() return ipaddress ipaddress = getIpAddress() port = 8765 async def echo(websocket): async for message in websocket: print("received from {}:{} : ".format(websocket.remote_address[0],websocket.remote_address[1]) + message) if('{' not in message): await websocket.send(message) else: request = json.loads(message) answer = {} if(request['type'] == 'setParam'): answer['type'] = request['type'] if(request['param1']<100 and request['param2']>1.2): answer['valid'] = True for key, val in request.items(): print("\t"+key+": ", val) else: answer['valid'] = False else: answer['type'] = 'unknown' answer['valid'] = False await websocket.send(json.dumps(answer)) async def main(): print("Server is activated on ws://{}:{}".format(ipaddress,port)) #async with serve(echo, "localhost", 8765): async with serve(echo, "0.0.0.0", port): await asyncio.Future() # run forever asyncio.run(main())
Resultados
Gracias a esta aplicación, podemos enviar Strings y JSON al servidor y mostrar sus respuestas
Código completo para la comunicación WebSockets con React Native
/** * https://reactnative.dev/docs/network * https://www.npmjs.com/package/react-use-websocket * https://github.com/robtaussig/react-use-websocket * test on python * https://websockets.readthedocs.io/en/stable/ */ import React, {useState, useEffect, useCallback} from 'react'; import { View, ScrollView, Text, TextInput, TouchableOpacity, StyleSheet} from 'react-native'; import { NetworkInfo } from 'react-native-network-info' import useWebSocket, { ReadyState } from 'react-use-websocket'; const WebsocketTerminal = () => { const [ipAddress, setIpAddress] = useState(''); const [ipServer, setIpServer] = useState('ws://192.168.1.52:8765'); const [messageText, setMessageText] = useState(""); const [messageHistory, setMessageHistory] = useState([]); const { sendMessage, sendJsonMessage, lastMessage, lastJsonMessage, readyState } = useWebSocket(ipServer); useEffect(() => { const fetchIpAddress = async () => { const ip = await NetworkInfo.getIPV4Address(); setIpAddress(ip); console.log("ip adresses ; ", ip) }; fetchIpAddress(); if (lastMessage !== null) { setMessageHistory((prev) => prev.concat(lastMessage)); } if (lastJsonMessage !== null) { console.log(JSON.stringify(lastJsonMessage)); } return () => { }; }, [lastMessage, setMessageHistory,lastJsonMessage]); const connectionStatus = { [ReadyState.CONNECTING]: 'Connecting', [ReadyState.OPEN]: 'Open', [ReadyState.CLOSING]: 'Closing', [ReadyState.CLOSED]: 'Closed', [ReadyState.UNINSTANTIATED]: 'Uninstantiated', }[readyState]; const sendJSON = () => { const paramCmd = { type: 'setParam', param1: 30, param2: 2.3, } sendJsonMessage(paramCmd); } return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC Websocket Terminal </Text> <ScrollView> <View style={styles.deviceItem}> <View style={{flex:2}}> <Text style={styles.deviceName}>{ipAddress}</Text> <Text style={styles.deviceInfo}>{connectionStatus}</Text> </View> <TouchableOpacity onPress={() => sendJSON()} disabled={readyState !== ReadyState.OPEN} style={styles.deviceButton}> <Text style={styles.buttonText}> Send JSON </Text> </TouchableOpacity> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Enter a message" value={messageText} onChangeText={(text) => setMessageText(text) } /> <TouchableOpacity onPress={() => sendMessage(messageText)} disabled={readyState !== ReadyState.OPEN} style={[styles.sendButton]}> <Text style={styles.buttonText}> SEND </Text> </TouchableOpacity> </View> <TextInput placeholder="Server IP" onChangeText={setIpServer} value={ipServer} /> <View style={{flex:1,minHeight:200}}> <Text>Received Message:</Text> <ScrollView style={styles.textOutput}> {lastMessage ? <Text>last message : {lastMessage.data}</Text> : null} {messageHistory.map((message, idx) => ( <Text key={idx}>{message ? message.data : null}</Text> ))} </ScrollView> </View> </ScrollView> </View> ); } export default WebsocketTerminal; let BACKGROUND_COLOR = "#161616"; //191A19 let BUTTON_COLOR = "#346751"; //1E5128 let ERROR_COLOR = "#C84B31"; //4E9F3D let TEXT_COLOR = "#ECDBBA"; //D8E9A8 var styles = StyleSheet.create({ mainBody: { flex: 1, justifyContent: 'center', backgroundColor: BACKGROUND_COLOR}, mainTitle:{ color: TEXT_COLOR, fontSize: 30, textAlign: 'center', borderBottomWidth: 2, borderBottomColor: ERROR_COLOR, }, backgroundVideo: { borderWidth: 2, borderColor: 'red', position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, webVideo: { borderWidth: 2, borderColor: 'green', position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, buttonText: { color: TEXT_COLOR, fontWeight: 'bold', fontSize: 12, textAlign: 'center', textAlignVertical: 'center', }, sendButton: { backgroundColor: BUTTON_COLOR, padding: 15, borderRadius: 15, margin: 2, paddingHorizontal: 20, }, deviceItem: { flexDirection: 'row', flex: 3, marginBottom: 2, }, deviceName: { fontSize: 14, fontWeight: 'bold', }, deviceInfo: { fontSize: 8, }, deviceButton: { backgroundColor: '#2196F3', padding: 10, borderRadius: 10, margin: 2, paddingHorizontal: 20, }, inputBar:{ flexDirection: 'row', justifyContent: 'space-between', margin: 5, }, textInput:{ backgroundColor: '#888888', margin: 2, borderRadius: 15, flex:3, }, textOutput:{ backgroundColor: '#333333', margin: 10, borderRadius: 2, borderWidth: 1, borderColor: '#EEEEEE', textAlignVertical: 'top', } });