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

一直想写一个跨局域网聊天和文件传输,以及视频聊天的软件,这两天刚好闲着没啥事就把代码写完了,代码已经上传至github:https://github.com/vinllen/chat

其实之前想法P2P模式,P2P的话必须穿透NAT,现在的NAT有4种模式:

  1. 完全圆锥型NAT
  2. 受限圆锥型NAT
  3. 端口受限圆锥型NAT
  4. 对称NAT(双向NAT)

维基百科给出的定义如下:

  • 1.Full cone NAT,亦即著名的一对一(one-to-one)NAT

一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。任意外部主机都能通过给eAddr:port2发包到达iAddr:port1

  • 2.Address-Restricted cone NAT

一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。任意外部主机(hostAddr:any)都能通过给eAddr:port2发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:any. "any"也就是说端口不受限制

  • 3.Port-Restricted cone NAT

类似受限制锥形NAT(Restricted cone NAT),但是还有端口限制。

一旦一个内部地址(iAddr:port1)映射到外部地址(eAddr:port2),所有发自iAddr:port1的包都经由eAddr:port2向外发送。一个外部主机(hostAddr:port3)能够发包到达iAddr:port1的前提是:iAddr:port1之前发送过包到hostAddr:port3.

  • 4.Symmetric NAT(对称NAT)

每一个来自相同内部IP与端口,到一个特定目的地地址和端口的请求,都映射到一个独特的外部IP地址和端口。同一内部IP与端口发到不同的目的地和端口的信息包,都使用不同的映射

只有曾经收到过内部主机封包的外部主机,才能够把封包发回

对于第1种特别简单,因为端口存在映射,只要把包发网出口路由的端口即可,路由会帮你转发

对于第2,3种情况,可以采用如下办法(内容来自该博客:点击打开链接):

假设网络模型如下:

限制性锥NAT 和端口限制性锥NAT (简称限制性NAT ),穿透限制性锥NAT 会丢弃它未知的源地址发向内部主机的数据包。所以如果现在ClientA-1 直接发送UDP 数据包到ClientB-1 ,那么数据包将会被NAT-B 无情的丢弃。所以采用下面的方法来建立ClientA-1 和ClientB-1 之间的通信。

  • 1 .ClientA-1 (202.103.142.29:5000 )发送数据包给Server ,请求和ClientB-1 (221.10.145.84:6000 )通信。
  • 2. Server 将ClientA-1 的地址和端口(202.103.142.29:5000 )发送给ClientB-1 ,告诉ClientB-1 ,ClientA-1 想和它通信。
  • 3. ClientB-1 向ClientA-1 (202.103.142.29:5000 )发送UDP 数据包,当然这个包在到达NAT-A 的时候,还是会被丢弃,这并不是关键的,因为发送这个UDP 包只是为了让NAT-B 记住这次通信的目的地址:端口号,当下次以这个地址和端口为源的数据到达的时候就不会被NAT-B 丢弃,这样就在NAT-B 上打了一个从ClientB-1 到ClientA-1 的孔。
  • 4. 为了让ClientA-1 知道什么时候才可以向ClientB-1 发送数据,所以ClientB-1 在向ClientA-1 (202.103.142.29:5000 )打孔之后还要向Server 发送一个消息,告诉Server 它已经准备好了。
  • 5. Server 发送一个消息给ClientA-1 ,内容为:ClientB-1 已经准备好了,你可以向ClientB-1 发送消息了。
  • 6. ClientA-1 向ClientB-1 发送UDP 数据包。这个数据包不会被NAT-B 丢弃,以后ClientB-1 向ClientA-1 发送的数据包也不会被ClientA-1 丢弃,因为NAT-A 已经知道是ClientA-1 首先发起的通信。至此,ClientA-1 和ClientB-1 就可以进行通信了。

对于第4种情况,oh no,太难了,需要猜测端口号(博客地址:点击打开链接):

上面讨论的都是怎样穿透锥(Cone )NAT ,对称NAT 和锥NAT 很不一样。对于对称NAT ,当一个私网内主机和外部多个不同主机通信时,对称NAT并不会像锥(Cone ,全锥,限制性锥,端口限制性锥)NAT那样分配同一个端口。而是会新建立一个Session ,重新分配一个端口。参考上面穿透限制性锥NAT 的过程,在步骤3 时:ClientB-1 (221.10.145.84:
?)向ClientA-1 打孔的时候,对称NAT 将给ClientB-1 重新分配一个端口号,而这个端口号对于Server 、ClientB-1 、ClientA-1 来说都是未知的。同样, ClientA-1 根本不会收到这个消息,同时在步骤4 ,ClientB-1 发送给Server 的通知消息中,ClientB-1 的socket 依旧是(221.10.145.84:6000 )。而且,在步骤6 时:ClientA-1 向它所知道但错误的ClientB-1 发送数据包时,NAT-1 也会重新给ClientA-1
分配端口号。所以,穿透对称NAT 的机会很小。下面是两种有可能穿透对称NAT 的策略。

