基于Java实现的元气骑士公网异地联机游戏设计

元气骑士公网异地联机

初探局域网联机过程

前言

元气骑士作为当下热门的单机游戏,很多人都在琢么怎么和好朋友即使不在一个局域网内也能一起玩耍。目前网上比较成熟的方案是在服务器上搭建 VPN,两个手机通过连接 VPN 来穿透内网,实现彼此的连通,再通过联机工具转发房间的广播信息,如此就能做到公网异地联机了,相关的教程 CSDN 上都有。

本学期正式学习了计算机网络、windows API 编程等课程,正想对这些课程进行一个实践总结,我打算不通过 VPN,而是通过自己编程实现元气骑士的公网异地联机。

不过要实现公网异地联机,需要做到转发数据报、穿透内网,本篇主要就是通过封包分析,搞明白这个游戏局域网联机过程,从而弄明白,要转发数据报应该监听哪个端口。

在这个过程中我用了两种方法监听游戏的封包,第一种是电脑开热点,Wireshark 监听虚拟 wifi 所对应的的网卡,这种方法能收到广播信息,但是收不到点对点的 UDP 数据报,随后根据所学内容对问题进行分析,找到了问题的原因。第二种方法是用 tcpdump 直接监听安卓设备,导出封包信息,用 Wireshark 进行分析。

对于当前我们研究的问题来讲,第二种方法才是恰当的方法,使用第二种方法既能对创建房间时的广播进行监听,又能对游戏进行时的 P2P 进行监听,但是我还是打算把第一种方法写上,一方面是对自己分析过程的记录,另一方面也希望能提醒自己遇到问题不要想当然,要有扎实的理论基础,从理论出发去分析问题。

读者若是不感兴趣可以直接跳过 监听创建房间阶段的封包数据监听游戏进行阶段的封包数据(错误方法) ,可直接从 监听游戏进行阶段的封包数据(正确方法) 开始阅读。

分析工具

Wireshark

tcpdump

逍遥安卓模拟器工作室版

获取封包

监听创建房间阶段的封包数据

搭建测试环境

第一步:配置电脑搭建一个热点,ssid 为 jack,密码为 jackjackjack

@echo off
netsh wlan set hostednetwork mode=allow ssid=jack key=jackjackjack
net start "Windows Firewall"
netsh wlan start hostednetwork
pause
12345

第二步:测试将测试所用的两部安卓手机连接到我们电脑搭建的热点上

第三步:用 Wireshark 监听虚拟 wif 所对应的网卡

监听并分析创建房间时的封包数据

使用手机连接电脑搭建的热点,随后进入元气骑士,创建一个房间,观察到有 UDP 封包如下:

192.168.137.110 是手机接入热点后,DHCP 给手机分配的内网 ip,当有玩家创建游戏房间时,玩家所在主机(192.168.137.110)会往 192.168.137.255 发送一封 UDP 封包,可见封包内容包括版本号、房间名和一些其他的配置信息。192.168.137.255 是一个特殊的地址,它最后 8 为全是 1,说明这是一个广播地址,向这个地址发送的数据会广播给同一网段下(192.168.137.0/24)下的其他主机。

监听游戏进行阶段的封包数据(错误方法)

将另一台主机连接热点,加入游戏,观察 Wireshark 监听的封包情况:

当另一台手机加入游戏后,Wireshark 并没有监听到什么可疑的封包,只是主机创建房间的消息一直在广播,之后我开始了游戏,房间内只有我的两部手机,手机 A 的角色移动也同步传输到了手机 B,但是 Wireshark 中并没有从 A 到 B 的数据包(无论 TCP 还是 UDP 都没有),这曾经让我一度陷入困惑。

随后我又观察到,在游戏的过程中,不断能接收到 ARP 封包,在广播寻问手机 A(192.168.137.110)、手机 B(192.168.137.92)的物理地址。
这说明,他们之中绝对有通信,只是 Wireshark 并未监听到。

问题分析过程:

出现了理论上应该存在、但是却监听不到的封包着实让我头疼。期初我的想法是,两个游戏角色的行为可以同步,说明他们之间绝对有数据往来,二者都连接到我电脑的热点上,按理说,我电脑虚拟 wifi 的网卡是二者数据往来的必经之路,只要在这里设下监听,什么数据都应该尽收眼底,所以我曾一度认为是软件配置和使用方法的问题,但我又能找到方法证明软件和配置都没有问题,那么问题出在哪里呢?我从中午思考这个问题一直到凌晨两点都未曾想通,要是这个问题解决不了,后面也无法继续了。

凌晨两点,我关了房间的灯,准备卧床睡觉,在床上我尽力回忆着计算机网络相关的理论知识,企图推出问题的原因,我想到了我的计网老师,我努力回想她上课讲过的每一句话……甘霖娘,我没去上几节课,上课也复习数学来着,仅剩的记忆也就是她最后一节课让我打扫实验室来着……

这事不怪老师,是我自己没去上课,虽然我没去上过课,但是书我还是看了,书中自有黄金屋,我又开始努力想,书上都写了什么?突然一句话映入我的脑海

我们知道适配器有过滤功能。但适配器从网络上每收到一个 MAC 帧就先用硬件检查 MAC 中的目的地址。如果是发往本站的帧则收下,然后再进行其他处理。否则就将此帧丢弃,不再进行其他处理。

——《计算机网络》第七版 P95 页 第四段

我清楚的记得这句话,上述引用是书中原文,这句话让我印象非常深刻,因为它有错别字!

但适配器从网络上每收到一个 MAC 帧就先用硬件检查 MAC 中的目的地址。

这句话的第一个字显然是错字,此处用”但”逻辑不通,应该“当”,作者打字的时候少打了个“g“。

目前的路由器兼有交换机的功能,加之监听到的数据包中有很多 ARP 协议,事件的全貌一下就理出来了:

  • 当手机 B 玩家加入游戏后,B 得到了手机 A 的 IP 地址
  • B 向 A 发送 IP 报,IP 报在数据链路层封装成帧,通过 ARP 协议得到了 IP 对应的 MAC 地址
  • 当 B 发送出去的 MAC 帧传输到我的电脑时,我电脑负责交换机功能的那一部分硬件,直接将 B 发送的帧转发给了 A,并没有向上传输
  • Wireshark 监听我电脑负责路由的那一部分,B 发送给 A 的 MAC 帧没到这一层,所以监听不到 AB 之间直接通信的数据报

混杂模式

按理说,开启混杂模式,应该就可以监听到 MAC 帧了,但是这里我并未成功,具体原因不明,先 mark 日后探究。

监听游戏进行阶段的封包数据(正确方法)

总之,进行到这一步,想获取游戏进行阶段的封包数据,只在网关监听是万万不行的了,必须想办法在安卓上直接对设备网卡进行监听。幸亏 Android 和 Linux 有着千丝万缕的联系,在安卓上可以直接执行 shell,这才为在安卓上拦截封包提供了可行性。想实现封包的拦截需要有:

  1. 一台支持 adb 的、已经 root 的手机
  2. tcpdump 工具

我使用的是逍遥安卓模拟器工作室版,在它根目录下有一个叫 memuc.exe 的工具,可以提供 ADB 支持,同时该模拟器默认就是已经 ROOT 的,很好满足了我的第一个需求。

按住 Shift,右键点击根目录文件件空白处,选择在此处打开命令行窗口,键入一下指令

memuc.exe adb -i 0 shell


可对编号为 0 的虚拟机执行 shell 命令

在安卓机装入 tcpdump 并开启监听的方法可以参考:

https://www.cnblogs.com/likwo/archive/2012/09/06/2673944.html

搭建实验环境

为实现模拟安卓在局域网联机的环境,又需要如下两点:

  1. 模拟器多开,至少 2 台
  2. 模拟器连于同一路由下

第一点逍遥模拟器的多开管理器已经做到了,第二点需要稍微配置一下。
找到设置-》网络设置,将网络模式设置为桥接,点击详细设置,IP 设置为 DHCP。
两台虚拟机都这样设置,注意:逍遥模拟器只有工作室版有网络模式设置这个功能,个人版没有,所以特别注明是工作室版。

执行监听

