简易p2p(中间人服务器)

先重新了解一些NAT穿透

一、什么是NAT?为什么要使用NAT?
NAT是将私有地址转换为合法IP地址的技术,通俗的讲就是将内网与内网通信时怎么将内网私有IP地址转换为可在网络中传播的合法IP地址。NAT的出现完美地解决了lP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。  
二、NAT的分类
    STUN标准中,根据内部终端的地址(LocalIP:LocalPort)到NAT出口的公网地址(PublicIP:PublicPort)的影射方式,把NAT分为四种类型:
1、Full Cone NAT: 内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,任何外部主机只要知道这个(PublicIP:PublicPort)就可以发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。 
2、Restricted Cone NAT: 内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机IP发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,任何端口)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。
3、Port Restricted Cone NAT: 内网主机建立一个socket(LocalIP:LocalPort) 第一次使用这个socket给外部主机发送数据时NAT会给其分配一个公网(PublicIP:PublicPort),以后用这个socket向外面任何主机发送数据都将使用这对(PublicIP:PublicPort)。此外,如果任何外部主机想要发送数据给这个内网主机,只要知道这个(PublicIP:PublicPort)并且内网主机之前用这个socket曾向这个外部主机(IP,Port)发送过数据。只要满足这两个条件,这个外部主机就可以用自己的(IP,Port)发送数据给(PublicIP:PublicPort),内网的主机就能收到这个数据包。
4、Symmetric NAT: 内网主机建立一个socket(LocalIP,LocalPort),当用这个socket第一次发数据给外部主机1时,NAT为其映射一个(PublicIP-1,Port-1),以后内网主机发送给外部主机1的所有数据都是用这个(PublicIP-1,Port-1),如果内网主机同时用这个socket给外部主机2发送数据,NAT会为其分配一个(PublicIP-2,Port-2), 以后内网主机发送给外部主机2的所有数据都是用这个(PublicIP-2,Port-2).如果NAT有多于一个公网IP,则PublicIP-1和PublicIP-2可能不同,如果NAT只有一个公网IP,则Port-1和Port-2肯定不同,也就是说一定不能是PublicIP-1等于 PublicIP-2且Port-1等于Port-2。此外,如果任何外部主机想要发送数据给这个内网主机,那么它首先应该收到内网主机发给他的数据,然后才能往回发送,否则即使他知道内网主机的一个(PublicIP,Port)也不能发送数据给内网主机,这种NAT无法实现P2P通信,但是如果另一方是Full Cone NAT,还是可以实现穿透的,下面我会详细分析各种类型NAT穿透的情况。
NAT 功能通常被集成到路由器、防火墙、ISDN路由器或者单独的NAT设备中。所以我们大家很少会知道NAT,上面NAT类型的概念描述是比较通俗的,但为了更便于理解,我再举例阐述一下NAT的原理。
    现有通信的双方A和B,当A和B都是在公网的时候,通信是不用NAT的。假设A在内网,内网IP是192.168.1.3,端口号是5000,A经过NAT后的IP是221.221.221.100,端口号是8000,B的IP是202.105.124.100,端口是8500。如果B要去主动连接A,即使B知道A经过NAT后的IP和端口也是无法连接成功的,因为A没有向B(202.105.124.100:8500)发送过数据,所以B的数据包会被A的NAT丢弃,于是连接失败。但是A如果去主动连接B,由于B是在公网,所以会连接成功,通信也就会建立。这也就是反弹连接木马“反弹”二字的精髓。
当客户端A和B都是处在内网的时候,双方由于都不知道对方的公网IP和端口,就会无从下手,所以要在客户端A和B之间架设一台服务器S来为它们牵线,而且S是处在公网,以保证A和B都能连接到S。客户端A和B登录时都首先连接S,S就会知道A和B经过NAT后的IP和端口,当A想要连接B时,就像S发出请求,S会把B经过NAT后的IP和端口告诉A,同时S向B发送A经过NAT后的IP和端口,并要求B发送数据给A,B发送数据到达A时会被A的NAT抛弃,但是B的NAT会有B发送数据到A的记录,这是A再向B发送数据时就会被B的NAT放行,因为B曾经向A的外网IP和端口发送过数据。可能有点乱,下面以故事的形式叙述一下这个情景。
人物:A(男) NAT_A(A家接线员)   B(女) NAT_B  (B家接线员) S
场景介绍:A想认识B,但是不知道B的电话,S跟A、B都是朋友,并且知道A和B的电话。接线员的职责:对往外转接的电话不做询问,对往内转接的电话则要过滤以免有骚扰电话。过滤规则:在一定时间内没有拨打过的号码就过滤。
首先A给S打电话:
         A说:我想认识你朋友B,你把她电话给我呗。
         S说:行,她的电话是PublicIP_B,我让她先给你打个电话,要不她家接线员不帮你转接。
         A说:好。

