Chat Demo

This example demonstrates how to create a simple chat application using libp2p.

$ python -m pip install libp2p
Collecting libp2p
...
Successfully installed libp2p-x.x.x
$ chat-demo
Run this from the same folder in another console:

chat-demo -p 8001 -d /ip4/127.0.0.1/tcp/8000/p2p/QmPouApKqyxJDy6YT21EXNS6efuNzvJ3W3kqRQxkQ77GFJ

Waiting for incoming connection...

Copy the line that starts with chat-demo -p 8001, open a new terminal in the same folder and paste it in:

$ chat-demo -p 8001 -d /ip4/127.0.0.1/tcp/8000/p2p/QmPouApKqyxJDy6YT21EXNS6efuNzvJ3W3kqRQxkQ77GFJ
Connected to peer /ip4/127.0.0.1/tcp/8000

You can then start typing messages in either terminal and see them relayed to the other terminal. To exit the demo, send a keyboard interrupt (Ctrl+C) in either terminal.

The full source code for this example is below:

  1import argparse
  2import logging
  3import sys
  4
  5import multiaddr
  6import trio
  7
  8from libp2p import (
  9    new_host,
 10)
 11from libp2p.custom_types import (
 12    TProtocol,
 13)
 14from libp2p.network.stream.net_stream import (
 15    INetStream,
 16)
 17from libp2p.peer.peerinfo import (
 18    info_from_p2p_addr,
 19)
 20
 21# Configure minimal logging
 22logging.basicConfig(level=logging.WARNING)
 23logging.getLogger("multiaddr").setLevel(logging.WARNING)
 24logging.getLogger("libp2p").setLevel(logging.WARNING)
 25
 26PROTOCOL_ID = TProtocol("/chat/1.0.0")
 27MAX_READ_LEN = 2**32 - 1
 28
 29
 30async def read_data(stream: INetStream) -> None:
 31    while True:
 32        read_bytes = await stream.read(MAX_READ_LEN)
 33        if read_bytes is not None:
 34            read_string = read_bytes.decode()
 35            if read_string != "\n":
 36                # Green console colour: 	\x1b[32m
 37                # Reset console colour: 	\x1b[0m
 38                print("\x1b[32m %s\x1b[0m " % read_string, end="")
 39
 40
 41async def write_data(stream: INetStream) -> None:
 42    async_f = trio.wrap_file(sys.stdin)
 43    while True:
 44        line = await async_f.readline()
 45        await stream.write(line.encode())
 46
 47
 48async def run(port: int, destination: str) -> None:
 49    from libp2p.utils.address_validation import (
 50        find_free_port,
 51        get_available_interfaces,
 52        get_optimal_binding_address,
 53    )
 54
 55    if port <= 0:
 56        port = find_free_port()
 57
 58    listen_addrs = get_available_interfaces(port)
 59    host = new_host()
 60    async with host.run(listen_addrs=listen_addrs), trio.open_nursery() as nursery:
 61        # Start the peer-store cleanup task
 62        nursery.start_soon(host.get_peerstore().start_cleanup_task, 60)
 63
 64        if not destination:  # its the server
 65
 66            async def stream_handler(stream: INetStream) -> None:
 67                nursery.start_soon(read_data, stream)
 68                nursery.start_soon(write_data, stream)
 69
 70            host.set_stream_handler(PROTOCOL_ID, stream_handler)
 71
 72            # Get all available addresses with peer ID
 73            all_addrs = host.get_addrs()
 74
 75            print("Listener ready, listening on:\n")
 76            for addr in all_addrs:
 77                print(f"{addr}")
 78
 79            # Use optimal address for the client command
 80            optimal_addr = get_optimal_binding_address(port)
 81            optimal_addr_with_peer = f"{optimal_addr}/p2p/{host.get_id().to_string()}"
 82            print(
 83                f"\nRun this from the same folder in another console:\n\n"
 84                f"chat-demo -d {optimal_addr_with_peer}\n"
 85            )
 86            print("Waiting for incoming connection...")
 87
 88        else:  # its the client
 89            maddr = multiaddr.Multiaddr(destination)
 90            info = info_from_p2p_addr(maddr)
 91            # Associate the peer with local ip address
 92            await host.connect(info)
 93            # Start a stream with the destination.
 94            # Multiaddress of the destination peer is fetched from the peerstore
 95            # using 'peerId'.
 96            stream = await host.new_stream(info.peer_id, [PROTOCOL_ID])
 97
 98            nursery.start_soon(read_data, stream)
 99            nursery.start_soon(write_data, stream)
100            print(f"Connected to peer {info.addrs[0]}")
101
102        await trio.sleep_forever()
103
104
105def main() -> None:
106    description = """
107    This program demonstrates a simple p2p chat application using libp2p.
108    To use it, first run 'python ./chat -p <PORT>', where <PORT> is the port number.
109    Then, run another host with 'python ./chat -p <ANOTHER_PORT> -d <DESTINATION>',
110    where <DESTINATION> is the multiaddress of the previous listener host.
111    """
112    example_maddr = (
113        "/ip4/[HOST_IP]/tcp/8000/p2p/QmQn4SwGkDZKkUEpBRBvTmheQycxAHJUNmVEnjA2v1qe8Q"
114    )
115    parser = argparse.ArgumentParser(description=description)
116    parser.add_argument("-p", "--port", default=0, type=int, help="source port number")
117    parser.add_argument(
118        "-d",
119        "--destination",
120        type=str,
121        help=f"destination multiaddr string, e.g. {example_maddr}",
122    )
123    args = parser.parse_args()
124
125    try:
126        trio.run(run, *(args.port, args.destination))
127    except KeyboardInterrupt:
128        pass
129
130
131if __name__ == "__main__":
132    main()