进入 shell 模式

memuc.exe adb -i 0 shell

在 shell 模式下,输入:

tcpdump -n -v -w "/sdcard/capture.pcap" src host 192.168.137.82 or src host 192.168.137.217 

这里的在 192.168.137.82 和 192.168.137.217 是我两台模拟器的 ip 地址,可以在设置-》关于手机-》状态信息中查看到。

模拟器上完成创建游戏、加入游戏、行走、使用技能、退出游戏等操作。

在命令行中按 ctrl+c 退出,输入

memuc.exe adb -i 0 pull /sdcard/capture.pcap capture.pcap 

将 capture.pcap 文件从模拟器中下载出来,随后用 Wireshark 加载该文件,进行分析。

封包分析

将刚才得到的封包数据,按时间进行排序,去掉无用的封包,从创建房间封包开始分析。
如此我们可以推断出,创建房间的主机 IP 为 192.168.137.217

随后收到的是来自 192.168.137.82:35198,发送给 192.168.137.217:7777 的封包
同时我们也看到, 192.168.137.217:7777 接收到封包后立刻给 192.168.137.82:35198 回了一个。

追踪这个过程的 UDP 流,我合计,这应该就是我想要的,是游戏交互过程用于同步的数据报。
有些数据报包含了武器、生命、能量等信息,应该是用于开局同步玩家信息用的,不过这数据报几乎就是明文……要是……岂不是……。

结语

如此,整个游戏局域网联机的具体实现流程就比较清晰了,元气骑士之间的通信用的全部都是 UDP,其通信流程大体如下:

  1. 主机 A 创建游戏房间,将房间信息在局域网内 23333 端口广播,同时监听自己的 7777 端口
  2. 主机 B 接受到了 A 的广播信息,当 B 想加入 A 的时候,B 就启用一个随机端口,向 A 地址的 7777 端口发送数据
  3. A 的 7777 端口监听器,发现了 B 的数据,记录 B 的发送端口,并立刻向 B 的发送端口发送响应数据
  4. B 给 A 发数据就发 7777 端口,A 给 B 发送数据就往 B 第一个数据报的发送端口发,如此实现两个游戏的通信

房间信息在局域网内 23333 端口广播,同时监听自己的 7777 端口
2.主机 B 接受到了 A 的广播信息,当 B 想加入 A 的时候,B 就启用一个随机端口,向 A 地址的 7777 端口发送数据
3.A 的 7777 端口监听器,发现了 B 的数据,记录 B 的发送端口,并立刻向 B 的发送端口发送响应数据
4.B 给 A 发数据就发 7777 端口,A 给 B 发送数据就往 B 第一个数据报的发送端口发,如此实现两个游戏的通信

要实现异地局域网联机,其实可以不用关心 A 给 B 发了什么,也不用关心 B 给 A 发了什么,只需要实现上述端口的监听与转发即可,现在已经迈出了第一步了,终于搞清楚转发哪些端口了,下一步就是得实现一个支持内网穿透的全双工转发机了,这涉及网络编程、多线程等知识。

两种方案可行性分析

前言

在上一篇中,我已经通过分析得知了元气骑士在局域网联机过程中是通过 UDP 进行交互的,只要我们能正确的转发这些 UDP 消息,就能实现异地公网的联机了。转发 UDP 消息通常有两种方案:一个是通过 UDP 穿透,建立起两部手机的对等连接,让两部手机直接向对方发送 UDP;另一个是搭建一个代理服务器,手机先将 UDP 消息发送给服务器,服务器将消息再转发给另外一部手机。通过本篇的分析可以得知, 对于元气骑士公网异地联机而言,方案一是不可行的 ,最后我只能选择方案二进行尝试。方案一虽然不可行,但是通过对方案一的可行性分析,学习了 NAT 与 UDP 穿透的相关知识,也算收获匪浅,故写下本篇博文记录一下。如果对 NAT 以及 UDP 的穿透不感兴趣可跳过本篇。

方案一:UDP 穿透,实现异地联机

为什么实现联机需要进行 UDP 穿透