S跟B打电话:
         S说:我有一个朋友A,人挺好的,他想认识你,你给他打个电话,他的电话号码是PublicIP_A。
         B说:行,打完告诉你。
         S说:好的。

B打电话到A家,B家接线员NET_B看到女主人想往PublicIP_A打电话就转接到A家了,同时把号码PublicIP_A记录下来,A家接线员NAT_A一看号码是个近期没打过的号,就给挂断了。
 
B给S打电话:
         B说:我打完电话了
         S说:好,等着吧,一会他就给你打进来了。

S给A打电话:
         S说:他给你打完电话了,你快点给她打。
  
A打电话到B家, A家接线员NET_A看到男主人想往PublicIP_B打电话就转接到B家了,B家接线员NET_B看到是刚刚拨过的PublicIP_A号码打过来的,就转接给B了,A和B的电话也就打通了。
A和B通话:
           A说:电话终于打通了,想认识你挺困难的。
           B说:是啊。
           ∶
           ∶
以上虽然和实际不太一样,但穿透的整体过程基本就是这样。A往B发送数据的唯一阻碍就是NET_B,所以想要成功发送数据,必须把NET_B穿一个洞,A是无法完成这项工作的,所以就得让B完成这个打洞操作,也就是让B往A发送数据,这样NET_B就会误以为A发送的数据是上次会话的一部分从而不予阻拦。
但是,由于NAT的类型没有一个统一的标准,所以NAT穿透使用的技术有很多种,穿透的成功率也不一样。还有些NAT类型的内网之间几乎无法穿透。下面我们用实例详细分析一下各种NAT类型穿透的可行性。

A机器在私网(192.168.0.3)     
    A侧NAT服务器(221.221.221.100)     
    B机器在另一个私网(192.168.0.5)     
    B侧NAT服务器(210.30.224.70)     
    C机器在公网(210.202.14.36)作为A和B之间的中介     
    A机器连接C机器,假使是A(192.168.0.3:5000)-> A侧NAT(转换后221.221.221.100:8000)-> C(210.202.14.36:2000)     
    B机器也连接C机器,假使是B(192.168.0.5:5000)-> B侧NAT(转换后210.30.224.70:8000)-> C(210.202.14.36:2000)     
    A机器连接过C机器后,A向C报告了自己的内部地址(192.168.0.3:5000),此时C不仅知道了A的外部地址(C通过自己看到的221.221.221.100:8000)也知道了A的内部地址。同理C也知道了B的外部地址(210.30.224.70:8000)和   内部地址(192.168.0.5:5000)。之后,C作为中介,把A的两个地址告诉了B,同时也把B的两个地址告诉了A。     
    假设A先知道了B的两个地址,则A从192.168.0.3:5000处同时向B的两个地址192.168.0.5:5000和210.30.224.70:8000发包,由于A和B在两个不同的NAT后面,故从A(192.168.0.3:5000)到B(192.168.0.5:5000)的包肯定不通,现在看A(192.168.0.3:5000)到B(210.30.224.70:8000)的包,分如下两种情况:     
1、B侧NAT属于Full  Cone  NAT     
    则无论A侧NAT属于Cone  NAT还是Symmetric  NAT,包都能顺利到达B。如果程序设计得好,使得B主动到A的包也能借用A主动发起建立的通道的话,则即使A侧NAT属于Symmetric  NAT,B发出的包也能顺利到达A。 
