初步规划:
要求使用 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具体是哪些方面,然后不断给这些方面填充具体的内容和实现方法。
- 找到了一段大文件复制读写的代码: filestream/IOliu.java
- 找到了UDP传输的代码:udp/
- 尝试拼接到一起,实现udp的不可靠大文件传输
尝试成功进行传输,但存在着很大的问题:比如传一个文本,中间出现了断层!
停止判断不能在接收端使用:while(!packet.getData().equals(end));,原因不明
出现了大量的丢包。
今天的目标是实现多客户端的UDP不可靠传输;大概思路如下:
- Server会固定一个监听端口,然后在每次收到报文的时候,回传一个新的端口给Client;
- 同时分配一个线程监听这个新的端口,进行数据的传输
- 在客户端使用Thread.sleep降低传输速率。
在传输的时候卡住了,原因不明。
今天先解决一下上次遗留下来的问题。 备忘:
- Server: arg0为文件名
- Client: arg0为端口,arg1为文件名
检查到原来是忘了给packet设置addr和port; 单用户测试成功;多用户测试成功。
fos.write(packet.getData(), 0, 64);出现报错,原因不明;
将64改成 packet.getData().length,出现了很多乱码
检测出问题是在 接收方,当发送完一个packet以后,packet的offset和len都固定了,要重新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的发送接收
今日的第一个目标是按照指令格式实现udp版本的上传和下载文件。
- 发送文件:
java Client lsend myserver mylargefile - 接收文件:
java Client lget myserver mylargefile - 约定Server的监听端口为6060
主要难点:
- Client、Server寻找可用端口
- IP地址解析
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 #.
在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 - 主线程负责将两个线程负责的变量进行互通
- Socket线程负责更新:
- 缓冲区满会死锁, 需要在 IO线程内读完后给主线程刷新缓存区的指令!
- 接收传输结束标志
- 未考虑某个包既有确认了的部分, 也有未确认的部分
本以为
ReceiveHandler会很简单,没想到它的异步问题很麻烦! 生产者消费者模型, 还可能死锁!
其他:
- 关于SEQ、ACK的计数,应留意它应是一个环,所以需要在
Utils内定义相关函数进行操作- Seq/Ack 的取值范围是
[rand, rand+1023] - 初始rand SEQ是在第一次握手的时候由Client产生发给Server
- Seq/Ack 的取值范围是
- Seq计数仍然存在着问题;暂时用int不考虑循环了(毕竟有4G呢)
- 在握手时设置rwind的初始值
- 太频繁调用IO导致IO失效
三次握手的原因:
在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。
SendHandler发生死锁: packet被等待ack的线程占用,主线程无法使用之发送