무엇인가
모든 참여자가 클라이언트이면서 동시에 서버인 구조다. 중앙 서버가 없고, 각 피어가 자원을 내놓기도 하고 남의 자원을 쓰기도 한다.
클라이언트-서버에서 역할의 구분을 없앤 것이라고 보면 된다. 그 대신 “누가 무엇을 갖고 있는가”를 찾는 문제가 새로 생긴다.
구조
graph TD P1[피어 A<br/>가짐: x] <--> P2[피어 B<br/>가짐: y] P2 <--> P3[피어 C<br/>가짐: z] P1 <--> P3 P3 <--> P4[피어 D<br/>가짐: x] P2 <--> P4
C++로 보기
각 피어가 자기 데이터를 갖고, 없으면 이웃에게 물어본다. 방문 표시를 안 하면 순환 구조에서 무한히 돈다 — 이 패턴의 첫 함정이다.
#include <iostream>
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
class Peer {
std::string name_;
std::unordered_map<std::string, std::string> local_;
std::vector<Peer*> neighbors_;
public:
explicit Peer(std::string name) : name_(std::move(name)) {}
const std::string& name() const { return name_; }
void store(const std::string& k, const std::string& v) { local_[k] = v; }
void connect(Peer& other) {
neighbors_.push_back(&other);
other.neighbors_.push_back(this);
}
// 서버 역할: 내가 가졌으면 준다.
// 클라이언트 역할: 없으면 이웃에게 물어본다.
std::optional<std::string> lookup(const std::string& key,
std::set<const Peer*>& visited) const {
if (!visited.insert(this).second) return std::nullopt; // 이미 왔던 곳
auto it = local_.find(key);
if (it != local_.end()) {
std::cout << " " << name_ << "가 가지고 있다\n";
return it->second;
}
std::cout << " " << name_ << "에 없음 → 이웃에게\n";
for (const Peer* n : neighbors_) {
if (auto found = n->lookup(key, visited)) return found;
}
return std::nullopt;
}
std::optional<std::string> find(const std::string& key) const {
std::set<const Peer*> visited;
return lookup(key, visited);
}
};
int main() {
Peer a{"A"}, b{"B"}, c{"C"}, d{"D"};
a.connect(b);
b.connect(c);
c.connect(d);
d.connect(a); // 고리가 생긴다
c.store("lang", "C++");
std::cout << "A에서 lang 찾기:\n";
if (auto v = a.find("lang")) std::cout << "찾음: " << *v << "\n\n";
std::cout << "A에서 없는 키 찾기:\n";
if (!a.find("nope")) std::cout << "못 찾음(모든 피어 방문 후 종료)\n";
}visited 집합이 없으면 A→B→C→D→A로 영원히 돈다.
그리고 이 단순한 방식은 찾을 때마다 망 전체를 훑는다 — 피어가 늘면 그대로 못 쓴다.
실제 P2P가 DHT 같은 구조를 쓰는 이유가 이것이다.
언제 쓰나
- 중앙 서버를 두기 어렵거나 두고 싶지 않을 때(비용, 검열 저항, 단일 장애점 제거).
- 참여자가 늘수록 자원도 같이 느는 성질이 필요할 때(대역폭, 저장 공간).
- 참여자들이 대등하고 서로 비슷한 일을 할 때.
대가
- 탐색이 어렵다. 위 예제처럼 순진하게 하면 망 크기에 비례해 비용이 커진다.
- 참여자가 언제든 사라진다. 가용성·일관성 보장이 서버 방식보다 훨씬 까다롭다.
- 보안·신뢰 문제가 크다. 아무나 피어가 될 수 있기 때문이다.
실제로 만나는 곳
BitTorrent, 블록체인 네트워크, IPFS, 그리고 일부 게임의 P2P 매치.
P2P 넷코드가 왜 어려운지 — 치팅, 호스트 이점, NAT 통과 — 는 그 자체로 따로 다룰 주제다.