结论1:只要单侧NAT属于Full  Cone  NAT,即可实现双向通信。    
    2、B侧NAT属于Restricted  Cone或Port  Restricted  Cone     
    则包不能到达B。再细分两种情况     
  (1)、A侧NAT属于Restricted  Cone或Port  Restricted  Cone     
    虽然先前那个初始包不曾到达B,但该发包过程已经在A侧NAT上留下了足够的记录:A(192.168.0.3:5000)->(221.221.221.100:8000)->B(210.30.224.70:8000)。如果在这个记录没有超时之前,B也重复和A一样的动作,即向A(221.221.221.100:8000)发包,虽然A侧NAT属于Restricted   Cone或Port Restricted Cone,但先前A侧NAT已经认为A已经向B(210.30.224.70:8000)发过包,故B向A(221.221.221.100:8000)发包能够顺利到达A。同理,此后A到B的包,也能顺利到达。     
   结论2:只要两侧NAT都不属于Symmetric  NAT,也可双向通信。换种说法,只要两侧NAT都属于Cone  NAT,即可双向通信。     
  (2)、A侧NAT属于Symmetric  NAT     
   因为A侧NAT属于Symmetric  NAT,且最初A到C发包的过程在A侧NAT留下了如下记录:A(192.168.0.3:5000)->(221.221.221.100:8000)-> C(210.202.14.36:2000),故A到B发包过程在A侧NAT上留下的记录为:   
  A(192.168.0.3:5000)->(221.221.221.100:8001)->B(210.30.224.70:8000)(注意,转换后端口产生了变化)。而B向A的发包,只能根据C给他的关于A的信息,发往A(221.221.221.100:8000),因为A端口受限,故此路不通。再来看B侧NAT,由于B也向A发过了包,且B侧NAT属于Restricted   Cone或Port   Restricted  Cone,故在B侧NAT上留下的记录为:B(192.168.0.5:5000)->(210.30.224.70:8000)->A(221.221.221.100:8000),此后,如果A还继续向B发包的话(因为同一目标,故仍然使用前面的映射),如果B侧NAT属于Restricted   Cone,则从A(221.221.221.100:8001)来的包能够顺利到达B;如果B侧NAT属于Port  Restricted  Cone,则包永远无法到达B。     
  结论3:一侧NAT属于Symmetric  NAT,另一侧NAT属于Restricted   Cone,也可双向通信。     
   反过来想,则可以得出另一个结论:两个都是Symmetric NAT或者一个是Symmetric  NAT、另一个是Port Restricted Cone,则不能双向通信,因为NAT无法穿透。     
   上面的例子虽然只是分析了最初发包是从A到B的情况,但是,由于两者的对称性,前面得出的几条结论没有方向性,双向都适用。        
   我们上面得出了四条结论,natcheck网站则把他归结为一条:只要两侧NAT都属于Cone  NAT(含Full  Cone、Restricted  Cone和Port  Restricted  Cone三者),即可双向通信。没有把我们的结论3包括进去。
一般情况下,只有比较注重安全的大公司会使用Symmetric NAT,禁止使用P2P类型的通信,很多地方使用的都是Cone  NAT,因此穿透技术还是有发展前景的。
三、使用UDP、TCP穿透NAT
        上面讲的情况可以直接应用于UDP穿透技术中,使用TCP 协议穿透NAT 的方式和使用UDP 协议穿透NAT 的方式几乎一样,没有什么本质上的区别,只是将无连接的UDP 变成了面向连接的TCP 。值得注意是:
        1、 B在向A打洞时,发送的SYN 数据包,而且同样会被NAT_A 丢弃。同时,B需要在原来的socket 上监听,由于重用socket ,所以需要将socket 属性设置为SO_REUSEADDR 。
        A向B发送连接请求。同样,由于B到A方向的孔已经打好,所以连接会成功,经过3 次握手后,A到B之间的连接就建立起来了。具体过程如下:

1、 S启动两个网络侦听,一个叫【主连接】侦听,一个叫【协助打洞】的侦听。
2、 A和B分别与S的【主连接】保持联系。
3、 当A需要和B建立直接的TCP连接时,首先连接S的【协助打洞】端口,并发送协助连接申请。同时在该端口号上启动侦听。注意由于要在相同的网络终端上绑定到不同的套接字上,所以必须为这些套接字设置 SO_REUSEADDR 属性(即允许重用),否则侦听会失败。
4、 S的【协助打洞】连接收到A的申请后通过【主连接】通知B,并将A经过NAT-A转换后的公网IP地址和端口等信息告诉B。
5、 B收到S的连接通知后首先与S的【协助打洞】端口连接,随便发送一些数据后立即断开,这样做的目的是让S能知道B经过NAT-B转换后的公网IP和端口号。
6、 B尝试与A的经过NAT-A转换后的公网IP地址和端口进行connect,大多数路由器对于不请自到的SYN请求包直接丢弃而导致connect失败,但NAT-B会纪录此次连接的源地址和端口号,为接下来真正的连 接做好了准备,这就是所谓的打洞,即B向A打了一个洞,下次A就能直接连接到B刚才使用的端口号了。
7、 客户端B打洞的同时在相同的端口上启动侦听。B在一切准备就绪以后通过与S的【主连接】回复消息“我已经准备好”,S在收到以后将B经过NAT-B转换后的公网IP和端口号告诉给A。
8、 A收到S回复的B的公网IP和端口号等信息以后,开始连接到B公网IP和端口号,由于在步骤6中B曾经尝试连接过A的公网IP地址和端口,NAT-B纪录了此次连接的信息,所以当A主动连接B时,NAT-B会认为是合法的SYN数据,并允许通过,从而直接的TCP连接建立起来了。
   
         
参考网址:
http://midcom-p2p.sourceforge.net/draft-ford-midcom-p2p-01.txt
http://www.vckbase.com/document/viewdoc/?id=1773*转载请注明来自看雪论坛@PEdiy.com