1.同时开放TCP ( Simultaneous TCP open )策略

如果一个 对称 NAT 接收到一个来自 本地 私有网 络 外面的 TCP SYN 包, 这 个包想 发 起一个 “ 引入” 的 TCP 连 接,一般来 说 , NAT 会拒 绝这 个 连 接 请 求并扔掉 这 个 SYN 包,或者回送一个TCP RST (connection reset ,重建 连 接)包 给请 求方。但是,有一 种 情况 却会接受这个“引入”连接。

RFC 规定:对于对称NAT , 当 这 个接收到的 SYN 包中的源IP 地址 : 端口、目 标 IP 地址 : 端口都与NAT 登 记 的一个已 经 激活的 TCP 会 话 中的地址信息相符 时 , NAT 将会放行 这 个 SYN 包。 需要 特 别 指出 的是:怎样才是一个已经激活的TCP 连接?除了真正已经建立完成的TCP 连接外,RFC 规范指出: 如果 NAT 恰好看到一个 刚刚发 送出去的一个 SYN 包和 随之 接收到的SYN 包中的地址 :端口 信息相符合的 话 ,那 么 NAT 将会 认为这
个 TCP 连 接已 经 被激活,并将允 许这 个方向的 SYN 包 进 入 NAT 内部。 同时开放TCP 策略就是利用这个时机来建立连接的。

如果 Client A -1 和 Client B -1 能 够 彼此正确的 预 知 对 方的 NAT 将会 给 下一个 TCP 连 接分配的公网 TCP 端口,并且两个客 户 端能 够 同 时 地 发 起一 个面向对方的 “ 外出 ” 的 TCP 连 接 请求 ,并在 对 方的 SYN 包到达之前,自己 刚发 送出去的 SYN 包都能 顺 利的穿 过 自己的 NAT 的 话 ,一条端 对 端的 TCP 连 接就 能 成功地建立了 。

2.UDP 端口猜测策略

同时开放TCP 策略非常依赖于猜测对方的下一个端口,而且强烈依赖于发送连接请求的时机,而且还有网络的不确定性,所以能够建立的机会很小,即使Server 充当同步时钟的角色。下面是一种通过UDP 穿透的方法,由于UDP 不需要建立连接,所以也就不需要考虑“同时开放”的问题。

为了介绍ClientB-1 的诡计,先介绍一下STUN 协议。STUN (Simple Traversal of UDP Through NATs )协议是一个轻量级协议,用来探测被NAT 映射后的地址:端口。STUN 采用C/S 结构,需要探测自己被NAT 转换后的地址:端口的Client 向Server 发送请求,Server 返回Client 转换后的地址:端口。

参考4.2 节中穿透NAT 的步骤2 ,当ClientB-1 收到Server 发送给它的消息后,ClientB-1 即打开3 个socket 。socket-0 向STUN Server 发送请求,收到回复后,假设得知它被转换后的地址:端口( 221.10.145.84:600 5 ),socket-1 向ClientA-1 发送一个UDP 包,socket-2 再次向另一个STUN Server 发送请求,假设得到它被转换后的地址:端口( 221.10.145.84:60 20 )。通常,对称NAT 分配端口有两种策略,一种是按顺序增加,一种是随机分配。如果这里对称NAT
使用顺序增加策略,那么,ClientB-1 将两次收到的地址:端口发送给Server 后,Server 就可以通知ClientA-1 在这个端口范围内猜测刚才ClientB-1 发送给它的socket-1 中被NAT 映射后的地址:端口,ClientA-1 很有可能在孔有效期内成功猜测到端口号,从而和ClientB-1 成功通信。

/************************************华丽分割线**********************************************************/

以上内容大部分参考维基百科和chengweiv5的博客,我自己也写了代码,尝试用第二种方式去穿透我们学校所在的NAT,结果悲剧了,找了好几天的问题也没找出来到底是代码的原因呢还是NAT限制为第4种的原因呢。

But项目不能这么搁浅了。我采用了另外一条曲线救国的路线,就跟现在QQ模式一样,用中转服务器进行消息转发实现。

1.首先搞了一台具有公网ip的服务器

2.配置好环境后(比如iptables等),把服务器代码放置服务器,运行

