Peer-to-Peer
The Peer-to-Peer (P2P) pattern is a distributed application architecture that eliminates the need for a central server. Instead, individual nodes (peers) in the network share resources directly with each other. Each peer acts as both a client and a server, contributing its own resources (storage, bandwidth, processing power) to the network and consuming resources from other peers. This decentralization offers benefits like increased resilience, scalability, and reduced costs.
This pattern is particularly useful in scenarios where centralized control is undesirable or impractical, such as file sharing, content distribution, and collaborative systems. It’s also well-suited for applications that require high availability and can tolerate some level of inconsistency. P2P networks can be structured (with defined topologies) or unstructured (random connections), each offering different trade-offs in terms of efficiency and robustness.
Usage
- File Sharing: Applications like BitTorrent rely heavily on P2P to distribute large files efficiently. Users download pieces of a file from multiple peers simultaneously, reducing the load on any single source.
- Cryptocurrencies: Blockchain technologies, like those powering Bitcoin and Ethereum, are fundamentally P2P. Transactions are broadcast to the network and validated by multiple peers, ensuring security and transparency.
- Decentralized Social Networks: Platforms like Mastodon utilize P2P principles through federated servers (instances) that communicate with each other, allowing users to interact across different communities without a single point of control.
- Collaborative Editing: Some real-time collaborative editing tools use P2P to synchronize changes between users directly, reducing latency and improving responsiveness.
Examples
- BitTorrent: A widely used protocol for P2P file sharing. Users download and upload file segments concurrently, creating a swarm of peers that collectively distribute the content. The tracker initially helps peers find each other, but the actual file transfer happens directly between peers.
- IPFS (InterPlanetary File System): A P2P hypermedia protocol designed to make the web faster, safer, and more open. Content is addressed by its hash, and nodes store and serve content based on demand, creating a distributed content-addressable storage system.
- WebRTC: A free and open-source project that provides real-time communication capabilities directly between browsers and mobile applications. It uses P2P connections for audio and video streaming, eliminating the need for a dedicated signaling server for media transfer (though signaling is still required for connection setup).
Specimens
15 implementationsThe Peer-to-Peer pattern establishes direct communication and data exchange between participating entities (peers) without relying on a central server. Each peer acts as both a client and a server, sharing resources and responsibilities. In this Dart example, we simulate a basic peer-to-peer network using streams and stream controllers to represent peers exchanging messages. Each peer has a controller for sending to others and a stream to receive messages, effectively broadcasting messages to all connected peers. This can be extended with more sophisticated peer discovery and security. It’s idiomatic Dart due to its use of streams for asynchronous communication, mirroring how real-time event handling would be implemented.
// peers.dart
import 'dart:async';
class Peer {
final String id;
final StreamController<String> _controller = StreamController<String>.broadcast();
Peer(this.id);
Stream<String> get stream => _controller.stream;
Sink<String> get sink => _controller.sink;
void broadcast(String message) {
sink.add("$id: $message");
}
}
void main() {
final peer1 = Peer('Peer1');
final peer2 = Peer('Peer2');
final peer3 = Peer('Peer3');
peer1.broadcast('Hello, everyone!');
peer2.broadcast('Hi Peer1!');
peer3.broadcast('Greetings!');
peer1.stream.listen((message) => print(message));
peer2.stream.listen((message) => print(message));
peer3.stream.listen((message) => print(message));
}
The Peer-to-Peer (P2P) pattern allows individual nodes (peers) in a network to directly share resources without relying on a central server. Each peer acts as both a client and a server. This implementation uses Akka actors to represent peers. Each actor holds a shared resource (a simple string in this case) and can respond to requests from other peers to retrieve it. The PeerSystem coordinates the creation of peers and facilitates initial message exchange.
This approach is idiomatic Scala due to its use of actors for concurrent and distributed systems, immutability (the resource string), and message passing, fitting well with Scala’s functional and concurrent programming paradigms. The actor model handles complexity associated with managing peer connections and requests.
import akka.actor._
import scala.util.Random
object PeerSystem {
def main(args: Array[String]): Unit = {
val system = ActorSystem("PeerSystem")
val peer1 = system.actorOf(PeerActor.props("Peer 1: Initial Resource"), "peer1")
val peer2 = system.actorOf(PeerActor.props("Peer 2: Initial Resource"), "peer2")
val peer3 = system.actorOf(PeerActor.props("Peer 3: Initial Resource"), "peer3")
// Initial exchange - each peer requests from a random other peer
peer1 ! PeerActor.RequestResource(peer2)
peer2 ! PeerActor.RequestResource(peer3)
peer3 ! PeerActor.RequestResource(peer1)
//Allow time for messages to process
Thread.sleep(2000)
system.terminate()
}
}
object PeerActor {
sealed trait Message
case class RequestResource(requester: ActorRef) extends Message
case class Resource(data: String) extends Message
def props(initialResource: String) = Props(new PeerActor(initialResource))
}
class PeerActor(initialResource: String) extends Actor {
import PeerActor._
var resource = initialResource
override def receive: Receive = {
case RequestResource(requester) =>
println(s"${self.path.name} received request from ${requester.path.name}")
requester ! Resource(resource)
case Resource(data) =>
println(s"${self.path.name} received resource from ${sender().path.name}: ${data}")
resource = data //Update resource with received data
case _ => println(s"${self.path.name} received unknown message")
}
}
The Peer-to-Peer (P2P) pattern enables direct communication and data exchange between independent components (“peers”) without relying on a central server or intermediary. Each peer acts as both a client and a server, discovering and interacting with other peers directly. This implementation uses simple file-based “discovery” to allow peers to find each other. Each peer writes its address to a shared peers.txt file. Then, another peer reads this file to discover addresses for connection. PHP’s file system functions and basic socket programming are utilized for simplicity—a production system would use more robust discovery mechanisms.
<?php
/**
* Peer-to-Peer Example in PHP
*/
class Peer {
private $address;
private $port;
public function __construct(string $address, int $port) {
$this->address = $address;
$this->port = $port;
}
public function publishPeer(string $peersFile): void {
file_put_contents($peersFile, $this->address . ":" . $this->port . PHP_EOL, FILE_APPEND);
}
public function discoverPeers(string $peersFile): array {
$peers = [];
$lines = file($peersFile, FILE_IGNORE_NEW_LINES);
if ($lines) {
foreach ($lines as $line) {
list($addr, $port) = explode(":", $line);
$peers[] = new self($addr, (int)$port);
}
}
return $peers;
}
public function communicate(Peer $peer, string $message): string {
// Simple socket communication (error handling omitted for brevity)
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_connect($socket, $peer->address, $peer->port);
socket_send($socket, $message, strlen($message), 0);
$response = socket_read($socket, 2048);
socket_close($socket);
return $response;
}
public function getAddress(): string {
return $this->address . ":" . $this->port;
}
}
// Example Usage:
$peersFile = 'peers.txt';
// Peer 1 (Publishes itself)
$peer1 = new Peer('127.0.0.1', 8000);
$peer1->publishPeer($peersFile);
echo "Peer 1 published: " . $peer1->getAddress() . PHP_EOL;
// Peer 2 (Publishes itself)
$peer2 = new Peer('127.0.0.1', 8001);
$peer2->publishPeer($peersFile);
echo "Peer 2 published: " . $peer2->getAddress() . PHP_EOL;
// Peer 3 (Discovers and communicates)
$peer3 = new Peer('127.0.0.1', 8002);
$peers = $peer3->discoverPeers($peersFile);
echo "Peer 3 discovered peers: " . PHP_EOL;
foreach($peers as $peer){
echo " - " . $peer->getAddress() . PHP_EOL;
}
if ($peers) {
$response = $peer3->communicate($peers[0], "Hello from Peer 3!");
echo "Peer 3 received: " . $response . PHP_EOL;
} else {
echo "No peers discovered." . PHP_EOL;
}
?>
The Peer-to-Peer pattern establishes direct communication and data exchange between nodes (peers) in a network without relying on a central server. Each peer acts as both a client and a server, contributing resources and accessing resources from others. This example simulates a simple peer network using Ruby’s socket programming. Each peer can “publish” messages which are then broadcast to all connected peers. The implementation utilizes a separate thread for each connection to handle concurrent communication. This style is considered idiomatic Ruby due to its concise syntax and thread handling capabilities, promoting concurrent operations in a straightforward manner.
require 'socket'
require 'thread'
class Peer
def initialize(port)
@port = port
@server = TCPServer.new(@port)
@peers = []
start_listening
end
def start_listening
puts "Peer listening on port #{@port}"
loop do
client = @server.accept
thread = Thread.new(client) do |c|
@peers << c
puts "Peer connected: #{c.peeraddr[2]}"
begin
while true
message = c.gets.chomp
break if message.nil? || message.empty?
broadcast(message, c)
end
rescue => e
puts "Error with client: #{e.message}"
ensure
@peers.delete(c)
c.close
puts "Peer disconnected: #{c.peeraddr[2]}"
end
end
end
end
def broadcast(message, sender)
@peers.each do |peer|
next if peer == sender
begin
peer.puts message
rescue => e
puts "Error broadcasting to peer: #{e.message}"
@peers.delete(peer)
end
end
end
end
# Example Usage:
if __FILE__ == $0
port = ARGV[0].to_i || 5000
Peer.new(port)
end
The Peer-to-Peer (P2P) pattern enables direct communication and data exchange between individual nodes (peers) in a network, without relying on a central server. This implementation simulates a basic chat application where peers can send and receive messages directly. We use a simple Peer class to represent each participant. Each peer maintains a dictionary of connected peers and handles sending/receiving messages via closures. This leverages Swift’s first-class functions for event handling (message received) and its struct/class flexibility for peer representation. Utilizing dictionaries for peer management is a natural fit for Swift’s associative collections.
// Peer.swift
import Foundation
class Peer {
let id: String
private var connections: [String: Peer] = [:]
var onMessageReceived: ((String, String) -> Void)?
init(id: String) {
self.id = id
}
func connect(to peer: Peer) {
connections[peer.id] = peer
print("\(id) connected to \(peer.id)")
}
func disconnect(from peer: Peer) {
connections[peer.id] = nil
print("\(id) disconnected from \(peer.id)")
}
func sendMessage(message: String, to peerId: String) {
guard let targetPeer = connections[peerId] else {
print("Error: Peer \(peerId) not connected.")
return
}
targetPeer.receiveMessage(message: message, from: id)
}
private func receiveMessage(message: String, from peerId: String) {
print("\(id) received message '\(message)' from \(peerId)")
onMessageReceived?(peerId, message)
}
}
// Example Usage
let peer1 = Peer(id: "1")
let peer2 = Peer(id: "2")
let peer3 = Peer(id: "3")
peer1.connect(to: peer2)
peer2.connect(to: peer1)
peer1.connect(to: peer3)
peer1.onMessageReceived = { (senderId, message) in
print("Peer 1's handler: Received '\(message)' from \(senderId)")
}
peer1.sendMessage(message: "Hello from Peer 1!", to: peer2.id)
peer3.sendMessage(message: "Hi Peer 1!", to: peer1.id)
The Peer-to-Peer (P2P) pattern enables direct communication and data exchange between independent entities (peers) without relying on a central server. Each peer acts as both client and server. This Kotlin example uses Kotlin Coroutines and Channels to simulate a simple P2P network. Peers broadcast messages to each other, and each peer listens for incoming messages to process. The implementation employs ReceiveChannel and SendChannel for asynchronous communication and launch for concurrent message handling, aligning with Kotlin’s concurrency model. The use of data classes for message representation illustrates Kotlin’s conciseness and readability.
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
data class Message(val sender: String, val content: String)
class Peer(val name: String, val peers: MutableList<Peer>) {
private val incomingMessages = Channel<Message>()
fun startListening() = CoroutineScope(Dispatchers.Default).launch {
for (message in incomingMessages) {
println("$name received: ${message.sender} -> ${message.content}")
}
}
fun sendMessage(content: String) {
val message = Message(name, content)
for (peer in peers) {
if (peer != this) {
peer.incomingMessages.send(message)
}
}
}
fun receiveChannel(): ReceiveChannel<Message> = incomingMessages
}
fun main() {
val peer1 = Peer("Alice", mutableListOf())
val peer2 = Peer("Bob", mutableListOf())
val peer3 = Peer("Charlie", mutableListOf())
peer1.peers.addAll(listOf(peer2, peer3))
peer2.peers.addAll(listOf(peer1, peer3))
peer3.peers.addAll(listOf(peer1, peer2))
val aliceListener = peer1.startListening()
val bobListener = peer2.startListening()
val charlieListener = peer3.startListening()
peer1.sendMessage("Hello, everyone!")
peer2.sendMessage("Hi Alice!")
peer3.sendMessage("Greetings from Charlie.")
Thread.sleep(1000) // Allow time for messages to propagate
aliceListener.cancel()
bobListener.cancel()
charlieListener.cancel()
}
The Peer-to-Peer (P2P) pattern enables direct communication and resource sharing between nodes (peers) in a network, removing the need for a central server. This example uses Rust’s asynchronous runtime (tokio) and UDP sockets to simulate a simple P2P network where peers can broadcast messages to each other. Each peer listens for incoming broadcasts and re-broadcasts received messages (excluding its own) to other peers. This implementation leverages Rust’s ownership and borrowing system for safe concurrent access to the socket and message buffer. Tokio provides the non-blocking IO necessary for an efficient P2P interaction. Error handling is included for robustness.
use std::net::UdpSocket;
use std::str;
use tokio::net::UdpFramed;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::time::{self, Duration};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let socket = UdpSocket::bind("127.0.0.1:8080")?;
let socket = UdpFramed::new(socket);
let mut reader = BufReader::new(socket);
let mut buffer = Vec::new();
println!("P2P Peer started. Listening on 127.0.0.1:8080");
let mut interval = time::interval(Duration::from_millis(100));
loop {
interval.tick().await;
// Receive messages
while let Some(line) = reader.lines().next_line().await? {
let message = line.trim();
if message != "" && message != "ping" {
println!("Received: {}", message);
broadcast_message(&socket, message).await?;
}
}
// Periodically send a ping to keep connections alive (optional)
broadcast_message(&socket, "ping").await?;
}
}
async fn broadcast_message(socket: &UdpFramed<UdpSocket>, message: &str) -> Result<(), Box<dyn std::error::Error>> {
let peers = vec!["127.0.0.1:8081", "127.0.0.1:8082"]; // An example peer list
for peer in &peers {
socket.send_to(message.as_bytes(), peer).await?;
}
Ok(())
}
The Peer-to-Peer (P2P) pattern enables decentralized data sharing and communication directly between nodes (peers) in a network, without relying on a central server. Each peer acts as both a client and a server. This example simulates a simple P2P file sharing system using goroutines and channels in Go. Each peer listens for file requests on a channel and responds with the file data if available. The shareFile function distributes the file to connected peers. This design emphasizes concurrency and efficient communication, key tenets of Go. Using channels and goroutines aligns with Go’s approach to concurrency, avoiding shared memory and locks whenever possible.
package main
import (
"fmt"
"sync"
"time"
)
// Peer represents a node in the P2P network.
type Peer struct {
ID int
files map[string][]byte
requests chan request
response chan []byte
wg *sync.WaitGroup
}
// request structure
type request struct {
fileID int
}
// NewPeer creates a new Peer instance.
func NewPeer(id int, wg *sync.WaitGroup) *Peer {
return &Peer{
ID: id,
files: make(map[string][]byte),
requests: make(chan request),
response: make(chan []byte),
wg: wg,
}
}
// Run starts the peer's listening loop.
func (p *Peer) Run() {
defer p.wg.Done()
go func() {
for req := range p.requests {
if data, ok := p.files[req.fileID]; ok {
p.response <- data
} else {
p.response <- nil // File not found
}
}
}()
}
// ShareFile adds a file to the peer's storage.
func (p *Peer) ShareFile(fileID string, data []byte) {
p.files[fileID] = data
}
// RequestFile sends a request to another peer for a file.
func (p *Peer) RequestFile(peer *Peer, fileID int) []byte {
p.requests <- request{fileID: fileID}
return <-p.response
}
func main() {
var wg sync.WaitGroup
wg.Add(2)
peer1 := NewPeer(1, &wg)
peer2 := NewPeer(2, &wg)
peer1.Run()
peer2.Run()
// Simulate file sharing
file1Data := []byte("This is the content of file 1.")
peer1.ShareFile("file1", file1Data)
// Peer 2 requests file 1 from peer 1
requestedData := peer2.RequestFile(peer1, 1)
if requestedData != nil {
fmt.Println("Peer 2 received file 1:", string(requestedData))
} else {
fmt.Println("Peer 2 failed to receive file 1.")
}
time.Sleep(1 * time.Second)
wg.Wait()
}
The Peer-to-Peer (P2P) pattern distributes tasks or data among multiple independent peers, rather than relying on a central server. Each peer acts as both a client and a server, sharing resources directly with other peers. This example simulates a simple P2P network for file sharing. Each process represents a peer, capable of requesting and providing files to others. We use sockets for inter-process communication and a simplified “file database” consisting of filenames and pointers to file data. This implementation uses low-level socket programming common in C, and a basic command-line interface for interaction, aligning with C’s performance-oriented and system-level approach. Error handling is included for robustness.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 12345
#define MAX_PEERS 5
#define MAX_DATA_SIZE 1024
typedef struct {
char filename[50];
char data[MAX_DATA_SIZE];
} File;
File file_database[MAX_DATA_SIZE];
int file_count = 0;
int main(int argc, char *argv[]) {
int socket_desc, new_socket, i;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_size;
int peer_id;
if (argc != 2) {
printf("Usage: %s <peer_id>\n", argv[0]);
return 1;
}
peer_id = atoi(argv[1]);
// Create socket
socket_desc = socket(AF_INET, SOCK_STREAM, 0);
if (socket_desc == -1) {
perror("Failed to create socket");
return 1;
}
// Bind socket
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT + peer_id); //Each peer uses a different port.
if (bind(socket_desc, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
return 1;
}
listen(socket_desc, 3);
// Add some initial files to the database for testing (peer 0).
if (peer_id == 0) {
strcpy(file_database[0].filename, "file1.txt");
strcpy(file_database[0].data, "This is the content of file1.");
file_count++;
}
printf("Peer %d listening on port %d...\n", peer_id, PORT + peer_id);
while (1) {
addr_size = sizeof(client_addr);
new_socket = accept(socket_desc, (struct sockaddr *)&client_addr, &addr_size);
if (new_socket < 0) {
perror("Accept failed");
continue;
}
char request[50];
if (recv(new_socket, request, sizeof(request), 0) > 0) {
printf("Peer %d received request: %s from %s\n", peer_id, request, inet_ntoa(client_addr.sin_addr));
if (strncmp(request, "GET", 3) == 0) {
char filename[50];
sscanf(request, "GET %s", filename);
// Search for the file
for (i = 0; i < file_count; i++) {
if (strcmp(file_database[i].filename, filename) == 0) {
// Send file data
send(new_socket, file_database[i].data, strlen(file_database[i].data), 0);
printf("Peer %d sent file: %s\n", peer_id, filename);
break;
}
}
if(i == file_count) {
send(new_socket, "FILE NOT FOUND", strlen("FILE NOT FOUND"), 0);
printf("Peer %d could not find file %s\n", peer_id, filename);
}
}
}
close(new_socket);
}
close(socket_desc);
return 0;
}
The Peer-to-Peer (P2P) pattern establishes direct communication and resource sharing between nodes (peers) in a network without relying on a central server. Each peer acts as both a client and a server. This implementation uses a simple TCP socket-based approach. Each peer listens for incoming connections while also proactively connecting to other peers specified at startup. Communication is done via shared strings, sent and received between peers. This design is purely illustrative and lacks robustness (error handling, security) for production. It leans heavily into standard C++ networking using sockets and threads, adhering to common practices for resource management and concurrency.
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <asio.hpp>
using asio::ip::tcp;
class Peer {
public:
Peer(const std::string& address, const std::vector<std::string>& connect_to)
: resolver_(asio::io_context_),
socket_(resolver_),
address_(address) {
// Start listening thread
listener_thread_ = std::thread([this]() { start_listen(); });
// Connect to other peers
for (const auto& peer_address : connect_to) {
std::thread([this, peer_address]() { connect_to_peer(peer_address); }).detach();
}
}
~Peer() {
socket_.close();
listener_socket_.close();
listener_thread_.join();
}
private:
void start_listen() {
try {
listener_socket_.open(tcp::endpoint(tcp::v4(), std::stoi(address_.substr(address_.find(':') + 1))) );
resolver_.resolve(tcp::endpoint::address_v4(), "");
asio::bind(listener_socket_, resolver_.result());
listener_socket_.listen();
std::cout << "Listening on port " << address_.substr(address_.find(':') + 1) << std::endl;
while (true) {
tcp socket(asio::io_context_);
listener_socket_.accept(socket);
std::thread([this, socket = std::move(socket)]() { handle_connection(socket); }).detach();
}
} catch (const std::exception& e) {
std::cerr << "Exception in listener: " << e.what() << std::endl;
}
}
void connect_to_peer(const std::string& peer_address) {
try {
tcp::resolver resolver(asio::io_context_);
tcp::socket socket(asio::io_context_);
tcp::resolver::results results = resolver.resolve(peer_address, std::to_string(address_.substr(address_.find(':') + 1)));
asio::connect(socket, results.begin(), results.end());
std::cout << "Connected to peer: " << peer_address << std::endl;
handle_connection(std::move(socket));
} catch (const std::exception& e) {
std::cerr << "Exception connecting to " << peer_address << ": " << e.what() << std::endl;
}
}
void handle_connection(tcp socket) {
try {
while (true) {
char data[1024];
size_t length = socket.read_some(asio::buffer(data));
std::string message(data, length);
std::cout << "Received: " << message << std::endl;
// Echo back to the sender
socket.write_some(asio::buffer(message));
}
} catch (const std::exception& e) {
std::cerr << "Exception in connection handler: " << e.what() << std::endl;
}
}
asio::ip::tcp::resolver resolver_;
tcp socket_;
std::string address_;
tcp listener_socket_;
std::thread listener_thread_;
};
int main() {
std::string my_address = "127.0.0.1:8080";
std::vector<std::string> peers = {"127.0.0.1:8081", "127.0.0.1:8082"};
Peer peer(my_address, peers);
// Keep main thread alive
while (true) {
std::this_thread::sleep_for(std::chrono::seconds(1));
}
return 0;
}
The Peer-to-Peer (P2P) pattern establishes direct communication and data exchange between independent components (peers) without relying on a central server. Each peer acts as both a client and a server, discovering and connecting to other peers. This example utilizes .NET’s built-in TCP networking capabilities to simulate a simple P2P chat application. Each peer listens for incoming connections and can initiate connections to other peers. Messages are sent directly between connected peers. This implementation is idiomatic C# due to its use of async/await for non-blocking network operations, using statements for resource management, and clear class structure representing peer functionality.
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
public class Peer
{
private readonly int _port;
private readonly string _name;
private TcpListener _listener;
private bool _isRunning = false;
public Peer(int port, string name)
{
_port = port;
_name = name;
}
public async Task Start()
{
_listener = new TcpListener(IPAddress.Any, _port);
_listener.Start();
_isRunning = true;
Console.WriteLine($"Peer {_name} started on port {_port}");
while (_isRunning)
{
TcpClient client = await _listener.AcceptTcpClientAsync();
_ = HandleClientAsync(client); //Fire and forget
}
}
public async Task ConnectToPeerAsync(string ipAddress, int port)
{
try
{
TcpClient client = new TcpClient();
await client.ConnectAsync(IPAddress.Parse(ipAddress), port);
_ = HandleClientAsync(client);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to connect to {ipAddress}:{port} - {ex.Message}");
}
}
private async Task HandleClientAsync(TcpClient client)
{
using (client)
using (NetworkStream stream = client.GetStream())
{
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine($"Received from {client.Client.RemoteEndPoint}: {message}");
}
}
Console.WriteLine("Client disconnected");
}
public async Task SendMessageAsync(string message, TcpClient client)
{
if (client.Connected)
{
byte[] buffer = Encoding.UTF8.GetBytes(message);
NetworkStream stream = client.GetStream();
await stream.WriteAsync(buffer, 0, buffer.Length);
await stream.FlushAsync();
}
else {
Console.WriteLine("Client not connected. Message not sent.");
}
}
public void Stop()
{
_isRunning = false;
_listener.Stop();
Console.WriteLine($"Peer {_name} stopped.");
}
}
public class Program
{
public static async Task Main(string[] args)
{
Peer peer1 = new Peer(12345, "Peer 1");
Peer peer2 = new Peer(12346, "Peer 2");
Task startPeer1 = peer1.Start();
Task startPeer2 = peer2.Start();
//Connect Peer 2 to Peer 1
await peer2.ConnectToPeerAsync("127.0.0.1", 12345);
Console.WriteLine("Press any key to stop...");
Console.ReadKey();
peer1.Stop();
peer2.Stop();
}
}
The Peer-to-Peer (P2P) pattern establishes direct communication channels between nodes (peers) in a network, eliminating the need for a central server. Each peer acts as both a client and a server, sharing resources and data directly with others. This implementation uses WebRTC Data Channels in TypeScript to create P2P connections between browsers. Each browser instance represents a peer, and utilizes signaling (omitted for brevity, but typically handled by a server) to discover and connect to other peers. Once connected, data can be sent and received directly, enabling real-time communication without intermediary servers for data transfer. TypeScript’s strong typing and class-based structure promote organization and maintainability, fitting well with the complex coordination required in a P2P system.
// peer.ts
class Peer {
private dataChannel: RTCDataChannel | null = null;
private peerConnection: RTCPeerConnection | null = null;
private remotePeerId: string | null = null;
constructor() {
this.createPeerConnection();
this.createDataChannel();
}
private createPeerConnection(): void {
this.peerConnection = new RTCPeerConnection();
this.peerConnection.ondatachannel = (event) => {
this.dataChannel = event.channel;
this.dataChannel.onmessage = (event) => {
console.log(`Received message from peer ${this.remotePeerId}: ${event.data}`);
};
this.dataChannel.onopen = () => {
console.log(`Data channel open with peer ${this.remotePeerId}`);
}
};
this.peerConnection.onicecandidate = (event) => {
if (event.candidate) {
//In a real application, you would send this to the signaling server.
console.log("ICE candidate:", event.candidate);
}
};
}
private createDataChannel(): void {
this.dataChannel = this.peerConnection!.createDataChannel('myChannel');
this.dataChannel.onopen = () => {
console.log('Data channel is open.');
};
}
async connectToPeer(peerId: string, offer: RTCSessionDescription): Promise<void> {
this.remotePeerId = peerId;
await this.peerConnection!.setRemoteDescription(offer);
const answer = await this.peerConnection!.createAnswer();
await this.peerConnection!.setLocalDescription(answer);
//In a real application, you would send the answer to the signaling server.
console.log("Answer (for signaling):", answer);
}
async createOffer(): Promise<RTCSessionDescription> {
const offer = await this.peerConnection!.createOffer();
await this.peerConnection!.setLocalDescription(offer);
//In a real application, you would send the offer to the signaling server.
console.log("Offer (for signaling):", offer);
return offer;
}
sendMessage(message: string): void {
if (this.dataChannel) {
this.dataChannel.send(message);
}
}
}
// Example usage in a browser environment (HTML would be needed similarly)
document.addEventListener('DOMContentLoaded', () => {
const peer1 = new Peer();
const peer2 = new Peer();
const sendButton = document.getElementById('sendButton') as HTMLButtonElement;
const messageInput = document.getElementById('messageInput') as HTMLInputElement;
sendButton.addEventListener('click', () => {
peer1.sendMessage(messageInput.value);
messageInput.value = "";
});
peer1.createOffer().then(offer => {
//Simulate sending offer to peer2 (normally via a signaling server)
peer2.connectToPeer("peer2", offer);
});
});
The Peer-to-Peer (P2P) pattern establishes direct connections between participants in a network, removing the need for a central server to mediate all communication after initial discovery. This improves scalability and resilience. This JavaScript example uses WebRTC Data Channels for P2P communication, facilitated by a simple signaling server (not included in this snippet - a small Node.js server would be typical). Clients initiate connections as ‘initiators’ or ‘receivers’ and exchange messages directly once established. This approach leverages JavaScript’s asynchronous nature via Promises for managing connection states and message handling, aligning with its event-driven paradigm. It avoids complex server-side logic where possible, pushing communication to the clients.
// p2p.js
const peerConnection = new RTCPeerConnection();
// Signaling is assumed to be handled elsewhere (e.g. WebSocket server)
// Function to initiate a connection (Initiator)
async function initiateConnection(remoteDescription) {
try {
await peerConnection.setRemoteDescription(remoteDescription);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
return answer;
} catch (error) {
console.error("Initiate Connection Error:", error);
return null;
}
}
// Function to receive a connection (Receiver)
async function receiveConnection(remoteDescription) {
try {
await peerConnection.setRemoteDescription(remoteDescription);
const localDescription = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(localDescription);
return localDescription;
} catch (error) {
console.error("Receive Connection Error:", error);
return null;
}
}
// Handle incoming data channel messages
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel;
dataChannel.onmessage = (event) => {
console.log("Received message:", event.data);
};
dataChannel.onopen = () => {
console.log("Data Channel opened");
};
dataChannel.onclose = () => {
console.log("Data Channel closed");
};
dataChannel.onerror = (error) => {
console.error("Data Channel error:", error);
};
};
// Function to send a message
function sendMessage(message) {
const dataChannel = peerConnection.createDataChannel("myChannel");
dataChannel.send(message);
dataChannel.onopen = () => {
console.log("Channel opened for sending");
};
dataChannel.onerror = (error) => {
console.error("Channel error:", error);
};
}
//Example usage (Simulated signaling - in real life this is done via a server)
// Initiator side:
// const remoteDescription = await initiateConnection(...);
// Receiver side:
// await receiveConnection(remoteDescription);
//Then in either side call sendMessage(...) to send the message
export { initiateConnection, receiveConnection, sendMessage };
The Peer-to-Peer (P2P) pattern establishes direct communication and data exchange between individual nodes (peers) in a network, without relying on a centralized server. Each peer acts as both client and server. This example simulates a simple P2P chat application where peers can connect and send messages directly to each other. The implementation uses Python’s sockets for network communication. Threads handle concurrent message sending and receiving for each peer. This approach aligns with Python’s concurrency capabilities and network programming libraries, providing a relatively clean and straightforward implementation suitable for prototyping a P2P system.
import socket
import threading
class Peer:
def __init__(self, host, port):
self.host = host
self.port = port
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind((self.host, self.port))
self.socket.listen()
self.connections = []
def start(self):
print(f"Peer listening on {self.host}:{self.port}")
while True:
conn, addr = self.socket.accept()
print(f"Connection from {addr}")
self.connections.append(conn)
thread = threading.Thread(target=self.handle_connection, args=(conn, addr))
thread.daemon = True # Allow program to exit when only daemon threads remain
thread.start()
def handle_connection(self, conn, addr):
while True:
try:
data = conn.recv(1024)
if not data:
break
message = data.decode('utf-8')
print(f"Received from {addr}: {message}")
except ConnectionResetError:
print(f"Connection to {addr} reset.")
break
except Exception as e:
print(f"Error handling connection from {addr}: {e}")
break
def send_message(self, peer_host, peer_port, message):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((peer_host, peer_port))
sock.sendall(message.encode('utf-8'))
except Exception as e:
print(f"Error sending message to {peer_host}:{peer_port}: {e}")
if __name__ == "__main__":
peer1_host = '127.0.0.1'
peer1_port = 5555
peer2_host = '127.0.0.1'
peer2_port = 5556
peer1 = Peer(peer1_host, peer1_port)
peer2 = Peer(peer2_host, peer2_port)
thread1 = threading.Thread(target=peer1.start)
thread2 = threading.Thread(target=peer2.start)
thread1.daemon = True
thread2.daemon = True
thread1.start()
thread2.start()
# Example usage (send a message from peer1 to peer2)
peer1.send_message(peer2_host, peer2_port, "Hello from Peer 1!")
import time
time.sleep(5) # Keep the script running to allow for communication
The Peer-to-Peer (P2P) pattern distributes application logic and data among multiple independent nodes (peers), rather than relying on a central server. Each peer functions as both a client and a server, directly communicating with other peers. This example simulates a simple chat application where peers can send messages directly to each other. It uses Java’s networking capabilities (Sockets and Threads) to establish peer connections and handle message exchange. The use of Threads allows multiple peers to be handled concurrently. The class structure and straightforward approach fit Java’s typical network programming style.
import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Peer {
private static final int PORT = 12345;
private static final String PROMPT = "> ";
private List<ConnectionHandler> connections;
public Peer() {
connections = new ArrayList<>();
}
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("Usage: java Peer <peer_id> [<peer_address>:<peer_port>]...");
return;
}
Peer peer = new Peer();
String peerId = args[0];
for (int i = 1; i < args.length; i++) {
String peerAddressPort = args[i];
String[] parts = peerAddressPort.split(":");
String address = parts[0];
int port = Integer.parseInt(parts[1]);
peer.connectToPeer(address, port, peerId);
}
peer.listenForConnections(peerId);
peer.startChat(peerId);
}
public void connectToPeer(String address, int port, String peerId) {
try {
Socket socket = new Socket(address, port);
ConnectionHandler handler = new ConnectionHandler(socket, peerId, this);
connections.add(handler);
new Thread(handler).start();
System.out.println("Connected to peer at " + address + ":" + port);
} catch (IOException e) {
System.err.println("Could not connect to " + address + ":" + port + ": " + e.getMessage());
}
}
public void listenForConnections(String peerId) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("Listening on port " + PORT);
while (true) {
Socket socket = serverSocket.accept();
ConnectionHandler handler = new ConnectionHandler(socket, peerId, this);
connections.add(handler);
new Thread(handler).start();
System.out.println("New peer connected: " + socket.getInetAddress().getHostAddress());
}
} catch (IOException e) {
System.err.println("Error listening for connections: " + e.getMessage());
}
}
public void sendMessage(String recipientAddress, int recipientPort, String message, String senderId) {
try {
Socket socket = new Socket(recipientAddress, recipientPort);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(senderId + ": " + message);
socket.close();
} catch (IOException e) {
System.err.println("Error sending message to " + recipientAddress + ":" + recipientPort + ": " + e.getMessage());
}
}
public void startChat(String peerId) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print(peerId + PROMPT);
String message = scanner.nextLine();
if (message.equals("exit")) {
break;
}
//Simple example - send to all connected peers
for(ConnectionHandler connection : connections){
sendMessage(connection.getAddress(), connection.getPort(), message, peerId);
}
}
scanner.close();
System.exit(0);
}
}
class ConnectionHandler implements Runnable {
private Socket socket;
private String peerId;
private Peer peer;
public ConnectionHandler(Socket socket, String peerId, Peer peer) {
this.socket = socket;
this.peerId = peerId;
this.peer = peer;
}
public String getAddress() {
return socket.getInetAddress().getHostAddress();
}
public int getPort() {
return socket.getPort();
}
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
String message;
while ((message = in.readLine()) != null) {
System.out.println("Received from " + getAddress() + ":" + getPort() + ": " + message);
}
} catch (IOException e) {
System.err.println("Error handling connection: " + e.getMessage());
}
}
}