NAT穿透进行P2P文件传输

实现一个简单的p2p文件传输,主要解决NAT穿透问题,使用tcp协议传输。

NAT背景介绍

简介

NAT(Network Address Translation ,网络地址转换) 是一种广泛应用的解决IP 短缺的有效方法, NAT 将内网地址转和端口号换成合法的公网地址和端口号,建立一个会话,与公网主机进行通信。

NAT 不仅实现地址转换,同时还起到防火墙的作用,隐藏内部网络的拓扑结构,保护内部主机。 NAT 不仅完美地解决了 lP 地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。 这样对于外部主机来说,内部主机是不可见的。

但是,对于P2P 应用来说,却要求能够建立端到端的连接,所以如何穿透NAT 也是P2P 技术中的一个关键。

分类

NAT 从表面上看有三种类型:

  1. 静态NAT :静态地址转换将内部私网地址与合法公网地址进行一对一的转换,且每个内部地址的转换都是确定的。
  2. 动态NAT :动态地址转换也是将内部本地地址与内部合法地址一对一的转换,但是动态地址转换是从合法地址池中动态选择一个未使用的地址来对内部私有地址进行转换。
  3. 地址端口转换NAPT :它也是一种动态转换,而且多个内部地址被转换成同一个合法公网地址,使用不同的端口号来区分不同的主机,不同的进程。

从实现的技术角度,又可以将NAT 分成如下几类:

  1. 全锥NAT :全锥NAT 把所有来自相同内部IP 地址和端口的请求映射到相同的外部IP 地址和端口。任何一个外部主机均可通过该映射发送数据包到该内部主机。
  2. 限制性锥NAT :限制性锥NAT 把所有来自相同内部IP 地址和端口的请求映射到相同的外部IP 地址和端口。但是, 和全锥NAT 不同的是:只有当内部主机先给外部主机发送数据包, 该外部主机才能向该内部主机发送数据包。
  3. 端口限制性锥NAT :端口限制性锥NAT 与限制性锥NAT 类似, 只是多了端口号的限制, 即只有内部主机先向外部地址:端口号对发送数据包, 该外部主机才能使用特定的端口号向内部主机发送数据包。
  4. 对称NAT :对称NAT 与上述3 种类型都不同, 不管是全锥NAT ,限制性锥NAT 还是端口限制性锥NAT ,它们都属于锥NAT (Cone NAT )。当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, 对称NAT 会重新建立一个Session ,为这个Session 分配不同的端口号,或许还会改变IP 地址。

解决问题

了解了NAT之后,开始思考如何解决两台在不同的NAT后面的主机直接相连的问题。静态NAT只要知道所给的公网地址即可,不在我们讨论的范围内。

思考问题并找到重点

假设有主机A和主机B分别在两个NAT转换设备NATA和NATB后面。


A与B之间要通信,我们可假设NATA中转发表有下面这个表项:

内网IP:Port 公网IP:Port
192.168.0.2:7000 202.103.142.29:5000

NATB转发表中如下:

内网IP:Port 公网IP:Port
192.168.1.12:8000 221.10.145.84:6000

这样A中绑定了 192.168.0.2:7000 的socket只需要连接221.10.145.84:6000即可与B中绑定了192.168.1.12:8000的socket进行通信。B同理。

所以如何在转发表中留下这样一个表项并让对方知道并可以连接就是我们要解决的重点。

解决重点

首先转发表中没有转发表项的话,两方无论如何也是无法连上的。这时候我们就需要借助有公网ip的Server帮我们搭个桥。

还是使用这张图


A与Server 129.208.12.38 相连,在NAT-A中插入

内网IP:Port 公网IP:Port
192.168.0.2:7000 202.103.142.29:5000

B也与Server 129.208.12.38 相连,在NAT-B中插入

内网IP:Port 公网IP:Port
192.168.1.12:8000 221.10.145.84:6000

然后服务器将 A 的源地址和端口 202.103.142.29:5000 发给 B, 将 B 的源地址和端口 221.10.145.84:6000 发给 A 。这样双方就有了对方的外部IP地址和端口的信息。

这时候对于全锥NAT来说就可以直接相连了,但是对于 端口限制性锥NAT限制性锥NAT 还不可以直接相连。因为只有当内部主机先给外部主机发送数据包, 该外部主机才能向该内部主机发送数据包。

如果有一方是(端口)限制性锥形NAT,就得由这一方作为客户端主动相连,另一方作为服务端进行连接。如果双方都是(端口)限制性锥形NAT,就得先由一方先行与对方连接,结果必然失败,但是在这一方的NAT中保留了接受对方IP和端口的信息,称之为打孔。这时候另一方再与先发送请求的一方连接即可成功。

对于对称NAT, 由于当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, 对称NAT都会重新建立一个Session ,为这个Session 分配不同的端口号,或许还会改变IP地址。穿透起来非常麻烦。若有兴趣可参考文后链接。


