1、socket实现大文件传输有个缺点,由于传输过程是通过字节缓存发送,接受也是读写字节,导致整个传输过程效率不高,我尝试了一个169MB的视频文件传输,虽然传完了,但是耗时将近1小时,所以socket在文件传输上还是比较低效的,不建议使用socket进行大文件传送。
2、因为计算机缓存有限,所以不可能开启太大的缓存来缓存数据的,所以当我们要发送文件较大的文件时我们就要进行分段处理,分段读取,分段发送保存。大家可以看到我们的两个窗口都是同步的,一遍在读取中,一遍就在写入中。如此反复的使用同一块缓存进行数据传递。
3、首先我们先进行循环读取文件信息,这里有个重要的就是做好标记,之前写C#实战026:socket实现单文件传输时就有提到,通过在第一个字节做标记来区分我们传送的信息是什么信息,这个规则自己定义,只要客户端和服务端同步即可这里我们把0定义成信息发送,1定义成文件发送,2定义成文件头信息发送
4、首先我们先需要凸鹣沮北把我们要发送的文件信息抛给服务器,这里主要需要文件的文件名和文件大小,这里我们只要在用文件流读取文件的时候将这些数据提取出来即可://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);//发送文件文件名和长度发过去
5、既然是循环写入,我们就要记录当前文件的数据大小和已读取的数据信息,这样循环才有终点,定义一个5M缓存区。byte[] Filebuffer = new byte[1024 * 1024 * 5];//定义5MB的缓存空间(1024字节(b)=1千字节(kb))int readLength = 0; //定义读取的长度bool firstRead = true;//定义首次读取的状态long sentFileLength = 0;//定义发送的长度
6、接下里解释对文件进行分包发送了,这里唯一要注意的就是第一次发送的时候要为文件价格标记,也就是第一个数据包前加标记,这样服务端才好去识别该数据是什么数据,然后做对应的处理。
7、while (readLength> 0 争犸禀淫&& sentFileLength < fileLength) { se荏鱿胫协ntFileLength += 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();//关闭文件流
8、接下来在可以在服务端来接受数据了,同样在处理数据的时候要把第一次数据分开,因为第一组数据中添加一个标记符,所以我们在写数据的时候要截取标记后面的数据。
9、if (buffer职邗珩垃[0] == 1)//1对应文件信息 { SaveFileDialog sfDialog = new SaveFil髫潋啜缅eDialog();//创建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("保存成功!!!!"); }