常见问题      欢迎来到牛博士论文网, 本站提供、本科毕业论文范文硕士论文范文博士毕业论文范文发表职称论文范文,牛博士用心为您服务!欢迎关注微信公众号   
理工论文毕业论文分类>>
毕业论文怎么写更多写论文技巧>>
关于我们
    牛博士论文网经过十余年的心酸代写历程,我们起初的梦想逐渐变成了现实,已经发展成为了一个本科、硕士、博士研究生毕业论文代写代发为主的代写毕业论文平台。十二年专注致力于博士硕士专本科论文代写服务这一核心业务模块,让我们成为了业内有序经营时间最长的综合性论文网站之一,拥有丰富的服务经验和社会资源。合作的写作老师已有2000多位,均为有丰富实践经验的高学历专业人才,以保证文稿的质量与版权,为广大毕业生解决经济、管理、法律、医学、会计、体育、历史、教育教学、建筑等专业的毕业论文及代发代写论文等服务,强大的写作团队奠定了我们的实力! 我们相信通过我们的不断努力和追求,一定能够实现与客户的互利共赢!

基于TCP的文件传输系统——文件接收模块的设计与实现

站外转载   发布时间:2019-01-03   [点击量:1524]  


这是一篇计算机毕业设计毕业论文范文、也教大家毕业论文怎么写。关注牛博士论文网(www.211nbs.com),每日分享毕业论文范文,也提供毕业论文代写,代写毕业论文,代写硕士论文,论文发表,服务广大学子,帮助完成学业,顺利毕业!


随着互联网的快速发展,资源共享的重要性越来越明显,资源的合理配置,不仅能够节约社会成本,而且还能创造出更多的社会财富,而文件传输则是资源共享的重要方法之一。资源共享的效率依赖于技术发展,因而研发安全、高效、全面的文件传输系统有着十分重要的意义,本设计主要完成的就是文件传输的有效实现。通过对整体设计的综合分析,本设计采用了计算机网络中运用最多的C/S工作模式和微软公司发布的安全、稳定、简单的面向对象C#程序设计语言,运用目前最为流行的Windows平台应用程序开发环境VS2010以及有很多优点的SQL Server 2008数据库。 TCP协议是一种可靠的通信协议,它的三次握手过程充分保证了通信的可靠性。在本文中我们采用了TCP协议传输文件,同时也使用了TcpLister和TcpClient两个重要的类,完成了一个具有简单聊天并收发文件等功能的系统平台。在本文中我们将对文件的接收过程做详细的介绍。


关键词:TCP协议;文件传输;Socket通信;可靠性


基于TCP的文件传输系统

        ——文件接收模块的设计与实现

          

    一、开发工具及其环境

本系统数据库采用的是SQL Server 2008,与其他数据库相比它有可靠性好、智能性、可伸缩性好、开发效率高、与Office 的集成以及总体拥有成本(TCO)低等诸多优点。

    VS2010是一款由微软推出的,也是目前最流行的Windows应用程序开发环境,于2010年4月12日上市,其界面与以前相比变得更加简单明了,同时支持最新的.Net Framework 4框架和64位Windows,以便其可以根据自己的需求来进行相关工作,同时也支持开发面向Windows 7的应用程序,数据库方面除了SQL Server,它还支持多种其他数据库。

    C#是微软公司发布的一种安全、稳定、简单的面向对象的、运行于.NET Framework之上的高级程序设计语言,是微软公司.NET windows网络框架的主角。它与Java有着很多相似的地方,同时与COM也是直接集成的。C#在继承C和C++功能的基础上去掉了一些它们的复杂特性,综合了VB和C++的诸多优点,成为.NET开发的首选语言。源代码的可移植性对编程语言十分重要,C#就有很好的可移植性性,它与C/C++极大的相似性,使其开发者可以很快转向。C#适合独立系统和嵌入式系统以及各种其他类型的系统,同时还对国际化的支持非常重要。

