Skip to content

Latest commit

 

History

History
169 lines (114 loc) · 9.11 KB

File metadata and controls

169 lines (114 loc) · 9.11 KB

项目日志

11-15

初步规划:

要求使用 UDP 实现 TCP,首先需要学会 UDP 和 TCP。比较犹豫的就是我该不该在现在就开始看《Unix网络编程》,还是从简单的代码入手。我进行了一下思考和资料的查阅,觉得如果我要深入学习Java后端,那我现在肯定要先将Java的内容好好啃下来。同时既然我需要学习Java,我不妨就从推荐的书目开始学起(见技术新知 11-15)。但在下载了推荐书目进行一番浏览后发现,Java基础内容中对套接字并没有太多叙述。所以我又得转回C语言(UNP),好好啃书。

关于UNP我存在着一些疑惑:C语言是如何实现套接字API的?其实现方法在不同平台(Unix、Linux、Windows)是否有差别?其实现方法和其他语言又有什么差别?希望这些疑惑可以在我阅读的过程中一一得到解答。

另外,我的Go作业也需要进行网络服务的编程——但这应该是直接使用HTTP协议的了

同时,现在我对于UDP、TCP的理论知识还不完善,因此需要重新仔细看一遍计网教材。在这个过程中应该列出一个(或多个)脑图,分析要用UDP实现TCP具体是哪些方面,然后不断给这些方面填充具体的内容和实现方法。

11-22

  • 找到了一段大文件复制读写的代码: filestream/IOliu.java
  • 找到了UDP传输的代码:udp/
  • 尝试拼接到一起,实现udp的不可靠大文件传输

尝试成功进行传输,但存在着很大的问题:比如传一个文本,中间出现了断层!

停止判断不能在接收端使用:while(!packet.getData().equals(end));,原因不明

出现了大量的丢包。

11-27

今天的目标是实现多客户端的UDP不可靠传输;大概思路如下:

  • Server会固定一个监听端口,然后在每次收到报文的时候,回传一个新的端口给Client;
  • 同时分配一个线程监听这个新的端口,进行数据的传输
  • 在客户端使用Thread.sleep降低传输速率。

在传输的时候卡住了,原因不明。

11-29

今天先解决一下上次遗留下来的问题。 备忘:

  • Server: arg0为文件名
  • Client: arg0为端口,arg1为文件名

检查到原来是忘了给packet设置addr和port; 单用户测试成功;多用户测试成功。

fos.write(packet.getData(), 0, 64);出现报错,原因不明;
将64改成 packet.getData().length,出现了很多乱码
检测出问题是在 接收方,当发送完一个packet以后,packet的offsetlen都固定了,要重新setData重置为64(magic number for now)

至此,多用户UDP传输测试成功!,
今日的下一个目标是实现基本的LFTP(本项目协议名)框架,即实现Client可以选择上传或者下载文件;
先确立一下协议的格式

协议的定义分析

  • TCP数据包包含了 src port & dst port; 但由于这里使用udp实现tcp, 端口在udp包里也是有的,所以LFTP协议内无需包含端口号
  • ACK, SEQ, CheckSum 需保留用于提供可靠传输
  • Window 用于流量控制&阻塞控制,其值是 rwind 和 cwind 当中的较小值
  • 协商最大报文需要一个MSS进行阻塞控制(?)
  • Data size, Data段

在做到最后的时候要注意留多一些时间给协议逐字段debug

为了降低一定的编写难度(在不失基本的TCP仿真性的基础上),打算用以下方式对LFTP协议进行简化:

  • 由于连接中不存在双工数据传输的情况,因此每次传输过程中发送方只发SEQ而接收方只发ACK
  • 超时间隔初始为1s,且暂定为不在超时发生时double
  • 接收区缓存 & 发送区缓存 设置为固定的常量;需要做流量控制的时候可以修改
  • 接收端: 收到超前包的时候丢弃; 发送端:因此不会收到乱序的ACK,也就无需在超时时候选择GBN还是Selective Repeat。

暂时没想到其他需要的;
完成了协议的简单定义以后,今天下一个任务是 实现udp的发送接收

12-1

