大家好,欢迎来到IT知识分享网。
前言
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
#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
#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; }
5:运行结果(暂时手上没有公网服务器,请自行编译测试)
如果觉得有用,麻烦点个赞,加个收藏
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/129348.html