客户端/服务器模式C/S和浏览器/服务器模式B/S是开发模式技术架构的两大主流技术,本设计采用的是C/S模式。在网络中向其他主机提供服务的主机称为服务器,接收服务的主机则称为客户机。目前Client/Server模式是计算机网络中采用最多的工作模式,它具有充分发挥客户端处理能力、负荷较轻、结构简单清晰、便于管理、交互性强和操作界面美观等诸多优点。


    二、相关知识与关键技术简介

本系统主要分成服务器和客户端两个大的模块,服务器端要实现的功能主要是启动侦听、接收客户端的连接请求,将所有与自己建立连接的客户及其相关信息存到自己的数据字典中以便响应客户端刷新在线用户列表的请求,以及响应客户端断开连接的请求等。客户端要实现的功能有与服务器和其他客户端建立连接、接收网络上传来的数据、处理接收到的数据等。下面介绍一下在本设计中使用到的知识与关键技术。

  (一)TCP协议与面向TCP协议的socket

    在计算机网络体系结构中,TCP/IP参考模型是因特网的基础。TCP/IP协议采用层次体系结构,每一层在逻辑上与通信端的对应层相连接,并由下层向上层提供服务。服务的形式有两种:一种是无连接的UDP服务,另一种是面向连接的TCP服务。UDP协议提供的服务是不可靠的,但是能大大地提高速度。而TCP则是一种面向连接的,可靠的,基于字节流的传输层通信协议,它提供的服务是可靠的,能够充分保证数据传输的准确性。目前大多数的网络应用层协议都是基于TCP协议的(如HTTP、FTP、SMTP、Telnet、POP3等协议)。TCP建立一个连接需要三次握手,一旦通信双方建立了TCP连接,连接双方就能任意收发对方的数据。TCP要求在发送数据之前需要先打开连接,服务器利用一个已知的端口号创建一个连接。客户端向服务器发送一个同步序列号(SYN)以标识连接,并选择一个动态端口号作为本地端口使用。服务器必须向客户端发送一个确认(ASK)以及服务器的序列号(SYN)。随后,客户端回复一个ASK,这样就建立了连接。然后可以发送和接收消息了,接收消息后,总是返回ASK消息。如果在收到ASK之前发送方已经超时,则消息将被重新发送。由于他的握手机制,TCP协议的可靠性很高,但是可靠性是用时间和负载等换取来的。

    应用程序一般通过Socket发出或应答网络请求,Socket即套接字,用于描述IP地址和端口信息。Socket分为两种,流式socket(Socket_Stream)和数据报式socket(Socket_DGram)。前者针对于面向连接的TCP服务应用,提供的是双向的、有序的、无差错的数据流服务,有较好的可靠性。后者针对的是无连接的UDP服务,以独立的数据报进行传输,但却不保证顺序性和可靠性。基于TCP的文件传输,可以直接使用socket通信,也可以使用TcpLister类和TcpClient类进行通信。TcpLister和TcpClient类,其实就是Socket封装后的类,本文就使用的是TcpLister类和TcpClient类,两个类都位于 System.Net.Socckets命名空间下。TCPListener类常用方法如表1所示:

表1  TCPListener类常用方法

20190103094710308

方法 说明

AcceptSocket 从端口处接收一个连接并赋予它Socket对象

AcceptTcpClient 从端口处接收一个连接并赋予它TCPClient对象

Equals 判断两个TcpListener对象是否相等

GetType 获取当前实例的类型

Pending 确定是否有挂起的连接请求

Start 开始接听传入的连接请求

Stop 关闭监听器

ToString 创建TcpListener对象的字符串表示

                                                    

TcpClient常用的属性与方法如表2所示:

20190103094722988

表2  TcpClient常用属性与方法

属性或方法 说明

Client 获取或设置基础套接字

LingerState 获取或设置套接字保持连接的时间

NoDelay 获取或设置一个值,该值在发送或接收缓存冲未满时禁止延迟

ReceiveBufferSize 获取或设置TCP接收缓存区的大小

ReceiveTimetut 获取或设置套接字接收数据的超时时间

SendBufferSize 获取或设置TCP发送缓存区的大小

SendTimeout 获取或设置套接字发送数据超时时间

