公网服务器端:
单线程,循环接收连接请求。之后转发两个客户端的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。互相打洞两次后均能收到对方发来的消息。实验成功。