对于udp来说,直接发送数据即可。但是对于tcp来说,由于需要在短时间内绑定同一端口连接不同地址,所以需要设置socket选项SOL_SOCKET level的SO_REUSEADDR为True。一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。

实现代码(Python)

服务器于阿里云,长春与重庆连接试验成功。可以本地指定不同端口试验。

获取本机ip地址在本地地址较多时可能获取得不对。还未找到办法。

发送双方ip:port信息时 我根据先来后到标记了 1 和 0 ,通过判断这个来决定是否为主动连接那一方。

仅为实验代码,多有纰漏请指出。

主机端:

 1 import os
 2 from time import sleep
 3 import struct
 4 import socket
 5
 6 def p2p_connect(local_address, local_port, send_file_path, recv_folder_path,server_address,server_port):
 7     if not os.path.exists(send_file_path):
 8         raise FileNotFoundError(recv_folder_path)
 9     if not os.path.exists(recv_folder_path):
10         os.mkdirs(recv_folder_path)  # 若为windows 只有mkdir
11     sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
12     sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
13     sock.bind((local_address, local_port))
14     sock.connect((server_address, server_port))
15     rcv_msgs = sock.recv(1024).decode()
16     while rcv_msgs.startswith("#"):
17         print(rcv_msgs)
18         rcv_msgs = sock.recv(1024).decode()
19     rcv_msgs = rcv_msgs.split("|")
20     remote_addr = rcv_msgs[0]
21     remote_port = int(rcv_msgs[1])
22     is_server = rcv_msgs[2] == "0"
23     print(rcv_msgs)
24     sock.close()
25
26     if is_server:
27         try_conn = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)  # 打孔
28         try_conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
29         try_conn.bind((local_address, local_port))
30         try_conn.connect_ex((remote_addr, remote_port))
31         try_conn.close()
32         recv_sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
33         recv_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
34         recv_sock.bind((local_address, local_port))
35         recv_sock.listen(1)
36         conn, addr = recv_sock.accept()
37         conn.sendall(os.path.split(send_file_path)[1].encode())  # 发送文件名
38         with open(send_file_path, "rb") as f:
39             size = os.path.getsize(send_file_path)
40             print("共发送", size, "字节")
41             conn.sendall(struct.pack(">I", size))  # 发送文件大小
42             data = f.read(1024)
43             while data:
44                 conn.sendall(data)
45                 data = f.read(1024)
46             conn.sendall("")
47         conn.close()
48         recv_sock.close()
49     else:
50         conn = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
51         while conn.connect_ex((remote_addr, remote_port)) != 0:  # 注意网络情况,可能为死循环
52             sleep(1)
53         file_name = conn.recv(1024).decode()  # 接收文件名
54         size = struct.unpack(">I", conn.recv(1024))[0]  # 接收文件大小
55         print("接收 : ", file_name, "  (", size, "bytes)")
56         with open(os.path.join(recv_folder_path,file_name), "wb") as f:
57             count = 0
58             data = conn.recv(1024)
59             print("\r已完成 : {:.0f}%".format(count / size*100), end="", flush=True)
60             while data:
61                 f.write(data)
62                 length = len(data)
63                 count += length
64                 print("\r已完成 : {:.0f}%".format(count / size*100), end="", flush=True)
65                 data = conn.recv(1024)
66         print(" 传输完成")
67         conn.close()
68
69 if __name__ == ‘__main__‘:
70     name = socket.gethostname()
71     local_port = 22000  # 本地端口
72     local_address = socket.gethostbyname(name)  #本地地址
73     file_path="text.xml"  # 待传输文件
74     folder_path=""  # 接收文件文件夹
75     remote_address="123.45.67.89"  # 服务器地址
76     remote_port=30000  # 服务器端口
77     p2p_connect(local_address,local_port,file_path,folder_path,remote_address,30000)

服务器端:

 1 import socket
 2
 3 sock = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
 4 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 5 sock.bind(("123.45.67.89", 30000))
 6 sock.listen(5)
 7
 8 conn1, addr1 = sock.accept()
 9 conn1_info = addr1[0] + "|" + str(addr1[1]) + "|0"
10 conn1.sendall("#你已连接上,请等待另一名用户\n".encode())
11 conn2, addr2 = sock.accept()
12 conn2_info = addr2[0] + "|" + str(addr2[1]) + "|1"
13 conn2.sendall("#你已连接上,另一名用户已就绪\n".encode())
14
15 conn1.sendall(conn2_info.encode())
16 conn2.sendall(conn1_info.encode())
17
18 conn1.close()
19 conn2.close()
20 sock.close()


背景参考: P2P,UDP和TCP穿透NAT

时间: 2024-11-05 11:39:00

NAT穿透进行P2P文件传输的相关文章

【原创】IP摄像头技术纵览(七)---P2P技术—UDP打洞实现内网NAT穿透