由于互联网用户的急速增长,每个用户又拥有众多上网设备(笔记电脑、智能手机、iPad 等),从 20 世纪 80 年代起,一个很明显的问题是 IPv4 地址在以比设计时的预计更快的速度耗尽。

为了缓解这一问题,计算机网络领域提出了网络地址转换(Network Address Translation,缩写为 NAT)技术。目前大多数手机、电脑所分配到的 IP 其实都经过了多次 NAT 转换。

例如:

10.125.225.198 是我在设置-》关于手机-》状态信息中所得到的 ip 地址,是移动运营商分配给我的 ip 地址,而我去浏览器查询 ip 时却显示的是 223.104.175.159 ,这说明在运营商那,我的 ip 地址经过了至少一次的转换。

显然,10.125.225.198 是内网 ip,223.104.175.159 才是能上网的公网 ip,此刻我的手机并未连接路由器,使用的是移动流量。如果手机连接了路由器,则内网 ip 为 192.168.. 公网 ip 为路由器拨号所得的 ip,在网络请求时,路由器会将将我们的地址进行了一次转换,这种地址转换技术就是 NAT。

当内网设备向公网发起请求的时候,不同的内网 ip 会被路由器转化为同一个外网 ip(因为路由器拨号上网只得到了一个外网 ip),那么问题来了,如果外部网络想向内网设备请求会怎样呢?如果不考虑其他技术或协议的话,外部网络设备只知道一个外网 ip, 当外部网络设备向这个外网 ip 发送数据的时候,路由器会懵逼,因为它并不知道该把这个源自外部消息转发给内网哪个设备。

现实情况是,我们的路由器不会那么傻,大多数情况下(几乎百分百)我们能正确收到外部网络返回的响应信息(外部网络的响应信息,是由外部服务器向本地主机发送的信息,“响应”说明服务器给本地主机发送信息之前,本地主机必须先发起请求)。

NAT 有三种类型:静态 NAT(StaticNAT)、动态地址 NAT(PooledNAT)、网络地址端口转换 NAPT(Port-LevelNAT)

我们最常见的是网络地址端口转换 NAPT,NAPT 在处理内网设备向外网的请求时,会将地址和端口一并进行转换,假如 A 主机的发送端口是 80,可能转换后发送端口就变成了 6000,若 B 主机也发送了一个请求,那么 B 主机的发送端口可能就是 60001 了,总之在一个时间段内,对于不同主机对外网的请求来说,NAPT 一定会给他们分配不同的发送端口, 这样服务器返回响应信息的时候,就可以根据信息的目的端口,来准确的识别将信息转发给哪个一个设备了 。而穿透就是指,让路由器知道当两个不同内网的设备进行通信时,该如何正确的转发消息。

UDP 穿透流程

当下,我们面临情况是手机与手机点对点的通信,无论用手机流量还是用 wifi,总之,是内网设备对内网设备的 P2P 通信,这种情况的 UDP 穿透需要一个 具有外网 IP 的 服务器,其穿透流程大体如下:

  1. 主机 A 通过路由器向服务器发送 UDP 数据报
  2. 路由器 A 转换地址与端口
  3. 服务器记录下路由器 A 的地址和端口
  4. 主机 B 通过路由器向服务器发送 UDP 数据报
  5. 路由器 B 转换地址与端口
  6. 服务器记录下路由器 B 的地址和端口
  7. 服务器向 B 发送 A 的端口和地址
  8. 路由器 B 会把服务器的信息转发给主机 B
  9. 主机 B 向服务器返回的 A 的端口和地址发送请求
  10. 路由器 B 将消息转发给路由器 A(消息会被路由 A 抛弃)
  11. 服务器向 A 发送 B 的端口和地址
  12. 路由器 A 会把服务器的信息转发给主机 A
  13. 主机 A 向服务器返回的 B 的端口和地址发送请求
  14. 路由器 A 将消息转发给路由器 B(消息会被路由 B 转发)
  15. 主机 B 会接受到经过路由 B 转发的、主机 A 的消息
  16. 此后主机 A 和主机 B 就可以无需经过服务器了,可以直接通信了

