Dans ce tutoriel, nous allons mettre en place une communication avec le protocole UDP sur une application React Native. L’application React Native pourra se comporter comme Serveur ou comme Client UDP. Nous utilisons un ordinateur avec un script Python comme partenaire de communication.
Configuration de React Native
Premièrement, créer un projet React Native UDPTerminalApp
Installer les librairies udp et netinfo
npm install react-native-udp
npm install react-native-network-info
Description du code de l’application
Importation
Pour utiliser les librairies, nous importons tout d’abord leur code.
import dgram from 'react-native-udp' import UdpSocket from 'react-native-udp/lib/types/UdpSocket.js'; import { NetworkInfo } from 'react-native-network-info'
Composant principal
Puis nous créons le composant fonctionnel qui nous permettra de gérer la communication Serveur/Client UDP.
const UDPTerminal = () => { const [isServer, setIsServer] = useState(false); const [connectionStatus, setConnectionStatus] = useState(''); const [socket, setSocket] = useState<UdpSocket>(); const [ipAddress, setIpAddress] = useState(''); const [ipServer, setIpServer] = React.useState(''); const [messageText, setMessageText] = useState(""); const [messageToSend, setMessageToSend] = useState("Hello from server"); const [receivedMessage, setReceivedMessage] = useState("");
- isServer est True si l’application se comporte comme un serveur et false si elle se comporte comme un client
- connectionStatus affiche le status de la connexion
- socket contient la variable socket de la communication
- ipAddress contient l’adresse IP de l’appareil Android sur le réseau
- ipServer contient l’adresse IP entrée sur l’interface
- messageText contient le texte tapé sur l’interface
- messageToSend contient le message à envoyer au client ou au serveur
- receivedMessage contient les messages reçu depuis le client ou le serveur
Gestion de la communication UDP
Le code qui permet la gestion de la communication UDP, que ce soit en mode serveur ou client, est entièrement contenu dans un hook useEffect
useEffect(() => { const fetchIpAddress = async () => { const ip = await NetworkInfo.getIPV4Address(); setIpAddress(ip); console.log("ip adresses ; ", ip) }; fetchIpAddress(); if (isServer) { // Configure app as server const server = dgram.createSocket('udp4'); server.on('message', (data, rinfo) => { server.send(messageToSend, undefined, undefined, rinfo?.port, rinfo?.address, (error) => { if (error) { console.log('Error sending message:', error); } else { console.log('Message sent correctly'); } }); console.log('Received message:', data.toString()); setReceivedMessage(receivedMessage => receivedMessage+data.toString()+"\n") }); server.on('listening', () => { console.log('Server listening on port:', server.address().port); setConnectionStatus(`Server listening on port ${server.address().port}`); }); try{ setReceivedMessage(""); server.bind(8888); setSocket(server); }catch(error){ console.log("error binding server",error); } } else { setConnectionStatus(`Server disconnected`); // Configure app as client const client = dgram.createSocket('udp4'); try{ setReceivedMessage(""); client.bind(8887); setSocket(client); }catch(error){ console.log("error binding client",error); } } return () => { socket && socket.close(); }; }, [isServer, messageToSend, messageText]);
La fonction fetchIpAdress permet de récupérer l’adresse IP de l’appareil.
En mode serveur
- nous créons le socket dgram.createSocket
- lorsqu’un message est reçu: nous envoyons messageToSend au client
- Puis nous affichons le message reçu
- nous lions le serveur au port 8888
En mode client
- nous créons le socket dgram.createSocket
- nous lions le client au port 8887
Dans la fonction suivante
- nous envoyons le message messageToSend au serveur
- puis nous attendons la réponse que nous affichons dans receivedMessage
Fonction envoyer message
Nous définissons également une fonction qui permet de mettre à jour le message envoyé en mode Server et d’envoyer le message en mode Client.
const sendMessage = () => { setMessageToSend(messageText); if (isServer) return; const client = socket; console.log("send message to "+ipServer) client.send(messageToSend, undefined, undefined, 8888, ipServer, (error) => { if (error) { console.log('Error sending message:', error); } else { console.log('Message sent correctly'); } }); client.on('message', async (message: { toString: () => string; }) => { setReceivedMessage(receivedMessage+message.toString()+"\n") }); };
Définition de l’affichage
Enfin l’interface graphique de l’application est défini comme suit:
- Le titre de l’application
- L’adresse IP de l’appareil
- Un bouton pour sélectionner le mode client ou serveur
- Un encart permettant d’éditer le message à envoyer
- Un bouton d’envoi
- Un encart pour spécifier l’adresse IP du serveur en mode client
- Une zone de texte pour afficher les messages reçu
return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC UDP Terminal </Text> <ScrollView> <View style={styles.deviceItem}> <View> <Text style={styles.deviceName}>{ipAddress}</Text> <Text style={styles.deviceInfo}>{connectionStatus}</Text> </View> <TouchableOpacity onPress={() => setIsServer(!isServer)} style={styles.deviceButton}> <Text style={styles.buttonText}> {isServer ? 'Server' : 'Client'} </Text> </TouchableOpacity> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Enter a message" value={messageText} onChangeText={(text) => setMessageText(text) } /> <TouchableOpacity onPress={() => sendMessage() } style={[styles.sendButton]}> <Text style={styles.buttonText}> SEND </Text> </TouchableOpacity> </View> <TextInput placeholder="Server IP" onChangeText={setIpServer} value={ipServer} /> <Text>Received Message:</Text> <TextInput editable = {false} multiline numberOfLines={20} maxLength={300} style={styles.textOutput} > {receivedMessage} </TextInput> </ScrollView> </View> );
N.B.: le style de l’interface est défini dans le fichier /src/styles/styles.jsx
React-Native UDP Client – Python UDP Server
Dans cet exemple, nous configurons l’application comme client et l’ordinateur comme serveur. Nous allons envoyer un message au serveur, depuis l’application, qui renverra une réponse au client.
Python UDP Server Code
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket localIP = "0.0.0.0" #"127.0.0.1" localPort = 8888 bufferSize = 1024 msgFromServer = "Hello UDP Client" bytesToSend = str.encode(msgFromServer) # Create a datagram socket UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) # Bind to address and ip UDPServerSocket.bind((localIP, localPort)) print("UDP server up and listening on port {}".format(localPort)) # Listen for incoming datagrams while(True): data,addr = UDPServerSocket.recvfrom(bufferSize) print("{} > {}".format(addr,data)) # Sending a reply to client UDPServerSocket.sendto(bytesToSend, addr)
N.B.: Pour écouter sur toutes les adresses locales nous utilisons 0.0.0.0. Si vous souhaitez tester la communication entre client et serveur sur le même ordinateur vous pouvez utiliser l’adresse 127.0.0.1
Sur l’application, dans l’encart ServerIp, entrez l’adresse IP de l’ordinateur (ici: 192.168.1.70). Modifiez ensuite le message à envoyer puis appuyer sur le bouton « SEND ».
Python terminal
UDP server up and listening on port 8888
('192.168.1.5', 8887) > b'Hello server'
('192.168.1.5', 8887) > b'Hello server'
Sur le terminal Python, nous recevons le message « Hello server » envoyé depuis l’application. Le serveur UDP renvoie « Hello UDP Client »
React-Native UDP Server – Python UDP Client
Ici, nous configurons l’application comme serveur et l’ordinateur comme client. Dans le code Python, nous entrons l’adresse IP du serveur.
Python UDP Client
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket import time HOST = "192.168.1.5" #Server IP #"127.0.0.1" local address PORT = 8888 bufferSize = 1024 counter=0 while True: # Create a UDP socket at client side with socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM) as client: # Send to server using created UDP socket client.sendto(str.encode("client counter : "+str(counter)), (HOST,PORT)) data,addr= client.recvfrom(bufferSize) msg = "{} > {}".format(addr,data) print(msg) counter+=1 time.sleep(1)
Python terminal
('192.168.1.5', 8888) > b'Hello client '
('192.168.1.5', 8888) > b'Hello client '
Code complet de l’application
App.tsx
/** * npm install react-native-udp * npm install react-native-network-info * */ import React, {useState, useEffect} from 'react'; import { View, ScrollView, Text, TextInput, PermissionsAndroid, Platform, TouchableOpacity } from 'react-native'; import dgram from 'react-native-udp' import UdpSocket from 'react-native-udp/lib/types/UdpSocket.js'; import { NetworkInfo } from 'react-native-network-info' import {styles} from "./src/styles/styles.jsx" const UDPTerminal = () => { const [isServer, setIsServer] = useState(false); const [connectionStatus, setConnectionStatus] = useState(''); const [socket, setSocket] = useState<UdpSocket>(); const [ipAddress, setIpAddress] = useState(''); const [ipServer, setIpServer] = React.useState(''); const [messageText, setMessageText] = useState(""); const [messageToSend, setMessageToSend] = useState("Hello from server"); const [receivedMessage, setReceivedMessage] = useState(""); useEffect(() => { const fetchIpAddress = async () => { const ip = await NetworkInfo.getIPV4Address(); setIpAddress(ip); console.log("ip adresses ; ", ip) }; fetchIpAddress(); if (isServer) { // Configure app as server const server = dgram.createSocket('udp4'); server.on('message', (data, rinfo) => { server.send(messageToSend, undefined, undefined, rinfo?.port, rinfo?.address, (error) => { if (error) { console.log('Error sending message:', error); } else { console.log('Message sent correctly'); } }); console.log('Received message:', data.toString()); if(receivedMessage.length>300){ setReceivedMessage(""); } setReceivedMessage(receivedMessage => receivedMessage+data.toString()+"\n") }); server.on('listening', () => { console.log('Server listening on port:', server.address().port); setConnectionStatus(`Server listening on port ${server.address().port}`); }); try{ setReceivedMessage(""); server.bind(8888); setSocket(server); }catch(error){ console.log("error binding server",error); } } else { setConnectionStatus(`Server disconnected`); // Configure app as client const client = dgram.createSocket('udp4'); try{ setReceivedMessage(""); client.bind(8887); setSocket(client); }catch(error){ console.log("error binding client",error); } } return () => { socket && socket.close(); }; }, [isServer, messageToSend, messageText]); const sendMessage = () => { setMessageToSend(messageText); if (isServer) return; const client = socket; console.log("send message to "+ipServer) client.send(messageToSend, undefined, undefined, 8888, ipServer, (error) => { if (error) { console.log('Error sending message:', error); } else { console.log('Message sent correctly'); } }); client.on('message', async (message: { toString: () => string; }) => { if(receivedMessage.length>300){ setReceivedMessage(""); } setReceivedMessage(receivedMessage+message.toString()+"\n") }); }; return ( <View style={styles.mainBody}> <Text style={styles.mainTitle}> AC UDP Terminal </Text> <ScrollView> <View style={styles.deviceItem}> <View> <Text style={styles.deviceName}>{ipAddress}</Text> <Text style={styles.deviceInfo}>{connectionStatus}</Text> </View> <TouchableOpacity onPress={() => setIsServer(!isServer)} style={styles.deviceButton}> <Text style={styles.buttonText}> {isServer ? 'Server' : 'Client'} </Text> </TouchableOpacity> </View> <View style={styles.inputBar}> <TextInput style={styles.textInput} placeholder="Enter a message" value={messageText} onChangeText={(text) => setMessageText(text) } /> <TouchableOpacity onPress={() => sendMessage() } style={[styles.sendButton]}> <Text style={styles.buttonText}> SEND </Text> </TouchableOpacity> </View> <TextInput placeholder="Server IP" onChangeText={setIpServer} value={ipServer} /> <Text>Received Message:</Text> <TextInput editable = {false} multiline numberOfLines={20} maxLength={300} style={styles.textOutput} > {receivedMessage} </TextInput> </ScrollView> </View> ); } export default UDPTerminal;
style.jsx
/* ./src/styles/styles.jsx */ import {StyleSheet, Dimensions} from 'react-native'; /** * links to palette * https://colorhunt.co/palette/161616346751c84b31ecdbba * */ //parameters let BACKGROUND_COLOR = "#161616"; //191A19 let BUTTON_COLOR = "#346751"; //1E5128 let ERROR_COLOR = "#C84B31"; //4E9F3D let TEXT_COLOR = "#ECDBBA"; //D8E9A8 const windowHeight = Dimensions.get('window').height; export const styles = StyleSheet.create({ mainBody: { flex: 1, justifyContent: 'center', height: windowHeight, color:TEXT_COLOR, backgroundColor: BACKGROUND_COLOR, }, mainTitle:{ color: TEXT_COLOR, fontSize: 30, textAlign: 'center', borderBottomWidth: 2, borderBottomColor: ERROR_COLOR, }, scanButton: { backgroundColor: BUTTON_COLOR, paddingBottom: 10, paddingTop: 10, borderRadius: 10, margin: 2, marginBottom: 10, marginTop: 10, paddingHorizontal: 20, fontWeight: 'bold', fontSize: 12, }, buttonText: { color: TEXT_COLOR, fontWeight: 'bold', fontSize: 12, textAlign: 'center', }, noDevicesText: { color: TEXT_COLOR, textAlign: 'center', marginTop: 10, fontStyle: 'italic', }, deviceItem: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 2, }, deviceName: { color: TEXT_COLOR, fontSize: 14, fontWeight: 'bold', }, deviceInfo: { color: TEXT_COLOR, fontSize: 10, }, deviceButton: { backgroundColor: BUTTON_COLOR, padding: 10, borderRadius: 10, margin: 2, paddingHorizontal: 20, fontWeight: 'bold', fontSize: 12, }, inputBar:{ flexDirection: 'row', justifyContent: 'space-between', margin: 5, }, textInput:{ backgroundColor: '#888888', margin: 2, borderRadius: 15, flex:3, }, sendButton: { backgroundColor: BUTTON_COLOR, padding: 15, borderRadius: 15, margin: 2, paddingHorizontal: 20, }, textOutput:{ backgroundColor: '#333333', margin: 10, borderRadius: 2, borderWidth: 1, borderColor: '#EEEEEE', textAlignVertical: 'top', } });
Applications
- Pilotez votre projet Arduino, ESP32 ou Raspberry Pi avec une application