Étiquettes : , ,

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 ».

react-native-udp-client Communication UDP avec React Native

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)

react-native-udp-server Communication UDP avec React Native

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

Sources