要实现上述穿透流程,有一个必要条件,就是路由器 B 向服务器发送请求时转换的地址和端口,必须和路由器 B 向路由器 A 发送请求时的地址和端口一致,也就是说,对于同一内网 ip,在段时刻内即使请求目标是不同的外网 IP,转后的端口也相同。 但事实上,并不是所有的 NAPT 都能满足这个条件。

NAT 类型

NAPT 总共有 4 中类型,分别是:

NAPT 类型 别称 端口转换策略 将外网设备消息转发给内网设备 A 的条件
Full Cone NAT NAT1 所有来自同一 个内网设备的请求均被转换为同一端口 A 曾经用该端口向外部任意地址发送过消息
Restricted Cone NAT NAT2 所有来自同一 个内网设备的请求均被转换为同一端口 A 曾经用该端口向消息的发送地址发送过消息
Port Restricted Cone NAT NAT3 所有来自同一 个内网设备的请求均被转换为同一端口 A 曾经用该端口向消息的发送地址与发送端口发送过消息
Symmetric NAT NAT4 只有来自于同一个内网设备、针对同一目标的请求才被转换为同一端口。来自同一 个内网设备、针对不同目标的请求会被转换为不同端口 A 曾经用该端口向消息的发送地址与发送端口发送过消息

根据端口转换策略,可将 NAPT 分为锥型(Cone)和对称型(Symmetric),锥型包括 NAT1、NAT2、NAT3,锥型 NAPT 会将所有来自同一 个内网设备的请求均转换为同一端口 ,而对称型 NAP 也称 NAT4 是只有来自于同一个内网设备、针对同一目标的请求才被转换为同一端口。

如果想实现 UDP 穿透,至少两端之中有一端是锥型 NAT 才可以,所以在确定方案可行性之前,需要测定手机流量的 NAPT 类型。

NAPT 类型测试

测试 NAPT 类型有专门的协议:STUN ,简单的办法就是,去网上下载一个 NAT 类型测试工具,这个 CSDN 就有资源。因为考虑到元气骑士联机,所以用电脑连接手机热点,然后在电脑上用 NAT 测试工具试一试就能知道个大概,

但非常不幸,手机热点是对称型的。

通过查阅资料,基本确定 3G/4G 流量都是对称型的,所以这个方案凉凉。

结论

因为手机流量、手机热点都是对称型 NAT,所以无法实现 UDP 穿透,故该方案不可行。

方案二:建立代理服务器,转发 UDP 消息

幸好通过之前的分析,已经得知元气骑士在局域网联机的时候,只会发送 23333 端口的广播信息,和 7777 端口的单一目标信息,但是要是公网联机,需要三个转发机,分别是:

  • 主机 A 和主机 B:用于将手机的消息转发给服务器,将服务器的消息转发给手机
  • 服务器:用于将主机 A 的消息转发给主机 B,将主机 B 的消息转发给主机 A

所以代理服务器的工作流程大致如下:

步骤 操作
1 主机 A 与服务建立连接
2 主机 B 与服务器建立连接
3 手机 A 创建游戏房间,发送到 23333 端口的广播消息
4 主机 A 监听 23333 端口,发送端口不变,将接受的消息发往服务器的 34444 端口,并记录手机 A 的内网地址
5 服务器监听 34444 端口,发送端口不变,将接受的消息发往主机 B 的 34444 端口
6 主机 B 监听 34444 端口,发送端口不变,将接受的消息转发为发送到 23333 端口的广播
7 手机 B 监听 23333 端口,发送端口不变,接受到 23333 端口的消息后,向主机 B 的 7777 端口发送加入房间的消息
8 主机 B 监听 7777 端口,同时开始监听消息的发送端口,并将接受的消息发送端口不变的发往服务器的 8888 端口,并记录手机 B 的内网地址
9 服务器监听 8888 端口,同时开始监听消息的发送端口,并将接受的消息发送端口不变的发往主机 A 的 8888 端口
10 主机 A 监听 8888 端口,同时开始监听消息的发送端口,并将接受的消息发送端口不变的发往手机 A 的 7777 端口
11 手机 A 监听 7777 端口,手机 A 收到消息立即向消息来源返回消息
12 主机 A 监听之前的发送端口,将接受的消息不改变目的端口和发送端口的转发给服务器
13 服务器监听之前的发送端口,将接受的消息不改变目的端口和发送端口的转发给主机 B
14 主机 B 监听之前的发送端口,将接受的消息不改变目的端口和发送端口的转发给手机 B

