Este artigo é o segundo de uma série de três.
Olá a todos, bem-vindos ao segundo artigo da série de tutoriais da nim-libp2p!
Este tutorial é para todos que estão interessados em construir aplicativos de chat ponto a ponto. Nenhuma experiência de programação em Nim é necessária.
Nesta parte, vamos orientá-lo sobre como chamar um ponto remoto e permitir que o usuário insira comandos personalizados para fazer isso. O código completo para esta parte pode ser encontrado em nosso repositório principal second.nim.
Espero que você ache útil. Bom aprendizado! ;)
Nota: Este tutorial está dividido em três partes conforme abaixo:
- Parte I: Configurar a função principal e usar multi-thread para processamento de entrada e saída.
- Parte II (agora): Chamar o ponto remoto e permitir comandos personalizados de entrada do usuário.
- Parte III: Configurar e estabelecer um nó libp2p.
Antes que você comece
O pré-requisito para executar este exemplo é ter dois computadores diferentes para executar dois pontos distintos. Não importa qual sistema operacional seu computador está usando.
Consulte a Parte III para saber como executá-lo no mesmo computador usando duas portas diferentes.
Executando o Exemplo
Vamos primeiro dar uma olhada em como nosso código final será executado.
Entre nim-libp2p
na pasta e digite o seguinte comando no terminal: ( Se você não configurou o repositório nim-libp2p, encontre instruções em nosso primeiro post.
nim c -r --threads:on examples/directchat.nim
Pressione Enter
para usar o endereço padrão para configurar sua porta. Sua porta estará funcionando com sucesso se você vir a seguinte saída:
Saída do terminal após executar directchat.nim e pressionar enter.
Refaça as etapas 1 e 2 no outro computador.
Encontre os endereços IPv4 de seus computadores. No meu caso, são 192.168.0.14
e 192.168.0.22
respectivamente.
ifconfig # MacOS ou Linux
ipconfig # Windows
Saída do terminal após executar ipconfig no prompt de comando do Windows.
Saída do terminal após executar ipconfig no terminal MacOS.
Envie o endereço IP e o peerID para o outro computador para se conectar à outra porta. Usaremos o netcat para transferir nossas strings. Digite o primeiro comando em um computador e o segundo no outro.
nc -l 8000 # chamar a conexão de entrada na porta 8000
nc 192.168.0.22 8000 # conecte-se à porta 8000 neste endereço IP
Tente enviar o texto "hi" entre as duas máquinas para teste:
Saída de terminal na primeira máquina com IP 192.168.0.14
Saída de terminal na primeira máquina com IP 192.168.0.22
Em seguida, envie o peerID desta máquina para a outra, assim:
Conecte-se à porta remota usando as informações recebidas.
Digite ip4/<the_other_IP>/tcp/55505/p2p/<the_other_peerID>
como abaixo:
Saída do terminal após executar o directchat.nim e inserir o endereço da porta remota..
Parabéns! Agora você pode enviar mensagens livremente entre as duas portas. Se você digitar em um dos terminais do seu computador como na janela acima, verá sua mensagem aparecer no terminal do outro computador.
Iniciar codificação
Primeiro, para nos ajudar a distinguir do primeiro arquivo de código, renomeie nosso arquivo start.nim para second.nim (ou crie uma cópia de start.nim e renomeie-o para second.nim).
Importe os módulos necessários. A primeira linha é para as funções de utilitários de tabelas de hash e strings. A terceira linha e abaixo é para construir nosso nó libp2p.
import tables, strformat, strutils
import chronos
import ../libp2p/[switch,
multistream,
crypto/crypto,
protocols/identify,
connection,
transports/transport,
transports/tcptransport,
multiaddress,
peerinfo,
peer,
protocols/protocol,
protocols/secure/secure,
protocols/secure/secio,
muxers/muxer,
muxers/mplex/mplex,
muxers/mplex/types]
Defina as seguintes constantes.
const ChatCodec = "/nim-libp2p/chat/1.0.0"
const DefaultAddr = "/ip4/127.0.0.1/tcp/55505"
const Help = """
Commands: /[?|hep|connect|disconnect|exit]
help: Prints this help
connect: dials a remote peer
disconnect: ends current session
exit: closes the chat
"""
O primeiro ChatCodec
é o identificador de protocolo do chat (codec significa identificador de protocolo). O segundo DefaultAddr
específica que nosso nó será executado na porta TCP 55505, uma porta aleatória que provavelmente não entrará em conflito com outras. Está seguindo o formato Multiaddress.
A terceira parte denota a string de saída quando digitamos o comando /help
.
Define nosso tipo de protocolo de chat, herdado do LPProtocol.
type ChatProto = ref object of LPProtocol
switch: Switch # um único ponto de entrada para discar e chamar portas
transp: StreamTransport # fluxos de transporte entre o descritor de arquivo de leitura e gravação
conn: Connection # criar e fechar fluxo de leitura e gravação
connected: bool # se o nó estiver conectado a outro porta
started: bool # se o nó foi iniciado
A palavra-chave type
especifica que este é um tipo personalizado chamado ChatProto, ref object
denota que é um tipo de referência, e of
significa que estamos herdando de LPProtocol
. O " LPProtocol" significa "Protocolo com Prefixo de Comprimento", que significa enviar junto com o tamanho da mensagem para que seja fácil verificar se os dados completos foram recebidos.
Usamos esse tipo personalizado para armazenar as informações do nó que vamos executar. As três primeiras variáveis armazenam os módulos utilitários que nosso nó usará e as três últimas armazenam o estado do nosso nó. A informação connected
é importante porque só podemos nos conectar a um único ponto neste exemplo de chat ponto a ponto.
Procedimentos que são usados posteriormente por cada módulo de utilitários:
switch
: montar, iniciar, parar, chamar
transp
: readLine, escrever
conn
: fechar, readLp (com prefixo de comprimento), writeLp
Crie um procedimento para chamar uma porta remota.
proc dialPeer(p: ChatProto, address: string) {.async.} =
let
multiAddr = MultiAddress.init(address).tryGet()
# dividir a parte peerId /p2p/...
peerIdBytes = multiAddr[multiCodec("p2p")]
.tryGet()
.protoAddress()
.tryGet()
remotePeer = PeerID.init(peerIdBytes).tryGet()
# dividir o endereço do fio
ip4Addr = multiAddr[multiCodec("ip4")].tryGet()
tcpAddr = multiAddr[multiCodec("tcp")].tryGet()
wireAddr = ip4Addr & tcpAddr
echo &"dialing peer: {multiAddr}"
p.conn = await p.switch.dial(remotePeer, @[wireAddr], ChatCodec)
p.connected = true
asyncSpawn p.readAndPrint()
O código acima dividirá o multiaddress corretamente para comunicar o peerId e o endereço de rede real ao comutador dial
.
Também imprime o multiaddress da porta ao qual estamos nos conectando usando echo
. O prefixo &
nos permite usar também a variável multiAddr
com {}
na string de saída. Em seguida, retorna um tipo Connection
após chamar para a porta e conectar-se com sucesso a ela. O estado connected
é assim definido como verdadeiro e a tarefa readAndPrint
é gerada.
Crie um procedimento para ler mensagens de chat enviadas de outra porta.
proc readAndPrint(p: ChatProto) {.async.} =
while true:
var strData = await p.conn.readLp(1024)
strData &= '\0'.uint8
var str = cast[cstring](addr strdata[0])
echo $p.switch.peerInfo.peerId & ": " & $str
await sleepAsync(100.millis)
Usamos while true
para acessar continuamente as mensagens da porta conectada, a cada 100 milissegundos. Em seguida, convertemos a mensagem no tipo string e a imprimimos no console (adicionando um terminador nulo, pois ele vem de apenas uma matriz de bytes).
Crie um procedimento para escrever mensagens no chat e executar comandos para conectar e desconectar ao ponto remoto.
proc writeAndPrint(p: ChatProto) {.async.} =
while true:
if not p.connected:
echo "type an address or wait for a connection:"
echo "type /[help|?] for help"
let line = await p.transp.readLine()
if line.startsWith("/help") or line.startsWith("/?") or not p.started:
echo Help
continue
if line.startsWith("/disconnect"):
echo "Ending current session"
if p.connected and p.conn.closed.not:
await p.conn.close()
p.connected = false
elif line.startsWith("/connect"):
if p.connected:
var yesno = "N"
echo "a session is already in progress, do you want end it [y/N]?"
yesno = await p.transp.readLine()
if yesno.cmpIgnoreCase("y") == 0:
await p.conn.close()
p.connected = false
elif yesno.cmpIgnoreCase("n") == 0:
continue
else:
echo "unrecognized response"
continue
echo "enter address of remote peer"
let address = await p.transp.readLine()
if address.len > 0:
await p.dialPeer(address)
elif line.startsWith("/exit"):
if p.connected and p.conn.closed.not:
await p.conn.close()
p.connected = false
await p.switch.stop()
echo "quitting..."
quit(0)
else:
if p.connected:
await p.conn.writeLp(line)
else:
try:
if line.startsWith("/") and "ipfs" in line:
await p.dialPeer(line)
except:
echo &"unable to dial remote peer {line}"
echo getCurrentExceptionMsg()
Crie um procedimento para ler e escrever mensagens e comandos simultaneamente.
proc readWriteLoop(p: ChatProto) {.async.} =
await p.writeAndPrint()
Neste caso vamos bloquear a execução, como você pode ver asyncSpawn
não foi usado.
Conclusão
Agora você aprendeu como chamar um porta remoto e como ler e executar os comandos de entrada do usuário simultaneamente.
No próximo tutorial, concluiremos a parte restante deste exemplo de chat direto, que trata do estabelecimento de um nó libp2p. Por favor, fique antenado!
Este artigo foi publicado por Lee Ting Ting e traduzido por Marcelo Panegali. Seu original em inglês pode ser encontrado aqui.
Top comments (0)