基于不可靠数据报的文件传输open in new window

目录:
    引言
    数据报格式
    协议
    实现
    总结

引言

这个学期学习了一下<<计算机网络>>,在此把它的一个小部分应用一下。大家都知道在TCP/IP中的运输层,有两种协议,那就是TCP和UDP。它们的区别,大家一定很清楚了,TCP面向连接的数据报传输,而UDP是无连接的数据报传输,从而TCP可以提供可靠的传输(不会出现包丢失,包错位等等,当然这只是站在应用层角度来看,其实TCP提供了完善的重传机制),UDP则只是尽最大努力交付,它是不可靠的(也即可能出现包丢失,包的路由不同而错位等等),但它比TCP小巧快捷。

FTP就是基于TCP的文件传输协议。但,最近我遇到了一种网络环境,它没有TCP,而只有UDP;现在,我们想通过这种网络环境实现文件传输。大家可能和我一样想到是用TFTP(基于UDP的文件传输协议),TFTP的包只有四种,实现起来也并不复杂,但是,它是基于停止等待协议,这种协议,数据传输的吞吐率太低。接着,我想到了把在数据链路层最实用的连续重传协议应用到文件传输中来。以下就来介始,我的实现过程。

数据报报头格式

首先,我们把我们文件传输协议称为UFTP(Unlinked File Transfer Protocol),接着我们来自定义UFTP的报头格式。

类型文件标号
块号检验和

以上,每个区都是16位。

1.类型:

public enum UFTPEnum :ushort
{
    WRITE=1,WRITE_OK=2,TRANSFER=3,ACK=4,REC_ALL=5,MSG=6,ERROR=7,PING=8,PING_OK=9 ,ABORT=10
}
MSG:聊天信息
PING: 检查对方主机是否在线
PING_OK: 在线确认包
WRITE:写请求(也即上传)
WRITE_OK: 表示同意上传
TRANSFER: 表示文件传输数据包
ACK: 确认包
REC_ALL: 表示收到了文件所有数据.
ERROR:表示错误包(如检验和不对的包)
ABORT: 请求立即中止

2.文件标号:唯一标志要传输的文件。

3.块号:唯一标志所传输的块.

4.检验和:用来检查包是否正确.

A协议

我们约定:请求上传的主机叫为A,作出响应的主机叫为B。现在A要上传文件给B:

数据流程

(1)A主动去PING(发送类型为PING的包) B,若失败(超时),则报告“对方主机不在线”而退出;否则,转到(2)。
(2)A主动发送写请求给B,若失败(超时),则报告错误而退出;否则记下目的文件当前大小,并转到(3)。
(3)A打开源文件(且定位到要开始传送的位置)并载入数据到缓存中。
(4)从缓存中循环发送窗口大小个的数据包;若收到OVER(指的是REC_ALL 或 ABORT),则立即退出;若超过循环次数,则报告超时错误而退出。
(5)收到ACK+块号时,设置块号(若块号为0,则载入数据到缓存)并重置循环次数。
(6)收到ABORT 或 REC_ALL,则退出。

相关说明:

(1)每次从源文件中载入数据都要记下载入长度,以便控制文件结尾的数据包大小。
(2)ACK 中块号标志“需要收到的包”。
(3)由于协议(5)不断修改块号和重置循环次数,从而协议(4)的窗口不断移动。
(4)到了缓存末端,可能循环发送小于窗口大小个的数据包。
(5)写请求包中含有文件名,文件大小,数据内容大小。

B协议

(1) 若收到PING,则立即发送PING_OK。
(2) 若收到WRITE,则记下文件标号.文件名.文件大小.数据内容大小,并做好接收数据准备(打开或新建文件),并发送WRITE_OK 。
(3) 若收到TRANSFER,则重置计数器,检查长度是否正确,再检查是不是当前要的包,若是则写入文件中且块号加一,接着检查文件数据是否接收完毕,若完毕则发送REC_ALL并退出,否则发送ACK+块号。

相关说明:

(1)WRITE_OK 中载有目的文件的当前大小,这样可以做到续传。
(2)若计数器到时,则报告超时且发送ABORT而退出。

实现

以下给出部分协议实现代码:

(1)A端

#region 打开和关闭流  

private void Open()
{
    if (MyStream == null)
    {
        if (File.Exists(this.FileName))
        {
            MyStream = new FileStream(this.FileName, FileMode.Open, FileAccess.Read, FileShare.Read);
            MyStream.Seek(_Position, SeekOrigin.Begin);
        }
        else throw new Exception(this.FileName + " 不存在!");
    }
}

public void Close()
{
    if (MyStream != null)
    {
        MyStream.Close();
        MyStream = null;
    }
}

#endregion

public void HandleWRITE_OK(UFTP_Packet packet)
{
    if (!WRITE_OK)
    {
        this.WRITE_OK = true;
        this._Position = Convert.ToInt64(System.Text.Encoding.Unicode.GetString(packet.MessageBuffer));
    }
}

public void HandleACK(UFTP_Packet packet)
{
    if (packet.AliceID == 0 && this._AliceID > 0)
    {
        LoadData();
    }
    this._AliceID = packet.AliceID;
    this._Time = 1000;
}

/// <summary>  
/// 把文件数据发出去  
/// </summary>  
private void SendThread()
{
    if (!this.Ping() || !this.SendWRITE())
    {
        ShowError("Ping or Write Fail");
        this.Dispose();
        return;
    }
    //  
    Open();
    LoadData();
    //  
    UFTP_Packet packet = new UFTP_Packet();
    packet.FileID = this.FileID;
    packet.UFTPType = UFTP_Packet.UFTPEnum.TRANSFER;
    while (_Time > 0)
    {
        uint start = (ushort)(_AliceID * Multi.UnitSize);
        uint file_end = _FileEnd;
        for (uint i = start; i < start + WindowSize * Multi.UnitSize && i < file_end; i += Multi.UnitSize)
        {
            if (OVER) return;
            packet.AliceID = (ushort)(i / Multi.UnitSize);
            uint len = Math.Min(Multi.UnitSize, file_end - i);
            if (len <= 0) break;
            packet.MessageBuffer = new byte[len];
            Array.Copy(this.Buffe, i, packet.MessageBuffer, 0, len);
            SendPacket(packet.ToBytes());
            Thread.Sleep(1);
        }
        _Time--;
    }
    if (!OVER)
    {
        ShowError("对方主机失去响应,超时退出");
    }
    //  
    this.Dispose();
}

/// <summary>  
/// 从源文件中载入数据  
/// </summary>  
private void LoadData()
{
    this._FileEnd = (uint)MyStream.Read(this.Buffe, 0, this.Buffe.Length);
}

/// <summary>  
/// 发送PING  
/// </summary>  
/// <returns></returns>  
private bool Ping()
{
    UFTP_Packet ping = new UFTP_Packet();
    ping.FileID = this.FileID;
    ping.UFTPType = UFTP_Packet.UFTPEnum.PING;
    for (int i = 0; i < 10; i++)
    {
        SendPacket(ping.ToBytes());
        Thread.Sleep(100);
        if (this.PING_OK) return true;
    }
    return false;
}

/// <summary>  
/// 发送写操作  
/// </summary>  
/// <returns></returns>  
private bool SendWRITE()
{
    FileInfo info = new FileInfo(FileName);
    UFTP_Packet packet = new UFTP_Packet();
    packet.FileID = this.FileID;
    ushort len = (ushort)(this.Buffe.Length / Multi.UnitSize);
    byte[] write = packet.ToBytes(UFTP_Packet.UFTPEnum.WRITE,
        System.Text.Encoding.Unicode.GetBytes(info.Name + "|" + info.Length.ToString() + "|" + len.ToString()));
    for (int i = 0; i < 10; i++)
    {
        SendPacket(write);
        Thread.Sleep(100);
        if (this.WRITE_OK) return true;
    }
    return false;
}