Close 释放TcpClient实例,而不关闭基础连接

Connect 用指定的主机名和端口号将客户端连接到TCP主机

BeginConnect 开始一个远程主机连接的异步请求

GetStream 获取能够发送和接收数据的NetworkStream对象

  (二)多线程技术

    对于多任务操作系统,多线程技术可以实现多个通信同时进行,提高CPU的利用率和程序的处理能力。多线程技术具有很多优点:

    1. 通信简洁、信息传送速度快

    线程间的通信在统一地址空间进程,不需要额外的通信机制; 

    2. 响应能力

    多线程在线程的一部分被阻塞的时候还能继续运行,增强了响应能力; 

    3. 资源共享

    线程共享它们所属进程的内存和资源,不同的线程可以拥有相同的地址空间;

    4. 系统开销小

    为进程创建分配内存和资源的开销是很大的,多线程可以资源共享,减少了内存分配,因此在创建线程的时候就能大大减少系统开销;

    5. 并行性高

线程的执行是独立的,并行性较高。

    本设计中多处使用到线程,以线程安全的方式访问控件十分重要。访问 Windows 窗体控件本质上不是线程安全的,如果有多个线程同时操作某一控件,就可能会使该控件进入到一种不一致的状态,或者出现争用或死锁的情况。如果某控件被非创建该控件的线程调用,调试器就会引发一个 InvalidOperationException,并报线程间操作无效的异常。windows窗体控件中有一个Invoke方法,结合代理我们就可以很好地实现线程安全访问控件。如本系统中的使用如下:

    richTextBoxLog.Invoke(new MethodInvoker(delegate()            

    {

         richTextBoxLog.AppendText(showInfo);

    }));

  (三)数据字典

    数据字典即键值对,存入的是值,但一般对外使用的只有键,键是值得对象,一般选用可以唯一识别值的对象做键。本文中设计到三个数据字典,用法与内容如下:

    1. 客户端数据字典1

用来存放已经成功与服务器建立连接的用户信息:

    public static Dictionary 

    clientUser = new Dictionary();

    2. 客户端数据字典2

用来存放与自己建立连接的其他用户信息:

    public static Dictionary 

    clientUserSocket= new Dictionary();

    3. 服务器端数据字典

用来存放与自己建立连接的用户信息:

    public static Dictionary 

    clientUser = new Dictionary(); 

    我们在定义数据字典之前一般都会先给其定义一个锁,用一个上锁解锁的操作来保证数据字典操作的安全性,以客户端数据字典1为例:                                 Monitor.Enter(ClassStaticData.lockClientUser);

       …………//此省略处为对数据字典1的操作;

Monitor.Exit(ClassStaticData.lockClientUser);

在操作数据字典之前要先对其上锁,拒绝其他操作该数据字典的行为,在该操作完成后再对其解锁,解锁后才能允许其他操作行为,这样就能避免出现正在写入时读取数据字典中的数据而导致读取不准确等问题。

  (四)文件流的读写

    1. 读

    创建文件流fs,并打开指定路径filepath的文件,将其读入流中:

FileStream fs = File.Open(path, FileMode.Open);然后将该流的当前位置设置为偏移量offset:fs.Seek(offset, SeekOrigin.Begin);再从流中偏移位置开始读取count大小的字节块写入到定缓冲区中:

fs.Read(byte[] array, int offset, int count);

    2. 写

    创建文件流fs,并在指定路径filesavepath中创建或覆盖文件:

FileStream fs = File.Create(filesavepath);再使用从缓冲区(数组)中读取的数据将字节块读入该流中:fs.Write(byte[] array, int offset, int count);

  (五)ProgressBar控件

    ProgressBar 控件是用来表示操作进度的。本设计中,在文件的“发送”按钮单击事件触发后,就会将聊天界面的ProgressBar 控件传给ClassClient类的sendFileBar对象,在准备发送文件的时候又将sendFileBar赋值给了ClassSendFiles 类对象sendFile的sendFileBar对象,这样就将聊天窗体的ProgressBar 控件与文件的发送联系了起来。在开始发送文件之前,先把控件范围的最大值(sendFileBar.Maximum)设置为n,即发送的文件的总包数,然后在成功发送了一个文件内容的数据包之后就将进度栏的当前位置(sendFileBar.Value)加1,在文件所有包都发送完毕之后,将其Visible属性设置为false,即将其设为不可见,这样进度条的设置就完成了。




    三、主要功能实现类

  (一)ClassClientToServer类

    主要功能是与服务器建立连接、断开连接,以及请求与刷新在线用户列表等。

    1. 解析过程

    在其connectServer()方法中通过创建TcpClient类的对象tcpClient,调用其Connect()方法,以服务器的IP与Port做参数,向服务器发出连接请求,然后启动一个线程,让其去执行receive()方法:

    TcpClient tcpClient = new TcpClient();

    tcpClient.Connect(serverIPAddress, serverPort);   

    Thread tempThread = new Thread(new ThreadStart(receive));

    tempThread.Start();

Receive()方法的具体处理过程如下:用TcpClient类对象tcpClient调用TcpClient类的Available属性,来获得从网络接收的数据量,即服务器向其发送的数据包的拼接。如果数据量大于0(tcpClient.Available > 0),则将从绑定的套接字中接收到的数据存入到接收缓冲区(tempBytes数组)中,用tcpClient.Client.Receive(tempBytes)来实现。然后设置一个开始标识和结尾标识来表示tempBytes数组中的位置,在一个for循环中实现如下操作:先运用findEndFlag()方法来寻找结尾标识,即从数组的开始标识所表示的位置开始,依次循环数组的每一位,看能不能找到依次为31、33、35、37的四位,如果找到的话,表示该包在此结束。然后将数组从开始标识到结尾标识的段取出来存入byte数组instructionByteS中,instructionByteS数组中即是一个完整的数据包。接着就去解析instructionByteS中存入的该数据包,最后将开始标识移到结尾标识的下一位,循环上面步骤去读取下一个数据包,直到把缓冲区中所有的数据循环完后结束for循环。如果数据量为0,表示服务器未发来任何数据,就停顿500ms后重新判断数据量。

    2. 解析的数据包

    具体解析过程如下:首先判断数组第1位:

    ⑴ 如果是0

    表示该包为文本格式,调用processInstructionText()方法处理。方法实现了从数组的第2位开始,取出数组第1位中所示的长度,其实就是把该数据包中的实际信息段给取了出来,转换成string类型赋值给变量,实际信息其实是服务器输入的会话内容,在这里主要是用来做测试,判断其是否与服务器成功建立连接,没有太大意义。

    ⑵ 如果是1

    表示该包为数据格式,调用processInstructionData()方法处理。方法实现了从数组的第1位开始,取出4位,即把该数据包的指令类型给取出来,解析到为1004,表示接收到的包是服务器发来的新的在线用户信息,是由多个用户的用户名,IP和Port组成的一个字符串。用户解析到此包后先清空自己的数据字典1,然后用循环的方式将收到的信息全写到数据字典1中。

    3. 封装的数据包

    同时里面也封装了一些包,用tcpClient.Client.Send(sendByteS)发送出去,send()方法即将指定的数据包发送到连接的Socket,其中包括:

    ⑴ 文本格式数据包

    在与服务器建立成功建立连接后,会封装自己的用户名并发送出去;

⑵ 数据格式数据包

    ① 1002

将自己的IP地址和侦听的端口号发送出去,主要目的是让服务器接收到后,将自己的用户名和信息一起记录到其数据字典中。该包在成功与服务器建立连接,并将实际信息为自己的用户名的数据包发送出去之后发送;

② 1003

发送方向服务器请求在线用户列表。这是在已经将指令类型为1002的包成功发送出去后,启动一个新的线程来执行发送出去的,同时在刷新在线用户列表的按钮单击事件中也发送;

    ③ 1010

    请求与服务器断开连接。由退出登录的按钮单击事件和Form1窗体的关闭事件触发。

  (二)ClassClientListener类

    主要功能是启动侦听,确定是否有挂起的连接请求。通过创建TcpListener类的对象tcpListener,调用TcpListener的Start()方法,参数为自己的IP和Port,表示开始接听传入的连接请求,即:

    TcpListener tcpListener = new TcpListener(myIPAddress, port);

    tcpListener.Start();

