NetworkComms网络通信框架序言
文件传输在客户端,服务器端程序的应用是非常广泛的,稳定的文件传输应该可以说是Tcp通讯的核心功能。下面我们来看一下如何基于networkcomms2.3.1来进行文件传输。最新的 v3版本做了一些加强,变化不是很大。
使用networkcomms2.3.1框架,您无需考虑粘包等问题,框架已经帮您处理好了。
我们看一下如何发送文件,相关代码如下:
发送文件:public void StartSendFile() { //声明一个文件流 FileStream stream = null; try { //FilePath是文件路径,打开这个文件 //根据选择的文件创建一个文件流 stream = new FileStream(this.FilePath, FileMode.Open, Fileaccess.Read, FileShare.Read); //包装成线程安全的数据流 ThreadSafeStream safeStream = new ThreadSafeStream(stream); //获取不包含路径信息的文件名 string shortFileName = System.IO.Path.GetFileName(FilePath); //根据参数中设定的值来角色发送的数据包的大小 因为文件很大 可能1个G 2个G 不可以一次性都发送 //每次都只发送一部分 至于每次发送多少 我们创建了一个fileTransOptions类来进行设定 long sendChunkSizeBytes = fileTransOptions.PackageSize; //总的文件大小 this.SizeBytes = stream.Length; long totalBytesSent = 0; //用一个循环方法发送数据,直到发送完成 do { //如果剩下的字节小于上面指定的每次发送的字节,即PackageSize的值,那么发送此次发送的字节数为 剩下的字节数 否则 发送的字节长度为 PackageSize long bytesToSend = (totalBytesSent + sendChunkSizeBytes < stream.Length ? sendChunkSizeBytes : stream.Length - totalBytesSent); //从ThreadSafeStream线程安全流中获取本次发送的部分 (线程安全流,totalbytesSent 是已发送的数,在此处代表从ThreadSafeStream中截取的文件的开始位置,bytesToSend 代表此次截取的文件的长度) StreamSendWrapper streamWrapper = new StreamSendWrapper(safeStream, totalBytesSent, bytesToSend); //我们希望记录包的顺序号 long packetSequenceNumber; //发送文件的数据部分 并返回此次发送的顺序号 这个顺序号在下面的发送文件信息时会用到 起到一个对应的作用。 connection.SendObject("PartialFileData", streamWrapper, sendFileOptions, out packetSequenceNumber); //发送上面的文件的数据部分向对应的信息 包括文件ID 文件名 在服务器上存储的位置 文件的总长度 totalBytesSent是已发送数,在此处用来传递给服务器后,服务器用来定位此部分数据存放的位置 进一步合成文件 connection.SendObject("PartialFileDataInfo", new SendInfo(fileID, shortFileName, destFilePath, stream.Length, totalBytesSent, packetSequenceNumber), sendFileOptions); totalBytesSent += bytesToSend; //更新已经发送的字节的属性 SentBytes += bytesToSend; //触发一个事件 UI可以依据此事件更新PRogressBar 动态的显示文件更新的过程 FileTransProgress.Raise(this, new FTProgressEventArgs(FileID, SizeBytes, totalBytesSent)); //每发送一部分文件 都Sleep几十毫秒,不然cpu会非常高 if (!((this.fileTransOptions.SleepSpan <= 0) || this.canceled)) { Thread.Sleep(this.fileTransOptions.SleepSpan); } } while ((totalBytesSent < stream.Length) && !this.canceled); if (!this.canceled) { //触发文件传输完成事件 UI可以调阅此事件 并弹出窗口报告文件传输完成 FileTransCompleted.Raise(this, new FTCompleteEventArgs(fileID)); } else { //触发文件传输中断事件 FileTransDisruptted.Raise(this, new FTDisruptEventArgs(FileID, FileTransFailReason.Error)); } } catch (CommunicationException ex) { LogTools.LogException(ex, "SendFile.StartSendFile"); FileTransDisruptted.Raise(this, new FTDisruptEventArgs(FileID, FileTransFailReason.Error)); } catch (Exception ex) { LogTools.LogException(ex, "SendFile.StartSendFile"); FileTransDisruptted.Raise(this, new FTDisruptEventArgs(FileID, FileTransFailReason.Error)); } finally { if (stream != null) { stream.Close(); } } }
接收文件 首先声明2个字典类 用来存放接收到的文件 和接收到的文件信息
/// <summary> /// 文件数据缓存 索引是 ConnectionInfo对象 数据包的顺序号 值是数据 /// </summary> Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>(); /// <summary> /// 文件信息数据缓存 索引是 ConnectionInfo对象 数据包的顺序号 值是文件信息数据 /// </summary> Dictionary<ConnectionInfo, Dictionary<long, SendInfo>> incomingDataInfoCache = new Dictionary<ConnectionInfo, Dictionary<long, SendInfo>>();
在接收端定义2个相对应的文件接收方法 一个用来接收文件字节部分 一个用来接收文件字节部分对应的信息类
一般在构造函数中声明
//处理文件数据 NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData); //处理文件信息 NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo", IncomingPartialFileDataInfo);
接收文件字节
private void IncomingPartialFileData(PacketHeader header, Connection connection, byte[] data) { try { SendInfo info = null; ReceiveFile file = null; //以线程安全的方式执行操作 lock (syncRoot) { //获取数据包的顺序号 long sequenceNumber = header.GetOption(PacketHeaderLongItems.PacketSequenceNumber); //如果数据信息字典包含 "连接信息" 和 "包顺序号" if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo) && incomingDataInfoCache[connection.ConnectionInfo].ContainsKey(sequenceNumber)) { //根据顺序号,获取相关SendInfo记录 info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber]; //从信息记录字典中删除相关记录 incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber); //检查相关连接上的文件是否存在,如果不存在,则添加相关文件{ReceiveFile} if (!recvManager.ContainsFileID(info.FileID)) { recvManager.AddFile(info.FileID, info.Filename, info.FilePath, connection.ConnectionInfo, info.TotalBytes); } file = recvManager.GetFile(info.FileID); } else { //如果不包含顺序号,也不包含相关"连接信息",添加相关连接信息 if (!incomingDataCache.ContainsKey(connection.ConnectionInfo)) incomingDataCache.Add(connection.ConnectionInfo, new Dictionary<long, byte[]>()); //在数据字典中添加相关"顺序号"的信息 incomingDataCache[connection.ConnectionInfo].Add(sequenceNumber, data); } } if (info != null && file != null && !file.IsCompleted) { file.AddData(info.BytesStart, 0, data.Length, data); file = null; data = null; } else if (info == null ^ file == null) throw new Exception("Either both are null or both are set. Info is " + (info == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed.")); } catch (Exception ex) { LogTools.LogException(ex, "IncomingPartialFileDataError"); } }
private void IncomingPartialFileDataInfo(PacketHeader header, Connection connection, SendInfo info) { try { byte[] data = null; ReceiveFile file = null; //以线程安全的方式执行操作 lock (syncRoot) { //从 SendI
新闻热点
疑难解答