Neste tutorial, vamos configurar a comunicação usando o protocolo UDP em um aplicativo React Native. O aplicativo React Native pode atuar como um servidor ou cliente UDP. Usamos um computador com um script Python como parceiro de comunicação.
Configurar o React Native
Primeiro, crie um projeto React Native UDPTerminalApp
Instalar as bibliotecas udp e netinfo
npm install react-native-udp npm install react-native-network-info
Descrição do código de aplicação
Importação
Para utilizar as bibliotecas, primeiro importamos o seu código.
import dgram from 'react-native-udp' import UdpSocket from 'react-native-udp/lib/types/UdpSocket.js'; import { NetworkInfo } from 'react-native-network-info'
Componente principal
Em seguida, criamos o componente funcional que nos permitirá gerir a comunicação do servidor
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 é True se a aplicação se comportar como um servidor e false se se comportar como um cliente
- connectionStatus apresenta o estado da ligação
- socket contém a variável de socket para a comunicação
- ipAddress contém o endereço IP do dispositivo Android na rede
- ipServer contém o endereço IP introduzido na interface
- messageText contém o texto digitado na interface
- messageToSend contém a mensagem a enviar ao cliente ou ao servidor
- receivedMessage contém mensagens recebidas do cliente ou do servidor
Gestão da comunicação UDP
O código utilizado para gerir a comunicação UDP, seja no modo servidor ou cliente, está inteiramente contido num 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]);
A função fetchIpAdress é utilizada para obter o endereço IP do dispositivo.
No modo de servidor
- criamos o socket dgram.createSocket
- quando uma mensagem é recebida: enviamos messageToSend ao cliente
- De seguida, apresentamos a mensagem recebida
- ligamos o servidor à porta 8888
No modo cliente
- criamos o socket dgram.createSocket
- ligamos o cliente ao porto 8887
Na seguinte função
- enviamos a mensagem messageToSend para o servidor
- depois aguardamos a resposta, que apresentamos em receivedMessage
Função de envio de mensagens
Também definimos uma função que actualiza a mensagem enviada no modo Servidor e envia a mensagem no modo Cliente.
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") }); };
Definição do ecrã
Por fim, a interface gráfica da aplicação é definida da seguinte forma:
- O título da aplicação
- O endereço IP do dispositivo
- Um botão para selecionar o modo cliente ou servidor
- Uma inserção para editar a mensagem a enviar
- Um botão de envio
- Uma inserção para especificar o endereço IP do servidor no modo de cliente
- Uma caixa de texto para apresentar as mensagens recebidas
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.: o estilo da interface é definido na secção
Cliente UDP React-Native – Servidor UDP Python
Neste exemplo, configuramos a aplicação como um cliente e o computador como um servidor. Enviaremos uma mensagem da aplicação para o servidor, que enviará uma resposta ao cliente.
Código do servidor UDP Python
#!/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)
Nota: Para escutar em todos os endereços locais, utilizamos 0.0.0.0. Se quiser testar a comunicação entre o cliente e o servidor no mesmo computador, pode utilizar o endereço 127.0.0.1.
Na aplicação, na caixa ServerIp, introduza o endereço IP do computador (aqui: 192.168.1.70). Em seguida, modifique a mensagem a enviar e prima o botão ENVIAR.
Terminal Python
UDP server up and listening on port 8888 ('192.168.1.5', 8887) > b'Hello server' ('192.168.1.5', 8887) > b'Hello server'
No terminal Python, recebemos a mensagem “Hello server” (Olá servidor) enviada pela aplicação. O servidor UDP envia de volta “Hello UDP Client” (Olá cliente UDP).
Servidor UDP React-Native – Cliente UDP Python
Aqui, configuramos a aplicação como um servidor e o computador como um cliente. No código Python, introduzimos o endereço IP do servidor.
Cliente UDP Python
#!/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)
Terminal Python
('192.168.1.5', 8888) > b'Hello client ' ('192.168.1.5', 8888) > b'Hello client '
Código completo da aplicação
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', } });
Aplicações
- Controle o seu projeto Arduino, ESP32 ou Raspberry Pi com uma aplicação