然后在登录按钮的单击事件与listBoxOnlineUser列表中在线用户名双击事件发生时,调用该类的startListen()方法,在该方法中启动一个线程,去执行listen()方法,Listen()方法的具体处理过程如下:

用TcpListener类的Pending()方法确定是否有挂起的连接请求进来,如果有就调用TcpListener类的AcceptSocket()方法,表示接收一个连接,并将其赋值给一个Socket对象,然后将该Socket作为参数创建一个ClassClient类的对象,调用ClassClient类的startListen()函数处理,具体实现将在下面的ClassClient类中详细介绍。如果没有挂起的连接请求进来,就等待500ms后再来判断是否有挂起的连接请求。

    在退出登录的按钮单击事件与Form1窗体关闭事件触发时,调用其stopListen()方法,在其里面通过TcpListener类的Stop()方法来关闭监听器,实现停止侦听。

  (三)ClassClient类

    主要功能是实现两个用户之间的通信,包括建立连接,收发消息和文件等。

    1. 解析过程

    在ClassClientListener类中,实现了将接收到的一个连接赋值给一个Socket对象,然后将该Socket作为参数创建一个ClassClient类的对象,并调用ClassClient类的startListen()函数处理,该方法开启了一个线程,去执行receive()方法。值得注意的是,在启动线程之前,我们要先将开启的线程设置成了单线程单元(STD)状态。因为在本类中接收文件的处理过程中,会使用SaveFileDialog控件,如果不设置成STD状态的话,则不能正常使用该控件。Receive()方法的具体处理过程如下:

