• 简单实现tcp/ip下的文件断点续传


          其实在tcp/ip协议中传输文件可以保证传输的有效性,但有一个问题文件传了一部分连接意外断开了怎样;那这种情况只能在重新连接后继续传输,由于文件那部分已经传了那部分没有完成并不是tcp/ip的范围,所以需要自己来制定协议达到到这个目的。实现这个续传的协议制定其实也是非常简单,通过协议把文件按块来划分,每完成一个块就打上一个标记;即使是连接断了通过标记状态就知道还需要传那些内容。下面通过beetle来实现一个简单断点续传的程序(包括服务端和客户端)。

          在实现之前先整理一下流程思路,首先提交一个发送请求信息包括(文件名,块大小,块的数量等),等对方确认后就进行文件块发送,对方接收块写入后返回一个标记,然后再继续发直到所有发送完成。思路明确后就制定协了:

    文件传输申请信息

        public class Post:MessageBase
        {   
            public string FileName;
            public long Size;
            public int PackageSize;
            public int Packages;
            public Post()
            {
                FileID = Guid.NewGuid().ToString("N");
            }
        }
        public class PostResponse : MessageBase
        {
            public string Status; 
        }

    FileID这个值是用来协同工作的,两端根据这个ID来找到具体操作的文件和相关信息;Response提供了一个Status属性,可以用来提供一个错误的描述,如果无有任何值的情况说明对方允许这个行为.

    文件块传输信息

        public class PostPackage:MessageBase
        {
            public byte[] Data;
            public int Index;
        }
        public class PostPackageResponse : MessageBase
        {
            public int Index;
            public string Status;
        }

    文件块传输也是一个请求,一个应答;分别带的信息就是块数据信息和块的位置,同样也是根据Status信息来标记块的处理是否成功。

          结构定义完成了,那就进行逻辑处理部分;不过为了调用更方便还需要封装一些东西,如根据块大小来划分文件块的数目,获取某一文件块的内容和写入文件某一些的内容等功能。

            public static int GetFilePackages(long filesize)
            {
                int count;
                if (filesize % PackageSize > 0)
                {
                    count = Convert.ToInt32(filesize / PackageSize) + 1;
                }
                else
                {
                    count = Convert.ToInt32(filesize / PackageSize);
                }
               
                return count;
            }
            public static byte[] FileRead(string filename, int index, int size)
            {
                using (Smark.Core.ObjectEnter oe = new Smark.Core.ObjectEnter(filename))
                {
                    byte[] resutl = null;
                    long length = (long)index * (long)size + size;
                    using (System.IO.FileStream stream = System.IO.File.OpenRead(filename))
                    {
                        if (length > stream.Length)
                        {
                            resutl = new byte[stream.Length - ((long)index * (long)size)];
                        }
                        else
                        {
                            resutl = new byte[size];
                        }
                        stream.Seek((long)index * (long)size, System.IO.SeekOrigin.Begin);
                        stream.Read(resutl, 0, resutl.Length);
                    }
                    return resutl;
                }
            }
            public static void FileWrite(string filename, int index, int size, byte[] data)
            {
                using (Smark.Core.ObjectEnter oe = new Smark.Core.ObjectEnter(filename))
                {
                    using (System.IO.FileStream stream = System.IO.File.OpenWrite(filename))
                    {
                        stream.Seek((long)index * (long)size, System.IO.SeekOrigin.Begin);
                        stream.Write(data, 0, data.Length);
                        stream.Flush();
                    }
                }
    
            }

          准备工作完成了,就开始写接收端的代码了。之前的文章已经介绍了Beetle如果创建一个服务和绑定分包机制,在这里就不多说了;看下接收的逻辑是怎样处理了.

    接收传文件请求

            public void Post(ChannelAdapter adapter, Beetle.FileTransfer.Post e)
            {
                string file = txtFolder.Text + e.FileName;
                PostResponse response = new PostResponse();
                response.FileID = e.FileID;
                response.ID = e.ID;
                try
                {
                    if (FileTransferUtils.CreateFile(file, e.Size))
                    {
                        Logics.FileItem item = new Logics.FileItem();
                        item.FileID = e.FileID;
                        item.FileName = file;
                        item.Packages = e.Packages;
                        item.PackageSize = e.PackageSize;
                        item.Completed = 0;
                        item.Size = e.Size;
                        Logics.Access.Update(item);
                        AddItem(item);
                    }
                    else
                    {
                        response.Status = "不能创建文件!";
                    }
                }
                catch (Exception e_)
                {
                    response.Status = e_.Message;
                }
                adapter.Send(response);
                
            }

    接收请求后根据信息创建临时文件,创建成功就把文件相关信息保存到数据库中,如果失败或处理异常就设置相关Status信息返回.

    接收文件块请求

            public void PostPackage(ChannelAdapter adapter, Beetle.FileTransfer.PostPackage e)
            {
                PostPackageResponse response = new PostPackageResponse();
                response.FileID = e.FileID;
                response.ID = e.ID;
                try
                {
                    Logics.FileListItem item = fileListBox1.GetAtFileID(e.FileID);
                    if (item != null)
                    {
                        FileTransferUtils.FileWrite(
                            item.Item.FileName + ".up", e.Index, item.Item.PackageSize, e.Data);
                        item.Completed(e.Index);
                        response.Index = e.Index;
                        if (item.Status == Logics.FileItemStatus.Completed)
                            FileTransferUtils.Rename(item.Item.FileName);
                    }
                    else
                    {
                        response.Status = "不存在上传信息!";
                    }
                }
                catch (Exception e_)
                {
                    response.Status = e_.Message;
                }
                adapter.Send(response);
            }

    接收块请求后处理也很简单,根据FileID获取相关信息,然后把数据写入到文件对应的位置中;当所有块都已经完成后把临时文件名改会来就行了。如果处理异常很简单通过设置到Status成员中告诉请求方。

    以下就是请求端的代码了,其代码比接收端更加简单了

            public void PostResponse(ChannelAdapter adapter, Beetle.FileTransfer.PostResponse e)
            {
                mResponse = e;
                mResetEvent.Set();
            }
            public void PostPackageResponse(ChannelAdapter adapter, Beetle.FileTransfer.PostPackageResponse e)
            {
                Logics.FileListItem item = fileListBox1.GetAtFileID(e.FileID);
                if (item != null)
                {
                    if (string.IsNullOrEmpty(e.Status))
                    {
                        item.Completed(e.Index);
                        PostPacakge(item);
                    }
                    else
                        item.Status = Logics.FileItemStatus.Default;
                }
            }
            private void PostPacakge(Logics.FileListItem item)
            {
                if (mChannel != null && mChannel.Socket != null && item.Status == Logics.FileItemStatus.Working
                    && item.Item.Completed != item.Item.Packages)
                {
                    PostPackage post = new PostPackage();
                    post.FileID = item.Item.FileID;
                    post.Index = item.Item.Completed;
                    post.Data = FileTransferUtils.FileRead(item.Item.FileName,
                        item.Item.Completed, item.Item.PackageSize);
                    mAdapter.Send(post);
                }
            }

    请求端要做的工作就是发送文件传输请求,等回应后就处理PostPacakge进行文件块发送,接收到当前文件块处理成功后就发送下一块直接完成。

          到这里断点续传的功能代码就已经完成,两边的程序已经可以工作。不过对于一些使用者来说希望程序更友好的表现工作情况,这个时候还得对UI下一点功夫,如看到当前传输的状态和每个文件进度情况等。

          以上效果看起来很不错,那接下来就把它实现吧,程序使用ListBox来显示传输文件信息,要达到以上效果需要简单地重写一下OnDrawItem达到我们需要的。在讲述代码之前介绍一个图标网站http://www.iconfinder.com/,毕竟好的图标可以让程序生色不少。下面看下这个重写的代码:

            protected override void OnDrawItem(DrawItemEventArgs e)
            {
                base.OnDrawItem(e);
                StringFormat ListSF;
                Point imgpoint = new Point(e.Bounds.X + 2, e.Bounds.Y + 1);
                ListSF = StringFormat.GenericDefault;
                ListSF.LineAlignment = StringAlignment.Center;
                ListSF.FormatFlags = StringFormatFlags.LineLimit | StringFormatFlags.NoWrap;
                ListSF.Trimming = StringTrimming.EllipsisCharacter;
                Rectangle labelrect = new Rectangle(e.Bounds.X + 44, e.Bounds.Y, e.Bounds.Width - 44, e.Bounds.Height);
                if (Site == null || Site.DesignMode == false)
                {
                    if (e.Index >= 0)
                    {
                        FileListItem item = (FileListItem)Items[e.Index];
                        LinearGradientBrush brush;
                        brush =
                          new LinearGradientBrush(e.Bounds, Color.FromArgb(208, 231, 253),
                          Color.FromArgb(10, 94, 177), LinearGradientMode.Horizontal);
                        double pent = (double)item.Item.Completed / (double)item.Item.Packages;
                        using (brush)
                        {
                            e.Graphics.FillRectangle(brush, e.Bounds.X + 40, e.Bounds.Y + 2, Convert.ToInt32((e.Bounds.Width - 40) * pent), e.Bounds.Height - 4);
                        }
                        if (item.Status == FileItemStatus.Working)
                        {
                            mImgList.Draw(e.Graphics, imgpoint, 1);
                        }
                        else if (item.Status == FileItemStatus.Completed)
                        {
                            mImgList.Draw(e.Graphics, imgpoint, 2);
                        }
                        else
                        {
                            mImgList.Draw(e.Graphics, imgpoint, 0);
                        }
                        e.Graphics.DrawString(item.ToString(),new Font("Ariel", 9), new SolidBrush(Color.Black),labelrect, ListSF);
                    }
                }
            }

    重绘代码就是根据当前文件的进度内容来计算出填冲的宽度,还有根据当前文件状态绘制不同的图标,是不是比较简单:)

    整个功能完成了看下总体的效果怎样:

    下载完整代码

    FileTransfer.rar (649.79 kb) 

    如果需要Smark名称空间的代码可以到 http://smark.codeplex.com/

     

     

    访问Beetlex的Github
  • 相关阅读:
    python psutil监控系统资源【转】
    nc用法【转】
    python enumerate用法总结【转】
    find查找时排除目录及文件
    python自动安装mysql5.7【转】
    Nginx报错:upstream timed out (110: Connection timed out)和client intended to send too large body【转】
    linux压缩日志并删除原始文件
    mysql5.7主从复制--在线变更复制类型【转】
    MySQL 5.7在线设置复制过滤【转】
    Linux dd命令中dsync与fdatasync的区别【转】
  • 原文地址:https://www.cnblogs.com/smark/p/2366158.html
Copyright © 2020-2023  润新知