今日的第一个目标是按照指令格式实现udp版本的上传和下载文件。

  • 发送文件: java Client lsend myserver mylargefile
  • 接收文件: java Client lget myserver mylargefile
  • 约定Server的监听端口为6060

主要难点:

  • Client、Server寻找可用端口
  • IP地址解析

12-2

LFTP和TCP存在着这样的差异:TCP是面向连接的,在同一条连接里会有多次的双向传输;而LFTP每次都是单次单向的传输。因此我认为在Client向Server发出指令以后,Client就直接启动定时器监测download/upload的过程。

  • Client发送请求格式为 : GETfilename/ SENfilename,中间无空格
  • Server接收到request时的回应(握手)
    • Server监测到指令格式错误:401
    • Server无该文件:404
    • Server内部错误:500
    • Server确认传输正常:200
  • 设定一个连续超时3次则取消任务的规则。

我做的三次握手是不够严谨的:我在三次握手的过程中更改了端口;更正规的practice连接和换端口应该切换了两个TCP连接。

  • 由于现在有三个参数 - 指令、文件名、Seq;因此握手时Data的格式需要重塑
    • 0: 指令, 3为客户要求下载, 7为客户要求上传 (011 & 111)
    • 1-4: Seq #. 4bytes存储int格式的Seq#.
    • 5-8: ACK #.
    • 9-12: window.
    • 13-16: data_size.
    • 17+: data部分

下一步任务是规范化header
再下一步任务是处理2,3次握手的情况

  • 由于UDP已经实现了校验和,所以这里再做一个CheckSum是多余的。[x]
  • 需要将包的处理函数打包出来 [x]
  • 建立连接以后,Server和Client就没有CS架构了,所以可以调用ReceiveHandler和SendHandler进行替代

再下一步任务是制作可信传输

遇到一个比较大的难点:GBN发送窗口应该用什么样的数据结构实现——图3-19

  • Window仅由ReceiveHandler决定,而由SendHandler保存;
  • SendHandler的缓冲区即为每次读取的文件内容
  • 每次读满一个缓冲区,然后使用GBN协议将缓冲区内数据全部传完;然后再读满,再传;循环这个过程直到文件被读完。
  • 每个seq应为 初始seq + 对应缓冲区byte #.

12-3

SendHandler编写GBN:

  • 增加rwind, cwind
  • window = min{rwind, cwind}
  • packet = min{MTU, window, RemainedBufferData},
  • 在发送文件时增加 接收ACK线程
  • 在发送文件时增加 Timer线程 (使用Thread.interrupt进行计时器更新) (超时时间暂定为1s)
  • 在发数据之前对变量进行更新:检测有无超时, 是否收到了新ACK
  • 暂定MTU为 5KB, IO缓冲区(发送方)暂定为 100KB (也即每次读文件的数量)
  • 传输结束标志为 SEQ=200

ReceiveHandler编写GBN:

  • 取消循环使用buffer数组读写,也采用一次缓冲区读完再刷新的策略
  • 新建线程处理写文件, 另一线程用来处理read socket, 主线程用来维护同步.
    • Socket线程负责更新: ack, buffer_end, end
    • File线程负责更新: buffer_start
    • 主线程负责将两个线程负责的变量进行互通
  • 缓冲区满会死锁, 需要在 IO线程内读完后给主线程刷新缓存区的指令!
  • 接收传输结束标志
  • 未考虑某个包既有确认了的部分, 也有未确认的部分

本以为ReceiveHandler会很简单,没想到它的异步问题很麻烦! 生产者消费者模型, 还可能死锁!

其他:

  • 关于SEQ、ACK的计数,应留意它应是一个环,所以需要在Utils内定义相关函数进行操作
    • Seq/Ack 的取值范围是 [rand, rand+1023]
    • 初始rand SEQ是在第一次握手的时候由Client产生发给Server
  • Seq计数仍然存在着问题;暂时用int不考虑循环了(毕竟有4G呢)
  • 在握手时设置rwind的初始值
  • 太频繁调用IO导致IO失效

三次握手的原因:

在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。

12-4

SendHandler发生死锁: packet被等待ack的线程占用,主线程无法使用之发送