3.客户端连接后就可以发送文件和聊天了

That‘s all,就这么简单。

唯一需要说明的就是发送文件时我调用的是sendfile的系统调用,接收文件调用了Pat Patterson的包裹函数:利用splice接收文件,理论上这两个函数是直接在内核态进行文件传递,而不用在内核态和普通态进行数据的拷贝,减少时间。我测试了一下,对几M大小的文件都没问题,不过貌似文件大了貌似传不动了,目测sendfile有大小限制。。。这个就之后再改改了。

最后一点,本来还要实现视频传输的,我的想法就是用opencv调用本地摄像头,然后把图片按每秒固定帧数截下来,按文件方法传输,再在对端对图片进行拼接成视频。后来感觉有点麻烦,最后上网一查,果然我做的部分就是重造轮子的活,现在直接都有现成的文件传输协议。这部分也等以后有时间再去完善吧。

Finally, 如果大家有谁有想法,可以和我交流交流,一起学习进步

时间: 2025-01-31 06:57:45

Linux socket跨局域网聊天和文件传输的相关文章

java UDP聊天与文件传输

package rgy.com.UDP3; import java.awt.Font; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; import java.io.Fil

android asmack 注册 登陆 聊天 多人聊天室 文件传输

XMPP协议简介 XMPP协议(Extensible Messaging and PresenceProtocol,可扩展消息处理现场协议)是一种基于XML的协议,目的是为了解决及时通信标准而提出来的,最早是在Jabber上实现的.它继承了在XML环境中灵活的发展性.因此,基于XMPP的应用具有超强的可扩展性.并且XML很易穿过防火墙,所以用XMPP构建的应用不易受到防火墙的阻碍.利用XMPP作为通用的传输机制,不同组织内的不同应用都可以进行有效的通信. 这篇文章有基本的介绍,http://bl

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

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

linux socket c/s上传文件

这是上传文件的一个示例,可以参照自行修改成下载或者其它功能. 在上传时,需要先将文件名传到服务器端,这是采用一个结构体,包含文件名及文件名长度(可以用于校验),防止文件名乱码. client #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 1492 #de

Linux(RadHat)基础学习—文件传输

1.scp传输 scp:远程复制传输,速度很慢 目录上传: scp -r 目录 用户名@主机ip:绝对路径 文件上传 scp 文件 用户名@主机ip:绝对路径 远程下载: scp -r 用户@主机ip:绝对路径 下载目录 2.rsync传输 rsync:镜像传输传输,远程同步,速度快,默认忽略文件属性.连接文件.设备文件,但可以加参数使其把忽略的文件也进行同步传输 下载: rsync [选项] 远程主机用户@ip:绝对路径 下载位置 上传: rsync [选项] 本地文件位置 远程主机用户@ip

进阶版socket套接字ftp文件传输可并发

server import socketserver import json import struct class Mytcp(socketserver.BaseRequestHandler): def handle(self): try: while True: data_path = self.request.recv(1024) data = str(data_path,encoding='utf8') print(data_path) with open(data,'rb') as f

Linux下nc命来实现文件传输

发送端:cat test.txt | nc -l -p 6666或者nc -l  -p 6666 < test.txt                    有些版本不要在 -p[监听6666端口,等待连接](设发送端IP为10.20.133.152)接收端:nc 10.20.133.152 6666 > test1.txt如上面的操作,即可将文件test.txt从发送端传送到接收端,保存为test1.txt

实现linux和windows文件传输

其实这个题目有点大,这里介绍的只是linux和windows文件传输中的一种,但是这种方法却非常实用,那就是:ZModem协议具体是linux命令是:rz和sz但是其实它们是两个非常方便的工具. rz,sz是Linux/Unix同Windows进行ZModem文件传输的命令行工具,windows端需要支持ZModem的telnet/ssh客户端,SecureCRT就可以用SecureCRT登陆到Unix/Linux主机(telnet或ssh均可) 1 . rz,接收文件,SecureCRT就会弹

如何在Linux中使用rz/sz工具进行文件传输

在Linux中,使用rz/sz工具能够进行Linux和windows之间的文件传输,那么要如何使用rz/sz工具工具呢?下面小编就给大家介绍下Linux下如何使用rz/sz工具进行文件传输,一起来学习下吧. 一般来说,linux服务器大多是通过ssh客户端来进行远程的登陆和管理的,使用ssh登陆linux主机以后,如何能够快速的和本地机器进行文件的交互呢,也就是上传和下载文件到服务器和本地: 与ssh有关的两个命令可以提供很方便的操作: sz:将选定的文件发送(send)到本地机器 rz:运行该