C#实战027:socket实现大文件传输

前面写了一个单文件传输的,后来发现当传送的文件大于设置的缓存空间时,文件就会出现丢包的现象,导致文件无法使用,所以为了适应大文件的传输,这里我将代码进行了下修改,实现大文件传输。

不过socket实现大文件传输有个缺点,由于传输过程是通过字节缓存发送,接受也是读写字节,导致整个传输过程效率不高,我尝试了一个169MB的视频文件传输,虽然传完了,但是耗时将近1小时。

因为计算机缓存有限,所以不可能开启太大的缓存来缓存数据的,所以当我们要发送文件较大的文件时我们就要进行分段处理,分段读取,分段发送保存。大家可以看到我们的两个窗口都是同步的,一遍在读取中,一遍就在写入中。如此反复的使用同一块缓存进行数据传递。

首先我们先进行循环读取文件信息,这里有个重要的就是做好标记,之前写 C#实战026:socket实现单文件传输时就有提到,通过在第一个字节做标记来区分我们传送的信息是什么信息,这个规则自己定义,只要客户端和服务端同步即可

这里我们把0定义成信息发送,1定义成文件发送,2定义成文件头信息发送

首先我们先需要把我们要发送的文件信息抛给服务器,这里主要需要文件的文件名和文件大小,这里我们只要在用文件流读取文件的时候将这些数据提取出来即可:

//1. 第一步:发送一个文件,表示文件名和长度,让客户端知道文件大小
string fileName = Path.GetFileName(filePath);//提取文件名
Console.WriteLine("发送的文件名是:" + fileName);//查看获取文件名是否正确
long fileLength = fsRead.Length;//获取文件长度
Console.WriteLine("发送的文件长度为:"+fileLength);//查看文件长度是否正确
string totalMsg = string.Format("{0}-{1}", fileName, fileLength);//将文件名和文件长度存入一条数据中
byte[] buffer = Encoding.UTF8.GetBytes(totalMsg); //将字符串转换成字节数组
byte[] newBuffer = new byte[buffer.Length + 1];//新建字节数组,增加一个字节空间
newBuffer[0] = 2;//将第一个字节标记成2,代表为文件头信息
Buffer.BlockCopy(buffer, 0, newBuffer, 1, buffer.Length);//偏移复制字节数组
socketClient.Send(newBuffer);//发送文件文件名和长度发过去

既然是循环写入,我们就要记录当前文件的数据大小和已读取的数据信息,这样循环才有终点,定义一个5M缓存区。

byte[] Filebuffer = new byte[1024 * 1024 * 5];//定义5MB的缓存空间(1024字节(b)=1千字节(kb))
int readLength = 0;  //定义读取的长度
bool firstRead = true;//定义首次读取的状态
long sentFileLength = 0;//定义发送的长度

接下里解释对文件进行分包发送了,这里唯一要注意的就是第一次发送的时候要为文件价格标记,也就是第一个数据包前加标记,这样服务端才好去识别该数据是什么数据,然后做对应的处理。

while (readLength> 0 && sentFileLength < fileLength)
{
    sentFileLength += readLength;//计算已读取文件大小
    //第一次发送的字节流上加个前缀1
    if (firstRead)
    {
        byte[] firstBuffer = new byte[readLength + 1];//这个操作同样也是用来标记文件的
        firstBuffer[0] = 1;//将第一个字节标记成1,代表为文件
        Buffer.BlockCopy(buffer, 0, firstBuffer, 1, readLength);//偏移复制字节数组
        socketClient.Send(firstBuffer, 0, readLength + 1, SocketFlags.None);
        Console.WriteLine("第一次读取数据成功,在前面添加一个标记");//发送文件数据包
        firstRead = false;//切换状态,避免再次进入
        continue;
    }
    socketClient.Send(buffer, 0, readLength, SocketFlags.None);//继续发送剩下的数据包
    Console.WriteLine("{0}: 已发送数据:{1}/{2}", socketClient.RemoteEndPoint, sentFileLength, fileLength);//查看发送进度
}
fsRead.Close();//关闭文件流
Console.WriteLine("发送完成");//提示发送完毕

接下来在可以在服务端来接受数据了,同样在处理数据的时候要把第一次数据分开,因为第一组数据中添加一个标记符,所以我们在写数据的时候要截取标记后面的数据。

if (buffer[0] == 1)//1对应文件信息
{
    SaveFileDialog sfDialog = new SaveFileDialog();//创建SaveFileDialog实例
    string spath = @"C:\Users\admin\Desktop";//制定存储路径
    string savePath = Path.Combine(spath, recStr);//获取存储路径及文件名
    int rec = 0;//定义获取接受数据的长度初始值
    long recFileLength = 0;
    bool firstWrite = true;
    using (FileStream fs = new FileStream(savePath, FileMode.Create, FileAccess.Write))
    {
        while (recFileLength < fileLength)//判断读取文件长度是否小于总文件长度
        {
            if (firstWrite)//第一次写入时
            {
                fs.Write(buffer, 1, firstRcv - 1);//截取字节数据写入文件中
                fs.Flush();//清空缓存信息
                recFileLength += firstRcv - 1;//记录已获取的数据大小
                firstWrite = false;//切换状态
            }
            else
            {
                rec = socketServer.Receive(buffer);//继续接收文件并存入缓存
                fs.Write(buffer, 0, rec);//将缓存中的数据写入文件中
                fs.Flush();//清空缓存信息
                recFileLength += rec;//继续记录已获取的数据大小
            }
            Console.WriteLine("{0}: 已接收数据:{1}/{2}", socketServer.RemoteEndPoint, recFileLength, fileLength);//查看已接受数据进度
        }
        fs.Close();
    }
    Console.WriteLine("保存成功!!!!");
}

我把源码传在CSDN了,有兴趣的可以下载:  Socket传输大文件(发送与接收源码)

首发百度经验 :   C#实战027:socket实现大文件传输 

猜你喜欢

转载自blog.csdn.net/kevinfan2011/article/details/84796797