本科课程期间最后的大型 Project,在这里进行简单的分析和拆分以便于后续的设计以及开发。
作为本科课程期间最后的大型 Project,为了方便后续的设计以及开发,这里将会进行简单的分析和拆分,还有顺便矫正一下我的现代汉语文法。
项目描述:
本项目以 TransPerth 公共交通数据作为数据源,每一个车站视为结点,所有的公交站点组成相连的图。在软件中,每个车站都视作处于运行状态的服务器软件的一个实例,不同的车站服务器进程运行的相同的软件,但有着不同的数据和网络连接。
车站服务器使用简单的 HTML 提供一个操作界面,用户可以通过网页搜索两个车站间指定时刻的交通路线。网页将会提交请求到出发站点的服务器询问到目的地的路线。
每个服务器只存储从本站出发的的火车及巴士班次信息,其中包含每个线路的目的地,处罚及到达时间。所以如果可以从出发点直达目的地无需中转,车站服务器就可以直接向用户反馈信息。但如果需要中转,服务器就需要向其他服务器获取下一段的行程信息。
当服务器成功计算出完整路线,将会通过网页反馈给用户需要搭乘的线路,出发时间,中转时间,行程时间以及预计到达时间。在项目完成之际,服务器应当返回耗时最短的路线,当然在开发项目的过程中,应当尝试首先把所有可用路线都显示出来。
为降低难度,我们可以忽略曜日,假定一周中的每一天都是同样的时间表。数据源会提供一共 105 个站点的信息,项目可以在个人电脑上运行,但为了更好的调试管理,应当先实现几个服务器的通信,确认无误后再增加数量。
网络实现:
1. 由于所有的车站服务器都是同一电脑上的不同进程,所有的网络通信都是在本地运行的,所以我们使用 localhost 或 127.0.0.1 的 IP 地址进行通信,在运行程序的时候就不需要在单独指定服务器的地址了。但是不同的服务器需要用不同的端口进行通信以便区分,写好的程序可以以下列的形式启动
shell> ./station Warwick-Stn 2401 2408 2560 2566 …. &
shell> ./station.py Greenwood-Stn 2402 2560 2567 2408 …. &
其中第一个程序(C 语言)将会作为 Warwick-Stn 的服务器,使用 2401 端口从网页端接收数据,用 2408 端口从其他车站接受数据,与其相邻的两个车站分别用 2560 和 2566 进行车站间的通讯。第二个程序(使用 Python)就与之相同,并且和 上一个车站相邻。 程序启动后就之后在后台默默运行,直到用户终止。
2. 当车站服务器从网页端收到请求时,所有的数据会使用 HTTP 协议通过双向的 TCP/IP 通信进行传输,收到请求且完成回应后,服务器就应当结束通讯。不同的车站见通讯时应该使用更方便的 UDP。
3. 所有的车站都只存储本站的班次表,并且记录在文本文件当中。班次表会在运行时随时变更,比如巴士坏掉了,就无法继续这趟运营。当服务器向用户反馈信息时,必须反映最新的情况。
项目要求:
1. 项目应该使用两种语言开发,写出两个不同的服务器程序,但程序的功能应当完全相同。
2. 在使用所选语言进行开发时,应当只用语言提供的最基础核心网络库,不可使用第三方框架,如 Python 的 http.server 或 C++ 的 Boost 库。本次项目考察的学生对系统调用以及编程语言标准库的使用,如果简单的将第三方提供的功能拼装就达不到学习效果。
3. 项目将会在 MacOS 或 Linux 下评估,在提交时需要标注。
更多项目细节可能会在未来推出,学生很快就可以获取将 TransPerth 数据信息转换为纯文本的脚本,以及同时启动所有服务器的最终指令。
以上即为 3002 Computer Network Project 的所有信息,所有的改动将会在下方提供,好运。
Tommy 2020 年 4 月 25 日早朝 Perth
项目起始:
本项目具有两个重要的网络功能,一旦完成网络部分,便可以着手于商业逻辑的设计与开发,网络功能要求如下:
- 每个车站服务器都支持对来自浏览器的请求作出响应,即使用 TCP 协议的接收及回复。当服务器收到请求后,两个进程间需要建立稳定的连接,直到任何一端主动中断。(当然也有其中一方崩溃的情况,连接中断但另一应答方并不知晓具体原因)
浏览器和车站服务器的连线将会通过建立在 TCP/IP 协议上的 HTML 和 HTTP 进行数据传输,HTML 数据将会封装在 HTTP 协议传输的内容中,再作为 TCP 协议的包封装到 IP 协议包里。通常网络层数据包(IP)将会进一步由数据链路层封装为以太网帧,但由于我们所有的服务器都在本机,所以这些 IP 包不会进入网络,只会在本地计算机的内存中等待交换。
通过该 TCP/IP 传输的网络流量是双向且可靠的。一端进行请求,另一端读取请求,并在计算后发送回应,原始请求端再读取回应。双方可以同时读写数据,在传输过程中不会相互干扰。 - 每个车站服务器同时使用 UDP/IP 数据报协议与其他服务器进行沟通,不同于建立双方之间稳定连接的 TCP 方式,该协议的两方将直接发送或收取包含来源和目的地的数据包,每条通讯都会包含地址信息,且只会被指定的目的地接收。所以同样的信息可以只对头部的目的地地址进行变更就可以向多个不同的目标发送。
浏览器与车站服务器连接
首先我们来看浏览器(客户端)如何通过 HTTP 向服务器发送请求。我们需要知道/指定进行通信所使用的端口。端口用于区分不同应用软件的流量,操作系统内核使用收到的数据包的端口来检测是否有正在运行的进程等待接收通讯,如果有的话,这组数据包将会交给此进程。所以在程序运行前应当先找到一个未被其他程序占用的可用端口。
在 Linux/macOS 平台下可以执行 portsinuse.sh 查看正在使用的端口。
$ chmod +x portsinuse.sh
$ ./portsinuse.sh
查看过后,选择一个没有列出且大于 1023 的端口,如 5555,然后执行:
$ nc -l 5555
就会创建一个监听 5555 端口新服务器进程,等待使用本端口的 TCP/IP 网络数据(默认)。之后我们准备开始使用浏览器充当 TCP/IP 协议的客户端。按照环境设定,浏览器和车站服务器运行在同一台电脑中,所以可以通过 localhost 主机名或一般为 127.0.0.1 的本地 IP 进行访问。
打开浏览器,输入 http://localhost:5555 地址。浏览器当然不会立刻成功打开某个页面,但在刚刚执行进程所用的终端中可以立刻看到浏览器向进程发送了 HTTP 请求。 开头形如 GET/ HTTP/1.1 等等,这就是一个简单的 HTTP 头。此刻浏览器仍在等待进程的回应,所以我们可以复制以下代码到命令行中。
HTTP/1.1 200 OK
Content-Type: text/html
Connection: Closed
<html>
<body>
<h1>Hello from nc!</h1>
</body>
</html>
然后同时按 control-C 或 D 结束进程与浏览器之间的连线。我们所回复的 HTTP 头代表着请求有效且可以被理解(即返回状态编号 200),下面写的五行 HTML 文档就会被浏览器渲染成为网页。
现在让我们重新进行试验,但使用 https://localhost:5555/?to=Perth_Stn 作为地址,就可以在终端中看到不一样的请求。如果我们执行的是已经写好的服务器程序,现在就取得了来自浏览器的请求可以开始做下一步行动了。
这部分涉及通过 TCP/IP 连接的 HTTP 请求,以及通过同样的连接回复带有 HTML 代码的 HTTP 回应。即使还没有真正的开始写代码,就一程成功的做到了进程和浏览器的交互。接下来就可以开始用所选的语言进行编程,用自己的服务器程序响应浏览器请求。Project 页面有针对不同语言的编程指导,可以点击本链接查看。
服务器网络的定义与执行
下图是一个四个车站的的简单示范网络:
- 每个车站有不同的名称
- 每个车站都通过巴士/铁路连接相邻车站
- 每个车站都有一个地址给浏览器访问(TCP 连接)且 车站的地址都是使用 HTTP 的非加密连接
- 每个车站还有一个用于接收 UDP 数据报的接口
- 每个车站只知道通过公交线路直接连接的车站的 UDP 端口(邻居)
- 由于我们的所有服务器都运行在本地,所以地址均为 localhost,同理不可有端口重复使用
上面的网络中由四个不同的进程运行,可以通过如下所示批量命令直接运行所有的程序。每个进程都需要几个启动参数,如上文“网络实现”中所描述,分别为车站名,TCP 端口,UDP 端口,以及相邻车站的 UDP 端口。运行指令后,所有的进程将会在后台运行。
shell> ./station North_Terminus 2210 2608 2606 &
shell> ./station East_Station 2230 2606 2608 2602 2605 &
shell> ./station.py West_Station 2220 2602 2605 2606 &
shell> ./station.py South_Busport 2240 2605 2606 2602 &
启动之后,每个进程首先初始化获取到的 TCP 和 UDP 端口,之后从指定的文本文档中读取时间表信息(假定所有的班次信息都是正确的,包括时间格式正确,出发时间早于到达,目的地真实存在等等)作为示范,名为 tt-North_Terminu 的文件包含下列信息:
North_Terminus,-31.8448,115.7963
07:15,12,Stop1,07:54,East_Station
08:15,12,Stop1,08:46,East_Station
....
14:20,12,Stop1,14:50,East_Station
其中第一行为站名(同时也是文件名)以及地理信息(瞎编的),第二行及之后各行是出发的车辆信息。第二行的意思是:7点15分,12路巴士从1号站台出发,下一站是 East_Station,7点45分到达。这个文件不包含任何网络信息(如端口,协议等等),本站点的时间信息也不包含任何其他站点的信息。
另一个车站的文件 tt-East_Station 为完全相同的格式,但从途中可以看出该站有更多的线路,所以时间表也会更为密集。
East_Station,-31.9442,115.8771
05:15,Line1,PlatformB,06:10,West_Station
05:22,180,StopA,05:51,South_Busport
06:15,Line1,PlatformB,07:18,West_Station
06:16,13,StopC,06:51,North_Terminus
....
Tommy 更新于 2020年5月8日深夜 于 Waterford, WA
简化交通网络的建设与执行
假定我们有一个文件存放车站网络的位置矩阵,名为 adjacency,内容如下:
North_Terminus East_Station
East_Station North_Terminus South_Busport West_Station
West_Station East_Station South_Busport
South_Busport West_Station East_Station
每行的第一个词代表该行描述的车站,后面紧跟的是他的相邻车站。在上面的例子中,所有的线路都是双向连接的。然后我们执行 assignports.sh,将会自动生成另一个批处理指令为所有的车站进程分配没有使用的 TCP 和 UDP 端口。
$ ./assignports.sh adjacency startstations.sh
执行上述指令,将会生成 startstations.sh 文件且内容如下:
./station North_Terminus 4001 4002 4004 &
./station East_Station 4003 4004 4002 4008 4006 &
./station West_Station 4005 4006 4004 4008 &
./station South_Busport 4007 4008 4006 4004 &
现在只需要执行该文件,所有的车站服务器都会被启动。我们可以使用提供的另一个 makeform.sh 文件根据 startstations.sh 找出所有服务器的 TCP 端口,并生成用于客户端操作的 HTML 网页。
$ ./makeform.sh startstations.sh myform.html
车站服务器间的数据通信
不同的车站服务器间使用 UDP/IP 数据报进行数据传输,使用 UDP 的通讯不需要建立或维系两端间的连接,只用在有需要的时候传送信息即可。每个车站只知道与之相邻车站的端口,不管是请求还是回应都使用数据报进行传输。
每个数据报的内容都应该使用清晰并且可以被接受方轻易理解的格式,并不需要与 HTTP 请求或是 HTML 格式相同,你可以自行设计你的协议。
值得注意的是,Java 对象可以在两个 Java 程序直接直接传输,但用其他语言写的程序就完全无法分析解读。
使用 Transperth GTFS 数据集
Perth PTA 提供予公众访问的时间表予班次信息,可以在其官方网站中下载(约 90MB)。这些数据采用内相关的 Google Transit Feed Specification (GTFS) 格式,这种格式同时被世界上徐彤不同的公共交通提供商采用。
最后,我们使用 buildtimetables.sh 处理从 Transperth 下载的数据,并生成更易于管理且适用于本项目的文本文档。请仔细阅读下列重要事项后操作。
请不要直接尝试使用本数据集作为车站网络运行程序,除非已经使用较小的数据集测试成功。
根据测试,使用本脚本处理 4月30日 Transperth 数据需要 6-14 分钟,会生成一共 95 个文本文件,每一个对应一个车站的时间表信息。每一个都是有车辆停驶或经过的真实车站,每个车站可能有多个火车或巴士站台,很多车站都有两个火车站台和两个巴士站台,一些车站只有一个站台,但 Elizabeth Quay 有 130 个站台。
但在本项目中,所有的站台都被视为一个大站。因为生成的文件并不包含公交线路上的每一个站,所以一些生活中常见的线路可能会有所不同。为了显著减少工作量,本脚本只会使用周三的班次信息。
可以参考上文的例子,每个时间表文件的第一行都有三个由逗号分开的数据,分别是站名,经度以及纬度(不一定真实)。之后的每一行都分为五节,出发时间,车次(巴士线路号码或火车路线名称),站台,到达下一站时间,下一站名。
在 Perth 城区,Transperth 网络共有95个相互连接的站点。如果使用 C 或 Python 编写,这些程序可以在普通的笔记本电脑运行。但一次运行大量的程序实例难以维护管理,建议先从小规模的网络逐步增加。需要注意的是,在同一台电脑上可能无法运行超过十个 Java 模拟器。
好运
克里斯托弗·麦当劳 最后编辑于 2020年5月7日
Tommy 最后编辑于 2020年5月9日午后 于 Waterford, WA