整理一下,给出每个代理服务器的任务:

转发机 监听端口
主机 A 23333、8888、8888 端口消息的发送端口 p
主机 B 34444、7777、7777 端口消息的发送端口 p
服务器 34444、8888、8888 端口消息的发送端口 p
转发机 转发逻辑
主机 A 23333-> 服务器:34444;8888-> 手机 A:7777;p-> 服务器:p;
主机 B 34444-> 内网广播:23333;7777-> 服务器:8888;p-> 手机 B:p;
服务器 34444-> 主机 B:34444;8888-> 主机 A:8888;p-> 主机 B:p;

结语

方案一虽然不可行,但是通过对方案一的可行性分析,学习了 NAT 与 UDP 穿透的相关知识,也算收获匪浅。方案二虽然可行,但是因为要持续占有服务器资源,所以如果将此方案列为第二方案。方案二是假设 A 和 B 两个玩家的联机情况,我的想法是,先完成双人联机的情况,再此基础上再逐步扩展。转发机我打算用 Java 实现,因为 Java 网络与多线程的编程封装了很多实用的 API,且 Java 具有良好的移植性

完善转发机的转发规则

前言

在上一篇中,我通过表格对转发机的监听端口和转发逻辑进行了简短的描述。

转发机 监听端口
主机 A 23333、8888、8888 端口消息的发送端口 p
主机 B 34444、7777、7777 端口消息的发送端口 p
服务器 34444、8888、8888 端口消息的发送端口 p
转发机 转发逻辑
主机 A 23333-> 服务器:34444;8888-> 手机 A:7777;p-> 服务器:p;
主机 B 34444-> 内网广播:23333;7777-> 服务器:8888;p-> 手机 B:p;
服务器 34444-> 主机 B:34444;8888-> 主机 A:8888;p-> 主机 B:p;

但是因为路由器和 3G 或者 4G 网络存在对称型 NAPT 的问题,所以实际的转发逻辑并非像表格总那么简单。同时,服务器要实现转发,需要知道接受谁的信息转发给谁,也就是说主机 A 和 B 必须主动与服务器建立连接,服务器存储 A 和 B 相关的信息才能正确的执行转发。本篇将在考虑上述问题的基础上进行对问题进行讨论。

考虑对称型 NAPT、进一步确定转发规则

将主机 A、主机 B、服务器的转发规则按时间顺序进行排列得到如下表格:

时序 源地址 监听端口 源端口 目的地址 目的端口
1 主机 A 23333 服务器 34444
2 服务器 34444 主机 B 34444
3 主机 B 34444 局域网广播 23333
4 主机 B 7777 服务器 8888
5 服务器 8888 主机 A 8888
6 主机 A 8888 手机 A 7777
7 主机 A p 服务器 p
8 服务器 p 主机 B p
9 主机 B p 手机 B p

为着重关注主机 A、B 与服务器的交互问题,将上述规则按交互对象进行分组,每组规则按时序升序排列。即 1、5、6、7 作为主机 A 与服务器交互的一组,2、4、8、9 作为主机 B 与服务器交互的一组,完成后表格如下:

时序 源地址 监听端口 源端口 目的地址 目的端口
1 主机 A 23333 服务器 34444
5 服务器 8888 主机 A 8888
6 主机 A 8888 手机 A 7777
7 主机 A p 服务器 p
时序 源地址 监听端口 源端口 目的地址 目的端口
2 服务器 34444 主机 B 34444
3 主机 B 34444 局域网广播 23333
4 主机 B 7777 服务器 8888
8 服务器 p 主机 B p
9 主机 B p 手机 B p

先考虑未知端口数 p 的取值,通过第一篇的分析,已知元气骑士局域网联机具有这样一个规律:

Address:Port 向 7777 发送请求,元气骑士会用 7777 端口向 Address:Port 回一个响应