用Socket对象clientSocket调用其Available属性来获得已经从网络接收的数据量,在这里即前面与其成功建立连接的用户发送过来的数据包的拼接。如果数据量大于0,则将从绑定的套接字中接收到的数据存入到接收缓冲区(receiveBytes数组)中,用clientSocket.Receive(receiveBytes)来实现。然后处理与ClassClientToServer类中大同小异。接着也去解析instructionByteS中存入的该数据包。

    2. 解析的数据包

    具体解析过程如下:首先判断数组第1位:

    ⑴ 当该包为文本格式时

    发送出该数据包的其实是想与该用户建立连接或者已经建立连接的另一个用户。如果是还未建立连接的用户,则包的实际信息为该用户的用户名,用户收到此包后即将该用户加到自己的数据字典2中;如果是已经建立连接的用户,则包的实际信息为该用户发送过来的会话消息;

    ⑵ 当该包为数据格式时

    把指令类型取出来后,根据指令类型去做不同的处理,具体如下:

    ① 1005

    表示对方请求向自己发送文件,弹出一个包含有Yes和No两个按钮的MessageBox,显示发送方名称、文件类型、文件大小等基本信息,在按Yes后,弹出一个saveFileDialog,选择存储文件的路径,并给文件起一个名字,按确定后,就封装一个1006包发送出去,表示自己同意接受文件;

    ② 1006

    表示对方同意接收文件,弹出一个MessageBox,显示接收方同意接收文件,请按确定开始发送的信息,然后就连接数据库SendFileSet,将要发送的文件读到文件流中,定义一个一定大小的byte数组,将文件拆成小段,每次取一段存入该数组中,根据流大小和数组大小确定要拆成多少个数据包(n)以及最后一包的大小(m),同时往数据库的SendFileInfo表中插入相应的记录。然后向数据库的SendState表中插入状态为等待发送的n条相应记录,接着封装一个指令类型为1007的数据包发送出去,即将文件的拆包情况告知接收方,最后定义一个ClassSendFiles类的对象,调用其startSendFile()方法去实现文件的具体发送;

    ③ 1007

    表示对方向自己发来了发送的文件的拆包情况,包括文件名、包总数、包大小以及最后一包大小等信息,先连接数据库,将相应的信息解析出来赋值给变量,然后向数据库的ReceiveFileInfo表中插入相应的一条记录。

    ④ 1008

    表示收到对方发来的带着文件的实际内容的数据包,连接数据库,解析包的相应信息赋值给变量,向ReceiveState 表中插入相应记录,同时根据判断该数据包是不是文件的最后一包来更新ReceiveFileInfo中对应的记录信息。值得注意的是,在这里包的长度我们不再定义为1位,因为文件的实际内容可能较大(存放文件内容的数组大小是自定义的,同时还可能有其他信息),所以1位一般情况下是不够的,因此在这里我们把长度固定成了10位,在解析包的实际信息段时是从第15位开始的;

    ⑤ 1009

    表示文件发送方告知文件发送已完成。连接数据库,先查询ReceiveFileInfo表,将相应的信息(发送完的文件的文件名)取出来赋值给变量,再根据变量按序号升序的方式查询ReceiveState表,将查询结果借助于DataTable填充到SqlDataAdapter的对象中,接着根据记录是不是最后一条记录,用一个for循环来将每条记录的文件内容列取出来依次写入文件流中。循环结束后跳出一个MessageBox提醒文件接收的完成。

    3. 封装的数据包

    同时里面也封装了一些包发送出去,包括:

    ⑴ 文本格式数据包

    发送出去的实际内容其实是自己的用户名或者会话消息。如果本用户未与另一个用户建立连接,则发送的实际内容就是自己的用户名;如果已经建立了连接,则发送的实际内容是自己给对方发的会话内容;

    ⑵ 数据格式数据包

    ① 1005

    请求向对方发送文件,告知接收方文件名称、类型和大小等信息;

    ② 1006

    表示自己同意接收文件,是在解析到指令类型为1005的数据包后封装发送的;

    ③ 1007

    表示将数据库中SendFileInfo表中记录的发送的文件的拆包情况发送出去;

  (四)ClassSendFiles类

    主要功能是实现文件的具体发送。ClassClient类中,在文件发送方得到接收方许可确认发送之后,我们通过创建了一个ClassSendFiles类的对象sendFile,以本类中收到的Socket、文件名和文件接收方用户名为参数,调用ClassSendFiles类的startSendFile()方法来实现文件内容的具体发送,该方法开启了一个线程去执行threadSendFile()方法,threadSendFile()方法具体操作如下:

    连接数据库SendFileSet,到SendFileInfo表中查询出文件路径、总包数、已发送包数、包大小以及最后一包大小等信息,借助于DataTable填充SqlDataAdapter对象,以便后面可以将查询到的记录的列的相应值取出来赋值给变量使用。然后更新SendFileInfo表的发送状态为正在发送,同时创建一个文件流对象fs来读入文件:

    FileStream fs = File.Open(filepath, FileMode.Open);

其中的filepath是通过上面查询语句查询出来的文件的存放位置,通过这条语句,就实现了将filepath位置的文件读入了文件流fs中,接下来的操作可以用伪代码表示为:While(CanRunning)//CanRunning在调用startSendFile()时被置成了true

{  

     //到SendState表中查询出一条序号最小的等待发送的包   

         //if,如果查到了记录,就发送,更新该包的状态为正在发送

         //如果序号小于包总数,大小用包大小

         //如果序号等于包总数,大小用最后一包大小

         //封装包,发送出去

         //发送完成后更新SendState中该包的发送状态为发送成功

         //else,没有查到记录

         //更新SendFileInfo的发送状态为发送完毕

     }

发送的数据包指令类型为1008,当文件发送完毕后,封装一条指令类型为1009的数据包发送出去,通知接收方文件已经发送完毕,发送界面(聊天界面)如图1所示:

2019010309463409

图1  发送文件的聊天界面


(全文略)

文章标题:《基于TCP的文件传输系统——文件接收模块的设计与实现》,原文地址:,如有转载请标明出处,谢谢。

上一篇:退休人员服务系统的设计与实现_毕业设计论文


下一篇:基于Django的B/S微博系统——后端程序设计与开发


[相关文章]