[原创]IP摄像头技术纵览(七)-P2P技术-UDP打洞实现内网NAT穿透 本文属于<IP摄像头技术纵览>系列文章之一: Author: chad Mail: [email protected] 本文可以自由转载,但转载请务必注明出处以及本声明信息. NAT技术的实际需求在10几年前就已经出现,为了解决这个问题,10几年来全世界的牛人早已经研究好了完整的解决方案,网上有大量优秀的解决方案文章,笔者自知无法超越,所以秉承拿来主义,将优秀文章根据个人实验及理解整理汇录于此,用于解释IP摄像头整个技

java基于P2P的聊天和文件传输实例

用java的NIO技术编写的 1. 支持聊天功能 2. 拖拽文件能够实现文件传输功能.也能够是目录 3. 启动时能够选择server端或client端启动 4. 本人原创.学习NIO和java的网络通信一定会不错 5. 下载下来能够直接导致eclipse.然后执行 代码下载路径例如以下: http://download.csdn.net/detail/tqtihihc/8266793 截图:

Linux socket跨局域网聊天和文件传输

一直想写一个跨局域网聊天和文件传输,以及视频聊天的软件,这两天刚好闲着没啥事就把代码写完了,代码已经上传至github:https://github.com/vinllen/chat 其实之前想法P2P模式,P2P的话必须穿透NAT,现在的NAT有4种模式: 完全圆锥型NAT 受限圆锥型NAT 端口受限圆锥型NAT 对称NAT(双向NAT) 维基百科给出的定义如下: 1.Full cone NAT,亦即著名的一对一(one-to-one)NAT 一旦一个内部地址(iAddr:port1)映射到外

NAT穿透技术

在现实Internet网络环境中,大多数计算机主机都位于防火墙或NAT之后,只有少部分主机能够直接接入Internet.很多时候,我们希望网络中的两台主机能够直接进行通信,即所谓的P2P通信,而不需要其他公共服务器的中转.由于主机可能位于防火墙或NAT之后,在进行P2P通信之前,我们需要进行检测以确认它们之间能否进行P2P通信以及如何通信.这种技术通常称为NAT穿透(NAT Traversal).最常见的NAT穿透是基于UDP的技术,如RFC3489中定义的STUN协议. STUN,首先在RFC

WebRTC中NAT穿透浅析

说NAT穿透之前先说几个关于WebRTC的概念,可能之前有跟作者一样对WebRTC概念理解错误的同学.. WebRTC(网络实时通信)它是一个支持网页浏览器进行实时语音对话或视频对话的技术,它为我们提供了视频会议的核心技术,包括音视频的采集.编解码.网络传输.显示等功能,并且还支持跨平台:windows,linux,mac,android,iOS. 1.WebRTC的实现原理 webRTC是基于P2P的网络通信,可能有同学不太了解P2P是什么,在此简单解释一下 P2P就是点对点的通信. 下面就拿

(转)NAT与NAT穿透 原理

原文:http://blog.csdn.net/ustcgy/article/details/5655050 5. NAT穿透5.1 转发     最可靠但又是最低效的点对点通信方法,莫过于将p2p网络通信看作一个C/S结构,通过服务器来转发信息.如下图,两个客户端A和B,均与服务器S初始化了一个TCP或UDP连接,服务器S具有公网固定IP地址,两个客户端分布在不同的私网中,这样,他们各自的NAT代理服务器将不允许他们进行直连.                                  

NAT与NAT穿透(二)

5. NAT穿透 5.1 转发 最可靠但又是最低效的点对点通信方法,莫过于将p2p网络通信看作一个C/S结构,通过服务器来转发信息.如下图,两个客户端A和B,均与服务器S初始化了一个TCP或UDP连接,服务器S具有公网固定IP地址,两个客户端分布在不同的私网中,这样,他们各自的NAT代理服务器将不允许他们进行直连. Server S | | +----------------------+----------------------+ |                             

NAT穿透的方式

目前主要的NAT类型有如下几种: 1)Full-cone NAT, also known as one-to-one NAT 一旦一个内网地址 (iAddr:iPort) 被映射到一个外部地址 (eAddr:ePort), 来自 iAddr:iPort 的任何数据包将通过 eAddr:ePort 发送. 任何外部主机能够通过eAddr:ePort这个地址发送数据包到iAddr:iPort. 2)Address-restricted-cone NAT 一旦一个内网地址 (iAddr:iPort)

高薪诚聘“P2P网络传输”技术经理

<中维世纪> 济南少有的不靠政商关系,不靠专属资源一步步成长起来的集团公司.面向行业终端用户.也大量生产民用消费类产品.也有互联网平台级产品.目前公司员工有千人以上,有五个事业部,两个子公司(均在深圳).公司是产品和技术导向型企业,拥有强大的研究型团队,其中有网络实验室.图像实验室.音视频实验室,现需要一名网络实验室P2P网络传输方向的技术经理,你的研究和实施将服务全球千万级的用户,当然挑战也很大:你能获得什么?做技术的我们都知道,钱不是事,重点自己的架构.自己的技术能用在千万级的用户身上才是