Nous allons créer une application React Native qui servira de client Websockets et pourra communiquer avec un serveur distant. WebSockets est un protocole populaire de communication web simple et robuste permettant la communication en temps réel entre client et serveur.
Matériel
- Appareil Android
- Ordinateur pour la programmation
- Un câble USB pour connecter l’appareil Android au PC
Configuration du projet React Native
Pour communiquer via Websocket nous allons utiliser la librairie Websocket, react-use-websocket
npm install --save react-use-websocket
Utilisation de la librairie
A partir de la librairie, nous importons les objets et fonctions qui nous intéresse
import useWebSocket, { ReadyState } from 'react-use-websocket';
- ReadyState état de la connexion avec le serveur
- useWebScoket permet d’initialiser une connexion websockets
Nous créons ensuite un composant fonctionnel avec les états désirés
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, }
Dans les états nous récupérons les fonctions et états de useWebSokcet
- sendMessage la fonction pour envoyer des messages au format String
- sendJsonMessage pour pour envoyer des messages au format JSON
- lastMessage contient la réponse du serveur au format String
- lastJsonMessage contient le dernier message du serveur au format JSON
- readyState contient l’état de la connexion
Nous définissons aussi une constante au format Json paramCmd.
Nous utilisons un hook useEffect dans lequel nous récupérons l’adresse IP de l’appareil et nous gérons les messages reçus du serveur.
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]);
Enfin, nous créons le rendu de l’application avec les éléments suivant
- un texte pour afficher l’adresse IP de l’appareil
- un bouton pour envoyer un message JSON
- zone de texte pour le texte à écrire pour écrire le message au format texte
- bouton Send pour envoyer le message
- zone de texte pour entrer l’adresse du serveur
- zone de texte pour afficher les réponses du serveur
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> );
Création d’un serveur WebSockets avec Python
Pour tester notre application, nous créons un serveur websockets sur le 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())
Résultat
Grâce à cette application, nous pouvons envoyer des String et JSON au serveur et afficher ses réponses
Code complet de communication WebSockets avec 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', } });