/// <summary>  
/// 创建文件标志号  
/// </summary>  
/// <returns></returns>  
private ushort CreateFileId()
{
    return (ushort)(new System.Random().Next() % ushort.MaxValue);
}

(2)B端

public void HandleWRITE()
{
    if (MyStream == null)
    {
        try
        {
            MyStream = new FileStream(SavePath + @"\" + FileName, FileMode.Append, FileAccess.Write, FileShare.Read);
            _Count = _Position = MyStream.Position;
            SendWRITE_OK();
        }
        catch (Exception ex)
        {
            this.Dispose(ex.Message);
        }
    }
}

public void SendWRITE_OK()
{
    if (MyStream != null)
    {
        _UFTP.FileID = this.FileId;
        _UFTP.AliceID = this.AliceId;
        _UFTP.UFTPType = UFTP_Packet.UFTPEnum.WRITE_OK;
        _UFTP.MessageBuffer = System.Text.Encoding.Unicode.GetBytes(_Position.ToString());
        SendPacket(_UFTP.ToBytes());
    }
}

#region 处理传输
public void HandleTRANSFER(UFTP_Packet packet)
{
    if (packet.MessageLength != Multi.UnitSize && packet.MessageLength != (FileLen % Multi.UnitSize)) return;
    // 
    _IsTimeOut = false;
    // 
    if (packet.AliceID == this.AliceId)
    {
        WriteData(packet.MessageBuffer);
        this.AliceId = (ushort)((this.AliceId + 1) % this.BuffeLen);
    }
    // 
    if (this._Count < FileLen)
    {
        SendAck();
    }
    else if (this._Count == FileLen)
    {
        Console.WriteLine("=");
        Close();
    }
    else if (this._Count > FileLen)
    {
        Console.WriteLine(">");
        Close();
    }
}

private void SendAck()
{
    _UFTP.FileID = this.FileId;
    _UFTP.AliceID = this.AliceId;
    _UFTP.UFTPType = UFTP_Packet.UFTPEnum.ACK;
    SendPacket(_UFTP.ToBytes());
}

private void WriteData(byte[] data)
{
    if (MyStream != null)
    {
        MyStream.Write(data, 0, data.Length);
        this._Count += data.Length;
    }
}

private void Close()
{
    //告诉对方文件传输完毕 
    _UFTP.FileID = this.FileId;
    _UFTP.AliceID = this.AliceId;
    _UFTP.UFTPType = UFTP_Packet.UFTPEnum.REC_ALL;
    for (int i = 0; i < 3; i++) SendPacket(_UFTP.ToBytes());
    // 
    this.Dispose();
}
#endregion

总结

本文的目的是改进TFTP,主要是在传输机制上(TFTP是用的是停止等待,而UFTP用的是连续重传),这样传输吞吐率有了提高。

UFTP调试成功,运行的结果还较理想。但它还有很多不足,它只实现了上传,没有下载;而且由于本人能力还有限,上传协议可能还不够完善,在此恳请大家提议。

posted on 2005-01-30 02:52 THOMAS 阅读(160) 评论(3)


re: 基于不可靠数据报的文件传输 2005-01-30 03:01 THOMAS

我的第一篇文章终于完成了,哈哈哈~~~

re: 基于不可靠数据报的文件传输 2005-01-30 03:16 THOMAS

说实话吧,为什么要写一个基于不可靠数据报的文件传输?
是这样的,我们学校上网还要认证。有一次,帐号没钱了,不能上网,闲着,就用自己写的截包工具去截包,发现此时还可以收到多播包(IGMP),它是基于UDP的。然后,我想到用这个学期的学的网络知识去实现一个基于IGMP的文件传输工具,这样,就可以在没有认证情况下同学之间传输文件.昨天,就用它传一个电影而还能边传边看还能续传,哈哈,爽呆了!!!

re: 基于不可靠数据报的文件传输 2005-01-30 03:42 fang

不过~~~~~~~~~~~~~~~~~~~~~~
好歹是你的一番心血
我会看的啊
放心
对我以后的学习肯定有好处啊

Contributors: FHL