在上次p2p代理的基础上修改,

1) 服务器先运行,等待mjpg-streamer注册,

2) mjpg-streamer发送登录消息给服务器,然后等待服务器请求。

3) 客户端向服务器发送请求,然后等待服务器响应。

4) 服务器收到客户端请求后,发送连接请求给mjpg-streamer,并等待mjpg-streamer响应。

5) mjpg-streamer收到服务器的连接请求后,先回复一个特征码;创建另外一个socket去连接服务器,并发送特征码的Md5值,然后将此socket设置监听,等待客户端连接。

6) 服务器先收到特征码,等待mjpg-streamer的另一个连接;收到另一个连接后,接收数据,校验是否为特征码的md5值;校验成功后发送mjpg-streamer的ip及port信息给客户端。

7) 客户端接收到mjpg-streamer信息后,向正常情况一样去连接。


下面是一个简易模型的部分代码,留下来做参考,希望对以后有帮助。

对一些情况未做考虑,同时只考虑只有一个mjpg-streamer注册到服务器,执行顺序就按上面说的流程走。

p2pserver.c:

void* connect_handler(void *arg)
{
	struct client *cli;
	int csockfd;
	struct p2pmsg msg;
	struct p2pdata data;
	int res;

	cli = (struct client*)arg;
	if(!cli) { 
		DBG("/SVR/: {connect_handler} arg is null !!!\n");
		pthread_exit(0);
	}

	csockfd = cli->sockfd;
	
	//DBG("/SVR/: {connect_handler} ipv4: %s, port: %d\n", inet_ntoa(cli->addr.sin_addr),
	//			ntohs(cli->addr.sin_port));
	while(1) {
		bzero(&msg, sizeof(msg));
		res = recv(csockfd, &msg, sizeof(msg), 0);
		if(res < 0) {
			perror("/SVR/: {connect_handler} recv msg");
			continue;
		} else if(0 == res){
			/* csockfd is shutdown */
			pthread_exit(0);
		} else if(msg.msg_type != CONNECTOR || msg.data_type != REQ4INFO) {
			fprintf(stderr, "/SVR/: {connect_handler} msg error");
			continue;
		}
		
		pthread_mutex_lock(&cntmtx);
		pthread_cond_signal(&lsncond);
		pthread_mutex_unlock(&cntmtx);
		pthread_cond_wait(&cntcond, &cntmtx);
		bzero(&msg, sizeof(msg));
		bzero(&data, sizeof(data));
		msg.msg_type = SERVER;
		msg.data_type = REPLY;
		msg.data_num = 1;
		data.msg_type = SERVER;
		data.data_type = REPLY;
		//DBG("/SVR/: {connect_handler} pthread_mutex_lock cntmtx\n");
		//pthread_mutex_lock(&cntmtx);
		//memcpy(data.data, &lsnaddr, sizeof(lsnaddr));
		sprintf(data.data, "%s:%d", inet_ntoa(lsnaddr.sin_addr), ntohs(lsnaddr.sin_port));
		//pthread_mutex_unlock(&cntmtx);
		DBG("/SVR/: {connect_handler} send reply msg to connector\n");
		while(send(csockfd, &msg, sizeof(msg), 0) < 0) {
			perror("/SVR/: {connect_handler} recv reply msg");
		}
		DBG("/SVR/: {connect_handler} send reply data to connector\n");
		while(send(csockfd, &data, sizeof(data), 0) < 0) {
			perror("/SVR/: {connect_handler} recv reply data");
		}
		DBG("/SVR/: {connect_handler} send reply end\n");
	}
	pthread_exit(0);
}

/*[thread_func]*/  
static void cleanup_handler(void *arg)  
{  
    DBG("/SVR/: {cleanup_handler} Cleanup handler of second thread./n");  
    pthread_mutex_unlock(&lsnmtx);  
} 

#include "md5.h"