所以可以通过设置规则 6 的源端口,来控制 p 的数值,这里设置规则 6 的源端口为 9898,那么规则 7 的监听端口也应该是 9898,向服务器转发的目的端口、源端口,不妨设为 9999。

随后可以推断,规则 8 的监听端口与目的端口、规则 9 的监听端口,也应该设为 9999。

在第一篇的分析中还提及元气骑士局域网联机的另一个规律:

Address:Port 向 7777 发送请求,Address 的元气骑士就会监听 Port 端口,期望得到来自 7777 端口的响应.

所以规则 9 的源端口应设为 7777

最终得到下面两个表格:

时序 源地址 监听端口 源端口 目的地址 目的端口
1 主机 A 23333 服务器 34444
5 服务器 8888 主机 A 8888
6 主机 A 8888 9898 手机 A 7777
7 主机 A 9898 服务器 9999
时序 源地址 监听端口 源端口 目的地址 目的端口
2 服务器 34444 主机 B 34444
3 主机 B 34444 局域网广播 23333
4 主机 B 7777 服务器 8888
8 服务器 9999 主机 B 9999
9 主机 B 9999 7777 手机 B p

随后开始针对 NAPT 考虑源端口的设定,通过在第二篇中对 NAPT 的学习,我们知道下面一个结论:

Address1:Port1 向 Address2:Port2 发送过请求后,Address1 一定能收到 Address2:Port2 发往 Address1:Port1 的响应。

对于规则 1 与规则 5、规则 4 与规则 8 这两组规则,我们都可以看成是由主机向服务器发送了请求,服务器给予了响应,所以只需要 将请求报文的源端口设置为响应报文的目的端口,将响应报文的源端口设置为请求报文的目的端口 即可,即令 1 的源端口为 8888,5 的源端口为 34444;4 的源端口为 9999,8 的源端口为 8888。

规则 7、规则 2、规则 3 的源端口可以随便选取,这里就取与它们各自监听端口一样的值即可。

最终得到表格如下:

时序 源地址 监听端口 源端口 目的地址 目的端口
1 主机 A 23333 8888 服务器 34444
5 服务器 8888 34444 主机 A 8888
6 主机 A 8888 9898 手机 A 7777
7 主机 A 9898 9999 服务器 9999
时序 源地址 监听端口 源端口 目的地址 目的端口
2 服务器 34444 34444 主机 B 34444
3 主机 B 34444 34444 局域网广播 23333
4 主机 B 7777 9999 服务器 8888
8 服务器 9999 8888 主机 B 9999
9 主机 B 9999 7777 手机 B p

结论

为方便 Java 面向对象的编程设计,将上述规则,按源地址进行分类:

主机 A

监听端口 源端口 目的地址 目的端口
23333 8888 服务器 34444
8888 9898 手机 A 7777
9898 9999 服务器 9999

注:第二行源端口、第三行监听端口应该一致,这里设置为 9898,其实可以设为成任何值,一致就行,不建议设置已经出现过的监听端口,不方便进行本地测试。

主机 B

监听端口 源端口 目的地址 目的端口
34444 34444 广播 23333
7777 9999 服务器 8888
9999 7777 手机 B p

注:p 等于 7777 端口所收到的报文的源端口,应在接受到 7777 端口的消息时动态设定。

服务器

监听端口 源端口 目的地址 目的端口
34444 34444 主机 B “ 34444”
8888 34444 主机 A “8888”
9999 8888 主机 B “9999”

注:服务器需要向主机 B 发送目的端口为 34444 的请求,所以建立连接时,主机 B 需要向服务器发送以 34444 为源端口,34444 为目的端口的请求报文。服务器的目的端口都加上了引号,因为 NAT 的原因,服务器需要把数据发到 nat 转换后的端口。

结语

明确了哪个转发机该用什么端口转发向哪里之后,剩下的工作就简单多了,对于转发机的实现,仁者见仁智者见智,原理上不难理解,主要就是需要注意主机 A 与主机 B 需要先向服务器发送请求才能接收到服务器发过来的数据。

猜你喜欢

转载自blog.csdn.net/newlw/article/details/131274382