P2P之UDP内网穿透实例 c++

2019/7/23 9:38:53 人评论 次浏览 分类:学习教程

公网服务器端: 

单线程,循环接收连接请求。之后转发两个客户端的IP和端口,转发发完成后自行关闭。

/*
文件:server.c
PS:第一个连接上服务器的客户端,称为client1,第二个连接上服务器的客户端称为client2
这个服务器的功能是:
1:对于client1,它返回"first",并在client2连接上之后,将client2经过转换后的IP和port发给client1;
2:对于client2,它返回client1经过转换后的IP和port和自身的port,并在随后断开与他们的连接。
 */

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>

#define MAXLINE 128
#define SERV_PORT 8877

//发生了致命错误,退出程序

void error_quit(const char *str) {
	fprintf(stderr, "%s", str);
	//如果设置了错误号,就输入出错原因
	if (errno != 0)
		fprintf(stderr, " : %s", strerror(errno));
	printf("\n");
	exit(1);
}

int main(void) {
	int res, cur_port;
	int connfd, firstfd, listenfd;
	int count = 0;
	char str_ip[MAXLINE] = {0}; //当前IP地址
	char str_ip1[MAXLINE] = {0}; //缓存IP地址1
	char cur_inf[MAXLINE] = {0}; //当前的连接信息[IP+port]
	char first_inf[MAXLINE] = {0}; //第一个链接的信息[IP+port]
	char buffer[MAXLINE] = {0}; //临时发送缓冲区
	struct sockaddr_in cliaddr;
	struct sockaddr_in servaddr;
	socklen_t clilen;
	int aport = 0;
	memset(&cliaddr, 0, sizeof (sockaddr_in));
	clilen = (socklen_t)sizeof (cliaddr);

	//创建用于监听TCP协议套接字        
	listenfd = socket(AF_INET, SOCK_DGRAM, 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");
	while (1) {
		printf("waiting\n");
		fflush(stdout);
		//接收来自客户端的连接

		sockaddr_in addr, addr1;
		memset(&addr, 0, sizeof (sockaddr_in));
		socklen_t addrlen(0);
		addrlen = sizeof (sockaddr_in);
		recvfrom(listenfd, buffer, MAXLINE, 0, (sockaddr*) & addr, &addrlen);
		count++;
		inet_ntop(AF_INET, (void*) &addr.sin_addr, str_ip, sizeof (str_ip));
		cur_port = ntohs(addr.sin_port);
		if (count == 1) {
			printf("accept %s\n", buffer);

			printf("accept1:  %s %d\n", str_ip, cur_port);
			fflush(stdout);
			firstfd = connfd;
			snprintf(first_inf, MAXLINE, "%s %d", str_ip, cur_port);
			aport = cur_port;
			strcpy(str_ip1, str_ip);
			strcpy(cur_inf, "first\n");
			sendto(listenfd, cur_inf, strlen(cur_inf), 0, (struct sockaddr *) &addr, sizeof (sockaddr_in)); //向客户端1发送first 告诉你是第一个
			if (-1 == res)
				error_quit("sendto error");
			memset(&addr1, 0, sizeof (sockaddr_in));
			inet_pton(AF_INET, str_ip, &addr1.sin_addr);
			addr1.sin_port = htons(cur_port);

		} else if (count == 2) {
			printf("accept %s\n", buffer);
			printf("accept2:  %s %d\n", str_ip, cur_port);
			fflush(stdout);
			//cur_port = ntohs(cliaddr.sin_port);
			snprintf(cur_inf, MAXLINE, "%s %d\n", str_ip, cur_port);
			snprintf(buffer, MAXLINE, "%s %d\n", str_ip1, aport);
			sendto(listenfd, buffer, strlen(buffer), 0, (struct sockaddr *) &addr, sizeof (sockaddr_in)); //向第二个客户端发送第一个客户端的信息
			if (-1 == res)
				error_quit("sendto error");
			sendto(listenfd, cur_inf, strlen(cur_inf), 0, (struct sockaddr *) &addr1, sizeof (sockaddr_in)); //向第一个客户端发送1
			if (-1 == res)
				error_quit("sendto2 error");
			sleep(5);
			printf("close\n");
			fflush(stdout);
			close(listenfd);
			return 0;
		} else
			error_quit("Bad required");
	}
	close(listenfd);
	return 0;
}

客户端:

分为两个线程,一个线程阻塞在recvfrom等待接收,另一个线程循环不停的向另一个客户端发送。发送既是打洞,也是互相通信。

 

/*
文件:client.c
PS:第一个连接上服务器的客户端,称为client1,第二个连接上服务器的客户端称为client2
这个程序的功能是:先连接上服务器,根据服务器的返回决定它是client1还是client2,
若是client1,它就从服务器上得到client2的IP和Port,连接上client2,
若是client2,它就从服务器上得到client1的IP和Port和自身经转换后的port,
在尝试连接了一下client1后(这个操作会失败),然后根据服务器返回的port进行监听。
这样以后,就能在两个客户端之间进行点对点通信了。
 */

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#define MAXLINE 128
#define SERV_PORT 8877
#define CLIENT1_PORT 4003
#define CLIENT2_PORT 4004
#define SERV_IP "111.111.111.111"//服务器IP

#define SIZE 128

typedef struct {
	char ip[32];
	int port;
} server;

void error_quit(const char *str) {
	fprintf(stderr, "%s", str);
	if (errno != 0)
		fprintf(stderr, " : %s", strerror(errno));
	printf("\n");
	exit(1);
}

void * recvfd(void * sockfd) {
	char buf[MAXLINE] = {0};
	socklen_t addrlen(0);
	sockaddr_in remote;
	while (1) {
		recvfrom(*(int*)sockfd, buf, SIZE, 0, (sockaddr *) & remote, &addrlen);
		printf("recvfrom b :%s\n", buf);
		sleep(5);
	}
}

int main(int argc, char **argv) {
	int res = 0, port = 0;
	int connfd, sockfd, listenfd;
	unsigned int value = 1;
	char buffer[MAXLINE] = {0};
	socklen_t clilen;
	struct sockaddr_in servaddr, remote_addr, connaddr, localsockaddr, remote;
	server other;
	socklen_t addrlen(0);
	pthread_t pthreadfd;
	addrlen = sizeof (sockaddr_in);
	memset(&remote, 0, sizeof (sockaddr_in));
	memset(&other, 0, sizeof (other));
	memset(&remote_addr, 0, sizeof (remote_addr));
	memset(&localsockaddr, 0, sizeof (localsockaddr));
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	remote_addr.sin_family = AF_INET;
	inet_pton(AF_INET, SERV_IP, &remote_addr.sin_addr);
	remote_addr.sin_port = htons(SERV_PORT); //连接SERVERport
	localsockaddr.sin_family = AF_INET;
	localsockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	localsockaddr.sin_port = htons(CLIENT1_PORT); //本地拍p2p port
	res = bind(sockfd, (struct sockaddr *) &localsockaddr, sizeof (localsockaddr)); //客户端connect时候绑定本地短偶
	if (-1 == res)
		error_quit("bind error");
	char buf[SIZE] = {0};
	bzero(buf, SIZE);

	strcpy(buf, "Hello, world \n");
	res = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *) &remote_addr, sizeof (sockaddr_in)); //向服务器发送
	if (-1 == res)
		error_quit("bind error");
	
	printf("sendto serv :%s\n", buf);
	int sinlen = sizeof (remote);
	bzero(buf, SIZE);
	int cur_port = 0;
	char str_ip[SIZE] = {0};
	recvfrom(sockfd, buf, SIZE, 0, (sockaddr *) & remote, &addrlen);

	inet_ntop(AF_INET, (void*) &remote.sin_addr, str_ip, sizeof (str_ip));
	cur_port = ntohs(remote.sin_port);
	if (-1 == res)
		error_quit("bind error");
	
	//若服务器返回的是first,则证明是第一个客户端
	if ('f' == buf[0]) {
		//从服务器中读取第二个客户端的IP+port
		printf("recv serv :%s\n", buf);
		fflush(stdout);
		bzero(buf, SIZE);
		recvfrom(sockfd, buf, SIZE, 0, (sockaddr *) & remote, &addrlen);
			
		printf("recv serv :%s\n", buf);
		sscanf(buf, "%s %d", str_ip, &cur_port);
		printf("C2 port is %s,%d\n", str_ip, cur_port);
		fflush(stdout);
		bzero(buf, SIZE);
		
		int rc = pthread_create(&pthreadfd, NULL,recvfd, (void *)&sockfd);
		if (rc){
         error_quit("无法创建线程 error");
         exit(-1);
		  }
		strcpy(buf, "hello b \n");
		memset(&remote, 0, sizeof (sockaddr_in));
		remote.sin_family = AF_INET;
		remote.sin_addr.s_addr = htonl(INADDR_ANY);
		remote.sin_port = htons(cur_port);
		inet_pton(AF_INET, str_ip, &remote.sin_addr);
		while (1) {
			printf("send b :%s\n", buf);
			sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *) &remote, sizeof (sockaddr_in));
			if (-1 == res)
				error_quit("sendto error");
			sleep(5);
		}
	}//第二个客户端的行为
	else {
		//从主服务器返回的信息中取出客户端1的IP+port和自己公网映射后的port
		sscanf(buf, "%s %d %d", other.ip, &other.port, &port);

		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);

		int rc = pthread_create(&pthreadfd, NULL,recvfd, (void *)&sockfd);
		if (rc){
         error_quit("无法创建线程 error");
         exit(-1);
		  }
		while (1) {
			printf("test connect A: %s %d\n", other.ip, other.port);
			sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *) &connaddr, sizeof (sockaddr_in));
		}
	}

	return 0;
}

测试结果:

服务器在公网云上,客户端一个是办公网的有线,一个是办公网的wifi,两个网络均是局域网,测试后两个局域网的公网映射IP不同,两个客户端绑定的内部端口也在NAT上进行了端口映射。nat类型通过检测工具得出,两个Nat均是Port Restricted Cone NAT。互相打洞两次后均能收到对方发来的消息。实验成功。

 

 

 

相关资讯

    暂无相关的资讯...

共有访客发表了评论 网友评论

验证码: 看不清楚?
    -->