void* listen_handler(void *arg)
{
	int csockfd;
	struct sockaddr_in addr;
	socklen_t len = sizeof(struct sockaddr_in);
	struct p2pmsg msg;
	struct p2pdata data;
	int i;
	unsigned char md5[16] = {0};
	unsigned char recvmd5[16] = {0};
  
    pthread_cleanup_push(cleanup_handler, NULL);
	bzero(&addr, sizeof(addr));
	while(1) {
		pthread_mutex_lock(&lsnmtx);
		csockfd = accept(lsnfd, (struct sockaddr*)&addr, &len);
		pthread_mutex_unlock(&lsnmtx);
		if(-1 == csockfd) {
			perror("/SVR/: {listen_handler} accept");
			continue;
		}

		while(1) {
			if(recv(csockfd, &msg, sizeof(msg), 0) < 0) {
				perror("/SVR/: {listen_handler} recv msg");
				continue;
			} else
				break;
		}
		if(msg.msg_type != LISTENER || msg.data_type != LOGIN) {
			send(csockfd, "Illegal logining !!! Please login",
					strlen("Illegal logining !!! Please login"), 0);
			close(csockfd);
			csockfd = -1;
			bzero(&addr, sizeof(addr));
			continue;
		} else {
			break;
		}
	}
	//DBG("/SVR/: {listen_handler} htttp server: %s:%d\n", 
	//			inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
	
	while(1) {
		pthread_mutex_lock(&lsnmtx);
		pthread_cond_wait(&lsncond, &lsnmtx);
		pthread_mutex_unlock(&lsnmtx);
		bzero(&msg, sizeof(msg));
		msg.msg_type = SERVER;
		msg.data_type = REQ4CNT;
		while(send(csockfd, &msg, sizeof(msg), 0) < 0) {
			perror("/SVR/: {listen_handler} send REQ4CNT msg");
		}
		while(1) {
			DBG("/SVR/: recv REPLY msg\n");
			bzero(&msg, sizeof(msg));
			if(recv(csockfd, &msg, sizeof(msg), 0) < 0) {
				perror("/SVR/: {listen_handler} recv REPLY msg for REQ4CNT");
			} else if(msg.msg_type == LISTENER &&
						msg.data_type == REPLY &&
						msg.data_num != 0) {
				break;
			}
		}
		DBG("/SVR/: recv REPLY data\n");
		/* recv the verify data(listener's fingerprint information) */
		while(1) {
			bzero(&data, sizeof(data));
			if(recv(csockfd, &data, sizeof(data), 0) < 0) {
				perror("/SVR/: {listen_handler} recv data");
				continue;
			} else if(data.msg_type == LISTENER &&
						data.data_type == REPLY) {
				break;
			}
		}
		if(createmd5("gaoke", md5) < 0) {
			DBG("Create md5 error !!!\n");
		}
		
		/* accept new connect, */
		while(1) {
			bzero(&addr, sizeof(addr));
			pthread_mutex_lock(&lsnmtx);
			DBG("/SVR/: {listen_handler} accept wait\n");
			i = accept(lsnfd, (struct sockaddr*)&addr, &len);
			DBG("/SVR/: {listen_handler} connected\n");
			pthread_mutex_unlock(&lsnmtx);
			if(i > 0) {
				/* and recv data to compare with the verify data */
				/* recv the verify data(listener's fingerprint information) */
				bzero(&data, sizeof(data));
				if(recv(i, &data.data, sizeof(data.data), 0) < 0) {
					perror("/SVR/: {listen_handler} recv md5 data");
					continue;
				} else {
					close(i);
					memcpy(recvmd5, data.data, sizeof(recvmd5));
					for(i=0; i<sizeof(recvmd5); i++) {
						if(recvmd5[i] != md5[i]) {
							i = -1;
							DBG("/SVR/: {listen_handler} md5 data error\n");
							continue;
						}
					}
					memcpy(&lsnaddr, &addr, sizeof(lsnaddr));
					fprintf(stderr, "/SVR/: mjpg server : %s:%d\n", inet_ntoa(lsnaddr.sin_addr),
								ntohs(lsnaddr.sin_port));
					pthread_cond_signal(&cntcond);
					break;
				}
			}
		}
	}
	pthread_cleanup_pop(0);
	pthread_exit(0);
}


