前言
P2P网络(Peer-to-Peer Network)是一种点对点的网络结构,它没有中心化的服务器或者管理者,所有节点都是平等的。在P2P网络中,每个节点都可以既是客户端也是服务端,这种网络结构的优点是去中心化、可扩展性强、抗攻击性强等。
1:P2P网络的优点
区块链 P2P 网络的优点有:
去中心化:没有中心化的服务器或者管理者,所有节点都是平等的。
高可用性:由于没有单点故障,所以整个系统非常稳定。
高安全性:由于每个节点都有完整的数据副本,所以即使有部分节点被攻击或者宕机,整个系统依然可以正常运行。
高效性:由于数据可以在多个节点之间共享和传输,所以整个系统非常高效。
2:分类
根据具体应用不同,可以把P2P分为以下这些类型[1]:
·提供文件和其它内容共享的P2P网络,例如Napster、Gnutella、eDonkey、emule、BitTorrent等;
·挖掘P2P对等计算能力和存储共享能力,例如SETI@home 、Avaki、Popular Power等;
·基于P2P方式的协同处理与服务共享平台,例如JXTA、Magi、Groove、.NET My Service等;
·即时通讯交流,包括ICQ、OICQ、Yahoo Messenger等;
·安全的P2P通讯与信息共享,例如Skype、Crowds、Onion Routing等。
3:区块链中的P2P网络
作为区块链的底层传输方式,P2P 技术帮助区块链成功实现了点对点的传播。比特币、以太坊等众多区块链项目都实现了属于自己的P2P网络协议,根据区块链的运行特点,我们可以总结出区块链客户端节点所组成p2p网络的一些需求:
1.节点可以任意地加入和离开网络;
2.每个节点所存储的数据(区块),在理想状态下是一致的(当然光凭p2p网络不能达到数据一致性,它只是提供了数据传输的逻辑通道,我们还需要共识算法来配合实现数据一致性);
3.在区块链网络中,查找数据时不需要向整个网络广播发送请求,正常情况下任意一个(或相邻几个)节点就可以提供完整的区块数据。
4:直接上代码
P2P打洞
UDP 打洞更容易点,网上一堆,不发了
TCP SERVER
cpp
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#ifdef _WIN32
#include <WinSock2.h>
#include<Ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
#define MAXLINE 128
#define SERV_PORT 7119
//发生了致命错误,退出程序
void error_quit(const char *str)
{
fprintf(stderr, "%s", str);
//如果设置了错误号,就输入出错原因
#ifdef _WIN32
if (errno != 0) {
const int errmsglen = 255;
char errmsg[errmsglen];
strerror_s(errmsg, errmsglen, errno);
fprintf(stderr, " : %s", errmsg);
}
#else
if (errno != 0)
fprintf(stderr, " : %s", strerror(errno));
#endif
printf("\n");
exit(1);
}
int main(void)
{
int i, res, cur_port;
#ifdef _WIN32
SOCKET connfd, firstfd, listenfd;
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
#else
int connfd, firstfd, listenfd;
#endif
int count = 0;
char str_ip[MAXLINE]; //缓存IP地址
char cur_inf[MAXLINE]; //当前的连接信息[IP+port]
char first_inf[MAXLINE]; //第一个链接的信息[IP+port]
char buffer[MAXLINE]; //临时发送缓冲区
socklen_t clilen;
struct sockaddr_in cliaddr;
struct sockaddr_in servaddr;
//创建用于监听TCP协议套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//把socket和socket地址结构联系起来
res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (-1 == res)
error_quit("bind error");
//开始监听端口
res = listen(listenfd, INADDR_ANY);
if (-1 == res)
error_quit("listen error");
while (1)
{
//接收来自客户端的连接
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
if (-1 == connfd)
error_quit("accept error");
inet_ntop(AF_INET, (void*)&cliaddr.sin_addr, str_ip, sizeof(str_ip));
count++;
//对于第一个链接,将其的IP+port存储到first_inf中,
//并和它建立长链接,然后向它发送字符串'first',
if (count == 1)
{
firstfd = connfd;
cur_port = ntohs(cliaddr.sin_port);
snprintf(first_inf, MAXLINE, "%s %d", str_ip, cur_port);
#ifdef _WIN32
strcpy_s(cur_inf, MAXLINE, "first\n");
send(connfd, cur_inf, strlen(cur_inf) + 1, 0);
#else
strcpy(cur_inf, "first\n");
write(connfd, cur_inf, strlen(cur_inf) + 1);
#endif
}
//对于第二个链接,将其的IP+port发送给第一个链接,
//将第一个链接的信息和他自身的port返回给它自己,
//然后断开两个链接,并重置计数器
else if (count == 2)
{
cur_port = ntohs(cliaddr.sin_port);
snprintf(cur_inf, MAXLINE, "%s %d\n", str_ip, cur_port);
snprintf(buffer, MAXLINE, "%s %d\n", first_inf, cur_port);
#ifdef _WIN32
send(connfd, buffer, strlen(buffer) + 1,0);
send(firstfd, cur_inf, strlen(cur_inf) + 1,0);
closesocket(connfd);
closesocket(firstfd);
#else
write(connfd, buffer, strlen(buffer) + 1);
write(firstfd, cur_inf, strlen(cur_inf) + 1);
close(connfd);
close(firstfd);
#endif
count = 0;
}
//如果程序运行到这里,那肯定是出错了
else
error_quit("Bad required");
}
#ifdef _WIN32
WSACleanup();
#endif
return 0;
}
/*
运行示例:
(第一个终端)
ubuntu@ubuntu ~/program/tcode $ gcc server.c -o server
ubuntu@ubuntu ~/program/tcode $ ./server &
[1] 4688
ubuntu@ubuntu ~/program/tcode $ gcc client.c -o client
ubuntu@ubuntu ~/program/tcode $ ./client localhost
Get: first
ff: 127.0.0.1 38052
send message: Hello, world
send message: Hello, world
send message: Hello, world
.................
第二个终端:
ubuntu@ubuntu ~/program/tcode $ ./client localhost
Get: 127.0.0.1 38073 38074
connect error
recv message: Hello, world
recv message: Hello, world
recv message: Hello, world
*/
client
cpp
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#ifdef _WIN32
#include <WinSock2.h>
#include<Ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#endif
#define MAXLINE 128
#define SERV_PORT 7119
typedef struct
{
char ip[32];
int port;
}server;
void error_quit(const char *str)
{
fprintf(stderr, "%s", str);
//如果设置了错误号,就输入出错原因
#ifdef _WIN32
if (errno != 0) {
const int errmsglen = 255;
char errmsg[errmsglen];
strerror_s(errmsg, errmsglen, errno);
fprintf(stderr, " : %s", errmsg);
}
#else
if (errno != 0)
fprintf(stderr, " : %s", strerror(errno));
#endif
printf("\n");
exit(1);
}
int main(int argc, char **argv)
{
int i, res, port;
#ifdef _WIN32
SOCKET connfd, sockfd, listenfd;
WSADATA wsaData;
WORD wVersionRequested = MAKEWORD(2, 2);
WSAStartup(wVersionRequested, &wsaData);
BOOL bReuseaddr = TRUE;
#else
int connfd, sockfd, listenfd;
unsigned int value = 1;
#endif
char buffer[MAXLINE];
socklen_t clilen;
struct sockaddr_in servaddr, sockaddr, connaddr;
server other;
if (argc != 2)
error_quit("Using: ./client <IP Address>");
//创建用于链接(主服务器)的套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&sockaddr, 0, sizeof(sockaddr));
sockaddr.sin_family = AF_INET;
sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
sockaddr.sin_port = htons(SERV_PORT);
inet_pton(AF_INET, argv[1], &sockaddr.sin_addr);
//设置端口可以被重用
#ifdef _WIN32
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(BOOL));
#else
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
#endif
//连接主服务器
res = connect(sockfd, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
if (res < 0)
error_quit("connect error");
//从主服务器中读取出信息
#ifdef _WIN32
res = recv(sockfd, buffer, MAXLINE,0);
#else
res = read(sockfd, buffer, MAXLINE);
#endif
if (res < 0)
error_quit("read error");
printf("Get: %s", buffer);
//若服务器返回的是first,则证明是第一个客户端
if ('f' == buffer[0])
{
//从服务器中读取第二个客户端的IP+port
#ifdef _WIN32
res = recv(sockfd, buffer, MAXLINE, 0);
sscanf_s(buffer, "%s %d", other.ip,&other.port);
#else
res = read(sockfd, buffer, MAXLINE);
sscanf(buffer, "%s %d", other.ip, &other.port);
#endif
printf("ff: %s %d\n", other.ip, other.port);
//创建用于的套接字
connfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&connaddr, 0, sizeof(connaddr));
connaddr.sin_family = AF_INET;
connaddr.sin_addr.s_addr = htonl(INADDR_ANY);
connaddr.sin_port = htons(other.port);
inet_pton(AF_INET, other.ip, &connaddr.sin_addr);
//尝试去连接第二个客户端,前几次可能会失败,因为穿透还没成功,
//如果连接10次都失败,就证明穿透失败了(可能是硬件不支持)
while (1)
{
static int j = 1;
res = connect(connfd, (struct sockaddr *)&connaddr, sizeof(connaddr));
if (res == -1)
{
if (j >= 10)
error_quit("can't connect to the other client\n");
printf("connect error, try again. %d\n", j++);
#ifdef _WIN32
Sleep(1);
#else
sleep(1);
#endif
}
else
break;
}
#ifdef _WIN32
strcpy_s(buffer, MAXLINE, "Hello, world\n");
#else
strcpy(buffer, "Hello, world\n");
#endif
//连接成功后,每隔一秒钟向对方(客户端2)发送一句hello, world
while (1)
{
#ifdef _WIN32
res = send(connfd, buffer, strlen(buffer) + 1,0);
#else
res = write(connfd, buffer, strlen(buffer) + 1);
#endif
if (res <= 0)
error_quit("write error");
printf("send message: %s", buffer);
#ifdef _WIN32
Sleep(1);
#else
sleep(1);
#endif
}
}
//第二个客户端的行为
else
{
//从主服务器返回的信息中取出客户端1的IP+port和自己公网映射后的port
#ifdef _WIN32
sscanf_s(buffer, "%s %d %d", other.ip, &other.port, &port);
#else
sscanf(buffer, "%s %d %d", other.ip, &other.port, &port);
#endif
//创建用于TCP协议的套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&connaddr, 0, sizeof(connaddr));
connaddr.sin_family = AF_INET;
connaddr.sin_addr.s_addr = htonl(INADDR_ANY);
connaddr.sin_port = htons(other.port);
inet_pton(AF_INET, other.ip, &connaddr.sin_addr);
//设置端口重用
#ifdef _WIN32
BOOL bReuseaddr = TRUE;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(BOOL));
#else
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
#endif
//尝试连接客户端1,肯定会失败,但它会在路由器上留下记录,
//以帮忙客户端1成功穿透,连接上自己
res = connect(sockfd, (struct sockaddr *)&connaddr, sizeof(connaddr));
if (res < 0)
printf("connect error\n");
//创建用于监听的套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
//设置端口重用
#ifdef _WIN32
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&bReuseaddr, sizeof(BOOL));
#else
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
#endif
//把socket和socket地址结构联系起来
res = bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (-1 == res)
error_quit("bind error");
//开始监听端口
res = listen(listenfd, INADDR_ANY);
if (-1 == res)
error_quit("listen error");
while (1)
{
//接收来自客户端1的连接
connfd = accept(listenfd, (struct sockaddr *)&sockaddr, &clilen);
if (-1 == connfd)
error_quit("accept error");
while (1)
{
//循环读取来自于客户端1的信息
#ifdef _WIN32
res = recv(connfd, buffer, MAXLINE,0);
#else
res = read(connfd, buffer, MAXLINE);
#endif
if (res <= 0)
error_quit("read error");
printf("recv message: %s", buffer);
}
#ifdef _WIN32
closesocket(connfd);
#else
close(connfd);
#endif
}
}
return 0;
}
golang的参考 github.com/ethereum/go-ethereum/
参考 https://blog.csdn.net/muxuen/article/details/137231514
5:运行结果(暂时手上没有公网服务器,请自行编译测试)
如果觉得有用,麻烦点个赞,加个收藏