WEB3DEV

Cover image for Tutorial Nim-libp2p: Um Exemplo de Chat Ponto a Ponto (2)
Panegali
Panegali

Posted on • Atualizado em

Tutorial Nim-libp2p: Um Exemplo de Chat Ponto a Ponto (2)

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
Enter fullscreen mode Exit fullscreen mode

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:

1 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
Enter fullscreen mode Exit fullscreen mode

2 Saída do terminal após executar ipconfig no prompt de comando do Windows.

3 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
Enter fullscreen mode Exit fullscreen mode

Tente enviar o texto "hi" entre as duas máquinas para teste:

4 Saída de terminal na primeira máquina com IP 192.168.0.14

5 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:

6

Conecte-se à porta remota usando as informações recebidas.

Digite ip4/<the_other_IP>/tcp/55505/p2p/<the_other_peerID> como abaixo:

7Saí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] 
Enter fullscreen mode Exit fullscreen mode

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
"""
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

Crie um procedimento para ler e escrever mensagens e comandos simultaneamente.

proc readWriteLoop(p: ChatProto) {.async.} =
  await p.writeAndPrint()
Enter fullscreen mode Exit fullscreen mode

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)