void server_run(void)
{
	socklen_t optival = 1;
	struct sockaddr_in addr;
	int res;
	socklen_t len;
	int cur_num;

	signal(SIGUSR2, server_sig_handler);

	bzero(p2pcli, sizeof(p2pcli));

	/* -------------------------------------------------------------------- */
	lsnfd = socket(AF_INET, SOCK_STREAM, 0);

	setsockopt(lsnfd, SOL_SOCKET, SO_REUSEADDR, &optival, sizeof(optival));
	if(-1 == lsnfd) {
		perror("/SVR/: [listen side] socket");
		return;
	}
	fprintf(stderr, "/SVR/: [listen side] create sockfd successfully !\n");
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons(LSNPORT);
	addr.sin_addr.s_addr = INADDR_ANY;	
		
	res = bind(lsnfd, (struct sockaddr*)&addr, sizeof(addr));
	if(-1 == res) {
		perror("/SVR/: [listen side] bind");
		goto listen_err;
	}
	fprintf(stderr, "/SVR/: [listen side] bind sockfd successfully !\n");
	
	res = listen(lsnfd, MAXLINK);
	if(-1 == res) {
		perror("/SVR/: [listen side] listen");
		goto listen_err;		
	}
	fprintf(stderr, "/SVR/: [listen side] listen port %u successfully !\n", LSNPORT);

	/* -------------------------------------------------------------------- */
	cntfd = socket(AF_INET, SOCK_STREAM, 0);

	setsockopt(cntfd, SOL_SOCKET, SO_REUSEADDR, &optival, sizeof(optival));
	if(-1 == cntfd) {
		perror("/SVR/: [connect side] socket");
		goto listen_err;
	}
	fprintf(stderr, "/SVR/: [connect side] create sockfd successfully !\n");
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons(CNTPORT);
	addr.sin_addr.s_addr = INADDR_ANY;	
		
	res = bind(cntfd, (struct sockaddr*)&addr, sizeof(addr));
	if(-1 == res) {
		perror("/SVR/: [connect side] bind");
		goto connect_err;
	}
	fprintf(stderr, "/SVR/: [connect side] bind sockfd successfully !\n");
	
	res = listen(cntfd, MAXLINK);
	if(-1 == res) {
		perror("/SVR/: [connect side] listen");
		goto connect_err;		
	}
	fprintf(stderr, "/SVR/: [connect side] listen port %u successfully !\n", CNTPORT);
	/* -------------------------------------------------------------------- */

	/* listen side */
	do {
		res = pthread_create(&lsntid, NULL, listen_handler, NULL);
		if(res != 0) {
			fprintf(stderr, "/SVR/: [listen side] pthread_create failed !\n");
		}
	} while(res);


	
	/* connect side */
	cur_num = 0;
	while(1) {
		len = sizeof(struct sockaddr_in);
		cur_num = find_num();
		fprintf(stderr, "/SVR/: [connect side] current num of client: %d\n", cur_num);
		if(-1 == cur_num) {
			fprintf(stderr, "/SVR/: [connect side] [WARNING] The number of connections"
							" has reached the maxinum !!!\n");
			while((cur_num = find_num()) == -1);
		}

		p2pcli[cur_num].sockfd = accept(cntfd, (struct sockaddr*)&(p2pcli[cur_num].addr), &len);
		if(-1 == p2pcli[cur_num].sockfd) {
			perror("/SVR/: [connect side] accept");
			continue;
		}
		p2pcli[cur_num].flag = USED;

		res = pthread_create(&(p2pcli[cur_num].tid), NULL, connect_handler, &p2pcli[cur_num]);
		if(res != 0) {
			fprintf(stderr, "/SVR/: [connect side] pthread_create port failed !\n");
			close(p2pcli[cur_num].sockfd);
			p2pcli[cur_num].flag = EMPTY;
			continue;
		}
	
		usleep(20);
	}	

connect_err:
	close(cntfd);

listen_err:
	close(lsnfd);
}

int main(void)
{
	signal(SIGINT, sig_handler);

	lsnfd = -1;
	cntfd = -1;
	cli_num = 0;

	childpid = vfork();
	if(0 == childpid) {
		/* let child use parent's space, but the atpid is setted to value 0 by child */
		/* so must set it to child's pid */
		childpid = getpid();
		/* we use vfork(), so if use it, the parent will ignore too. */
		//signal(SIGINT, SIG_IGN); 
		server_run();
	} else {
		waitpid(childpid, NULL, 0);
	}
}

p2plistener.c:

void* request_handler(void *arg)
{
	uint16_t port;
	socklen_t optival = 1;
	struct sockaddr_in addr;
	int res;
	socklen_t len;
	int i;
	unsigned char md5[16];
	int tmpfd;
	
	/* reply p2pserver */
	do {
		tmpfd = socket(AF_INET, SOCK_STREAM, 0);
		perror("/LSN/: {http_handler} socket");
	} while(tmpfd < 0);
	
	setsockopt(tmpfd, SOL_SOCKET, SO_REUSEADDR, &optival, sizeof(optival));
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(HTTPPORT);
	addr.sin_addr.s_addr = INADDR_ANY;	
	if(bind(tmpfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
		perror("/LSN/: {http_handler} bind");
	}

	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(P2PPORT);
	addr.sin_addr.s_addr = inet_addr(SERVERIP);	
	len = sizeof(addr);
	while(connect(tmpfd, (struct sockaddr*)&addr, len) < 0) {
		perror("/LSN/: connect");
	}
	fprintf(stderr, "/LSN/: connect %s:%u successfully !\n", SERVERIP, P2PPORT);
	while(createmd5("gaoke", md5) < 0) {
		fprintf(stderr, "Create md5 error !!!\n");
	}
	while(send(tmpfd, md5, sizeof(md5), 0) < 0) {
		perror("/LSN/: send md5");
	}
	/* get its info */
	bzero(&addr, sizeof(addr));
	while(getsockname(tmpfd, (struct sockaddr*)&addr, &len) < 0) {
		perror("/LSN/: get my info");
	}
	port = ntohs(addr.sin_port);
	printf("/LSN/ %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
	
	close(tmpfd);
	
	/* bind */
	do {
		httpfd = socket(AF_INET, SOCK_STREAM, 0);
		perror("/LSN/: {http_handler} socket");
	} while(httpfd < 0);
	
	bzero(&addr, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;	
	
	setsockopt(httpfd, SOL_SOCKET, SO_REUSEADDR, &optival, sizeof(optival));
	while(bind(httpfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
		perror("/LSN/: {http_handler} bind");
		pthread_exit(0);
	}
	fprintf(stderr, "/LSN/: {http_handler} bind sockfd successfully !\n");

	/* get its info */
	/*
	bzero(&addr, sizeof(addr));
	while(getsockname(httpfd, (struct sockaddr*)&addr, &len) < 0) {
		perror("/LSN/: get my info");
	}
	printf("/LSN/ %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
	*/
	
	while(listen(httpfd, MAXLINK) < 0) {
		perror("/LSN/: {http_handler} listen");
	}
	fprintf(stderr, "/LSN/: {http_handler} listen port %u successfully !\n", port);
	pthread_exit(0);
}

void listener_run(void)
{
	struct sockaddr_in addr;
	int res;
	socklen_t len;
	int i;

	struct p2pmsg msg;
	struct p2pdata data;

	sockfd = socket(AF_INET, SOCK_STREAM, 0);

	fprintf(stderr, "/LSN/: create sockfd successfully !\n");
	
	addr.sin_family = AF_INET;
	addr.sin_port = htons(P2PPORT);
	addr.sin_addr.s_addr = inet_addr(SERVERIP);	
	len = sizeof(addr);
	res = connect(sockfd, (struct sockaddr*)&addr, len);
	if(-1 == res) {
		perror("/LSN/: connect");
		goto connect_err;		
	}
	fprintf(stderr, "/LSN/: connect sockfd port %u successfully !\n", P2PPORT);

	/* Login */
	msg.msg_type = LISTENER;
	msg.data_type = LOGIN;
	msg.data_num = 0;  /* now we donot use user:password */
	while(send(sockfd,&msg, sizeof(msg), 0) < 0) {
		perror("/LSN/: login error");
	}

	while(1) {
		if(recv(sockfd, &msg, sizeof(msg), 0) < 0) {
			perror("/LSN/: recv msg");
			continue;
		} 
		
		if(msg.msg_type != SERVER) {
			fprintf(stderr, "/LSN/: recv msg from unknown where !\n");
			continue;
		}
		
		fprintf(stderr, "/LSN/: recv msg from p2pserver !\n");
		if(msg.data_type == REQ4CNT) {
			bzero(&msg, sizeof(msg));
			msg.msg_type = LISTENER;
			msg.data_type = REPLY;
			msg.data_num = 1;
			while(send(sockfd, &msg, sizeof(msg), 0) < 0) {
				perror("/LSN/: send msg");
			}
			fprintf(stderr, "/LSN/: send reply to p2pserver !\n");
			bzero(&data, sizeof(data));
			data.msg_type = LISTENER;
			data.data_type = REPLY;
			strcpy(data.data, FINGERPRINT);
			while(send(sockfd, &data, sizeof(data), 0) < 0) {
				perror("/LSN/: send data");
			}
			do {
				res = pthread_create(&mjpg_server, NULL, request_handler, NULL);
			} while(res < 0);
			pthread_join(mjpg_server, NULL);
			mjpg_server = -1;
			/* wait to client to connect */
			bzero(&addr, sizeof(addr));
			do {
				clisock = accept(httpfd, (struct sockaddr*)&addr, &len);
			} while(clisock < 0);
			while(1) {
				bzero(data.data, sizeof(data.data));
				res = recv(clisock, data.data, sizeof(data.data), 0);
				if(res) {
					fprintf(stderr, "server: recv %s\n", data.data);
					send(clisock, "ok", strlen("ok"), 0);
				} else if(0 == res) {
					break;
				}
			}
			close(clisock);
			clisock = -1;
			close(httpfd);
			httpfd = -1;
		}
	}	
	
connect_err:
	close(sockfd);
	return ;
}

int main(void)
{
	signal(SIGINT, sig_handler);

	childpid = vfork();
	if(0 == childpid) {
		/* let child use parent's space, but the atpid is setted to value 0 by child */
		/* so must set it to child's pid */
		childpid = getpid();
		listener_run();
	} else {
		waitpid(childpid, NULL, 0);
	}
}

p2pconnector.c:

void connector_run(void)
{
    struct sockaddr_in addr;
    int res;
    socklen_t len;
    int i;

    struct p2pmsg msg;
    struct p2pdata data;

    unsigned char buf[100];

    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    fprintf(stderr, "/CNT/: create sockfd successfully !\n");
    
    addr.sin_family = AF_INET;
    addr.sin_port = htons(P2PPORT);
    addr.sin_addr.s_addr = inet_addr(SERVERIP);    
    len = sizeof(addr);
    res = connect(sockfd, (struct sockaddr*)&addr, len);
    if(-1 == res) {
        perror("/CNT/: connect");
        goto connect_err;        
    }
    fprintf(stderr, "/CNT/: connect %s:%u successfully !\n", SERVERIP, P2PPORT);

    /* request */
    msg.msg_type = CONNECTOR;
    msg.data_type = REQ4INFO;
    msg.data_num = 0;  /* now we donot use user:password */
    while(send(sockfd,&msg, sizeof(msg), 0) < 0) {
        perror("/CNT/: request error");
        bzero(&msg, sizeof(msg));
    }
    fprintf(stderr, "/CNT/: send request to p2pserver\n");
    
    bzero(&msg, sizeof(msg));
    while(recv(sockfd, &msg, sizeof(msg), 0) < 0) {
        perror("/CNT/: recv msg");
        bzero(&msg, sizeof(msg));
    }
    if(msg.msg_type != SERVER || msg.data_type != REPLY)
        fprintf(stderr, "/CNT/: recv msg from unknown where !\n");
    else {
        fprintf(stderr, "/CNT/: recv msg from p2pserver !\n");
        while(1) {
            bzero(&data, sizeof(data));
            if(recv(sockfd, &data, sizeof(data), 0) < 0) {
                perror("/CNT/: recv data");
            } else {
                bzero(buf, sizeof(buf));
                for(i=0;i<30;i++) {
                    sprintf(&buf[strlen(buf)], "%02x ", data.data[i]);
                }
                if(data.msg_type == SERVER && data.data_type == REPLY)
                    break;
            }
        }
        fprintf(stderr, "/CNT/: recv data from p2pserver !\n");
        fprintf(stderr, "/CNT/: {connector_run} htttp server: %s\n", data.data);
        /*
        bzero(&addr, sizeof(addr));
        memcpy(&addr, data.data, sizeof(addr));
        fprintf(stderr, "/CNT/: {connector_run} htttp server: %s:%d\n",
                inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        mjpg_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(connect(mjpg_sockfd, (struct sockaddr*)&addr, len) < 0) {
            perror("/CNT/: {connector_run} connect");
            goto connect_err;
        }
        while(1) {
            send(mjpg_sockfd, "connect ...", strlen("connect ..."), 0);
            bzero(data.data, sizeof(data.data));
            recv(mjpg_sockfd, data.data, sizeof(data.data), 0);
            fprintf(stderr, "client: recv %s\n", data.data);
            sleep(2);
        }
        */
    }
    
connect_err:
    close(sockfd);
    return ;
}

int main(void)
{
    signal(SIGINT, sig_handler);

    childpid = vfork();
    if(0 == childpid) {
        /* let child use parent's space, but the atpid is setted to value 0 by child */
        /* so must set it to child's pid */
        childpid = getpid();
        connector_run();
    } else {
        waitpid(childpid, NULL, 0);
    }
}

不过在内网中测试可以,但外网测试,可以获取信息,但不能通信

你可能感兴趣的:(简易p2p(中间人服务器))