1、网络文件传输系统的设计与实现 摘要:在科学技术飞速发展的今天,Internet已经和人们的日常生活息息相关,无论是工作,学习还是娱乐,都离不开网络。比如有时候需要进行文件的传输,虽然现在的许多网络文件传输工具能基本满足人们对文件传输质量的要求,但是它们往往都存在安全性,工作效率低等问题。本课程设计的文件传输系统是在Windows操作系统下,Visual C + + 6.0环境下借用WinSock控件实现的,是基于TCP/IP协议的C/S模式,在服务器和客户端分别以socket为中心进行编程,客户端和服务器端的界面分别是由文件发送模块和文件接收模块组成。客户端先调用connect()与服务器建立
2、连接,然后用send()发送数据;服务器端先调用listen()侦听客户端的连接请求,然后调用accept()对连接请求进行响应,如果需要接收数据,则会调用receive()接收。本文件传输系统成功的实现了服务器和客户端的文件传输,不论是较小范围内的局域网还是远程网,而且还可以传输多种格式的文件,如word,视频,图像等。相比其它文件传输工具而言,本系统有很多的优点。首先,界面简单,易于操作;其次,传输较大的文件时,不需要花费很长时间。关键词: 文件传输 ; WinSock ; socket编程; C/S结构 目 录1 绪论11.1选题背景11.2选题意义12开发环境及相关技术简介22.1开发
3、环境 Visual C+ 6.0介绍22.2基于vc的socket网络编程的基本原理22.2.1 socket的基本概念22.2.2Winsock网络编程原理32.3 TCP/IP协议简介32.5 C/S结构42.5.1 C/S结构的概念42.5.2 C/S结构的工作模式42.5.3 C/S结构的优点43 网络文件传输系统的设计53.1服务器端和客户端界面介绍53.1.1服务器界面介绍53.1.2客户端界面介绍63.2服务器端,客户端程序分析63.2.1服务器端分析63.2.2客户端分析114实现154.1系统运行环境154.2文件传输系统的测试154.2.1实验一:局域网内文件传输154.2
4、.2实验二:远程网络上文件传输184.2.3实验三:较大文件的传输194.2.4实验四:不同格式文件的传输204.3该文件传输系统的不足205结论21致谢22参考文献231 绪论 1.1选题背景21世纪被称为信息时代,因为计算机技术的迅猛发展,给人们的日常生活以及工作,都带来翻天覆地的变化。其中尤以互联网最为显著,人们通过Internet达到学习,娱乐,交流,工作等目的。通过internet获取更多,更及时有效的信息。特别是在工作或者学习上,需要经常进行文件传输,所以各种文件传输系统应运而生,比如邮件,各种聊天工具等。这些软件在使用上各有所长,各有特色。但是与此同时,这些文件传输工具自身的很多
5、缺点以及局限性依然给文件的传输带来了很多的不便。首先,对远程服务器的依赖导致了有些文件传输工具不能完全实现点对点的文件传输,甚至对文件的安全造成了威胁;其次,这些传输工具只能传输体积较小的文件,如果传输的文件体积过大的话,会耗费很长的时间,进而导致资源的浪费,网络速度不理想的情况下可能会导致传输中断。最后,电子邮件传输文件存在实时性的缺陷,把邮件传到另一个服务器上的用户的前提是先把邮件信息存到本地服务器上,这中间可能经过其它若干服务器,因此很难实现用户对文件的实时传送的要求。因此,急需开发一个功能简单,易于操作,可移植的文件传输工具,并且该文件传输工具可以在Windows平台下运行。1.2选题
6、意义通过对本次课题的研究,使我们更加清晰的了解到互联网技术的变革之快以及给我们的生活带来的巨大变化,更重要的是培养了我们科学的解决问题的能力,同时,使我们对文件传输系统的原理及设计有了正确的认识。文件传输系统是基于C/S模式的,以socket为中心实现服务器和客户端的通信。该系统结构简单,便于操作,且可以发送各种类型的文件,如word文档,图像,视频等。只需要输入服务器的ip地址或者主机名以及文件的保存路径,就可以发送文件。该文件传输系统给企业的文件的共享带来了很大的便捷。我们大学学到的很多理论知识在这次文件传输系统的设计中得到了很好的体现,如tcp/ip协议,Visual C +等。除此之外
7、,还接触到了socket套接字,以及对socket编程有了一定的了解。2开发环境及相关技术简介2.1开发环境 Visual C+ 6.0介绍Visual C +是一个可视化的软件开发工具,且其功能非常强大 。Visual C+主要由三个部分组成1。分别为:Developer Studio,MFC,Platform SDK。由于Visual C+其他版本存在局限性等问题,所以实际编程中更多的是以Visual C+6.0为平台。Visual C+6.0的组件包括编辑器、调试器以及程序向导AppWizard、类向导Class Wizard等开发工具。Developer Studio组件会把这些组件集
8、成为一个和谐的开发环境。Visual C+提供了基于CASE技术的可视化软件自动生成和维护工具Wizard Bar、 Class Wizard、Visual Studio、AppWizard等,实现了直观、可视的程序设计风格2,除此之外,Visual C+还封装了Windows的API函数、GDI、KERNEL、USER函数,使得原本编程时创建、维护窗口的许多复杂的工作变得更为简单。Visual C+应用程序的开发有两种模式2,WIN API和MFC。WIN API开发方式较为繁琐,而对于MFC而言,首先,MFC则是对WIN API的再次封装;其次,MFC减少了大量冗余代码的编写以及定义消息处
9、理所需的繁杂的代码段。所以MFC相对来说会给程序的编写带来很多方便。本课程设计就是在MFC中完成的。2.2基于vc的socket网络编程的基本原理2.2.1 socket的基本概念套接字(socket)是一种网络编程接口4,实际上就是一个通信端点,提供了发送和接收数据的机制。而Winsock是基于Windows操作系统下的网络编程接口,也就是基于 Socket 模型的API 。而最简单的一对一的CS结构的通信程序,就只有两个端点,即两个套接字(Socket),一个在Server端,另一个在Client端,这两个套接字就在CS间建立了双向数据传送的连接。每个套接字都有一个套接字地址,通常是IP和
10、端口的组合。套接字(Socket)最基本的分为两类4:流式套接字和数据报套接字。流式套接字:顺序的、无重复的、面向连接的可靠的传输机制,而且是双向数据传输,主要用于TCP的通信程序;数据报套接字:是面向无连接的传输机制,不保证顺序、无重复和可靠的双向数据传输,主要用于UDP的通信程序。本课程设计中使用的是流式套接字。2.2.2Winsock网络编程原理在服务器端利用socket函数创建服务器端套接字,然后调用bind()函数将套接字绑定到本地地址和端口上,再调用lisnten()函数,将套接字设置为监听模式,用于监听连接请求。调用accept()函数等待客户的连接到来并且接受客户的连接请求,当
11、accept函数接受客户端的连接请求时,会返回一个相对于新的套接字描述符,然后利用新的套接字就可以和客户端进行通信,原来的套接字继续监听其他的连接请求。利用send()函数向客户端发送数据,从客户端接受数据时,利用recvive()函数接收客户端发送的数据。当通信完成后,调用closesocket()关闭套接字。在客户端利用socket()函数创建客户端套接字,然后调用connect()函数向服务器端发送连接请求,建立连接之后,就可以利用recvive()函数接受服务器端的数据,客户端向服务器端发送数据可以调用send(),完成数据的发送或接收之后,调用closesocket()关闭套接字,释
12、放资源。2.3 TCP/IP协议简介TCP/IP协议是Transmission Control Protocol/Internet Protocol的简写7,即传输控制协议/因特网互联协议,是internet最基本的协议,tcp/ip协议是定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。该协议采用了4层的层级结构:网络接口层、网络层、传输层、应用层。每一层都利用它的下一层所提供的网络来完成自己的需求。网络接口层对应于原OSI结构中的物理层和数据链路层7,物理层是定义物理介质的各种特性,如:机械特性,功能特性等。而数据链路层是负责接收IP数据包并通过网络发送,或者从网络上接收物理
13、帧,抽出IP数据报,交给IP层。常见的网络接口层协议有:Ethernet 802.3、Token Ring 802.5、X.25、Frame relay、PPP 等。网络层负责相邻计算机之间的通信7。其功能包括:处理来自传输层的分组发送请求,处理输入数据报,流控拥塞等问题。常见的网络层协议有:ip协议、ICMP协议、IGMP协议、ARP协议。传输层提供应用程序之间的通信8。主要功能有:格式化信息流以及提供可靠传输。常见的传输层协议有:TCP协议、UDP协议。应用层是向用户提供一组常用的应用程序的,如电子邮件,远程登录等。一般是面向用户的服务。常见的应用层协议有:FTP协议、Telent协议、D
14、NS、SMTP协议、POP3协议。2.5 C/S结构2.5.1 C/S结构的概念C/S (Client/Server)结构7,它是一种软件系统体系结构,也就是客户机/服务器结构。它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。2.5.2 C/S结构的工作模式C/S 结构的基本原则是“功能分布”原则8,也就是将计算机应用任务分解成多个子任务,由多台计算机分工完成。客户端完成数据处理,数据表示以及用户接口功能;服务器端完成DBMS的核心功能。这种客户请求服务、服务器提供服务的处理方式是一种新型的计算机应用模式。2.5.3 C/S结构的优
15、点C/S结构的优点是客户端和服务器端都能够处理任务,所以很多工作可以在客户端处理之后再提交给服务器,这样不仅减轻了服务器的压力,而且使得客户端响应速度变得很快。现在已经普遍采用3层C/S结构,与传统的二层结构相比,三层C/S结构具有以下优点:首先,合理地划分三层结构的功能,从而使整个系统的逻辑结构更为清晰,能提高系统和软件的可维护性和可扩展性;其次,可以更灵活地选用相应的平台和硬件系统,应用的各层可以并行开发或者各自选择最适合的开发语言,使之能并行地而且是高效地进行开发。除此之外,允许充分利用功能层有效地隔离开表示层与数据层,未授权的用户难以绕过功能层而利用数据库工具或黑客手段去非法地访问数据
16、层,提高了系统的安全性。 3 网络文件传输系统的设计本设计是在Windows操作系统下,在Visual C+6.0开发环境中完成的,是基于TCP/IP协议的C/S模式,以socket为中心实现客户端与服务器的连接及文件传输,实现快速的文件资源共享且能够在很大程度上有效地提高工作效率。在此次设计中,文件传输是很重要的一部分,在服务器和客户端之间,客户端通过三次握手主动向服务器端发送socket套接字连接请求后,服务器对其进行响应且初始化临时内存空间,建立有效的连接,利用TCP/IP协议实现文件传输。 3.1服务器端和客户端界面介绍3.1.1服务器界面介绍本服务器负责从客户端接收文件并且把文件存盘
17、。当没有接收到客户端的请求连接时,连接状态和接收状态都是处于空闲的;如果监听到客户端的连接请求时,服务器则会对该请求作出响应,此时,连接状态则为已连接,当客户端传送文件时,接收状态则为文件正在存盘。完成文件的传送后,服务器把接收到的文件存盘到本地目录c盘中且文件名为example.doc,然后关闭连接,点击退出。设计的服务器界面如图3-1所示。把接收到的数据存为 连接状态:空闲接收状态:空闲c:example.doc退出 关闭当前连接 图3-1 服务器端界面设计3.1.2客户端界面介绍 客户端界面包括要连接的服务器的主机名或IP以及准备传送的文件等。首先,当客户端空闲状态时,连接状态和发送状态
18、也都是空闲的,当客户端要进行文件传输时,首先要知道服务器的主机名或者IP,然后连接服务器,此时,连接状态为已连接,输入需要传送文件的地址,发送文件即可。完成文件的发送之后,关闭与服务器的连接,点击退出按钮即可。设计的客户端界面如图3-2所示。服务器主机名或IP地址: 准备传送的文件是:连接状态:空闲接收状态:空闲Pcbeta-PC退出关闭连接发送文件连接服务器图3-2 客户端界面设计3.2服务器端,客户端程序分析3.2.1服务器端分析在服务器端的程序设计中,主要使用了基于TCP/IP协议的CListenSocket类,CAcceptSocket类,CReceiveSocket类。首先,服务器端
19、会利用socket函数创建服务器端套接字socket(),然后调用bind()函数将套接字绑定到本地地址和端口上,然后再调用lisnten()函数,将套接字设置为监听模式,用于监听客户端的连接请求。调用accept()函数等待客户的连接到来并且接受客户的连接请求,当accept函数接受客户端的连接请求时,会返回一个相对于新的套接字描述符,然后服务器端就可以利用新的套接字和客户端进行通信,原来的套接字继续监听其他的连接请求。服务器利用send()函数向客户端发送数据,当从客户端接受数据时,利用recvive()函数接收客户端发送的数据。当通信完成后,调用closesocket()关闭套接字。服务
20、器端流程图如图3-3所示。 服务器socket()建立流式套接字,返回sockets bind()将sockets与本地地址绑定 listen()通知TCP, 服务器准备好accept()接收数据,得到新的套接字S2等待客户数据recv( )/send( ), 在S2上读写数据 closesocket(), 关闭套接字S2closesocket(),关闭最初的套接字 图3-3 服务器端程序流程图当完成调用CSocket类创建套接字,并且已把创建的套接字和本地地址绑定后,就会调用ClistenSocket去监听来自客户端的连接请求,如果监听到客户端的请求,就会调用CAcceptSocket接受客
21、户端的请求,建立连接。详细程序如下所示。BOOL CServerDlg:OnInitDialog() CDialog:OnInitDialog();SetIcon(m_hIcon, TRUE);SetIcon(m_hIcon, FALSE); /程序启动时就创建用于监听的SOCKET m_socketListen.Create(2001); m_socketListen.Listen(1); GetDlgItem(IDC_RECV_FILE)-SetWindowText(c:example.doc);return TRUE; LRESULT CServerDlg:OnAccept(WPARAM
22、 wParam,LPARAM lParam) /处理用户定义消息的 WM_USER_ACCEPT。 /当系统调用CListenSocket:OnAccept时, /向主窗口发送此消息。 /在这里接收连接请求。 if (m_socketListen.Accept(m_socketAccept) GetDlgItem(IDC_CLOSE)-EnableWindow(TRUE); GetDlgItem(IDC_LINK_STATUS)-SetWindowText(已连接); return 0;服务器端与客户端建立连接之后,如果客户端发送文件,服务器端就会调用CReceiveSocket来接收发送来的
23、文件数据。如下所示:void CTransferSocket_hawk:OnReceive(int nErrorCode) if (m_hSocket != INVALID_SOCKET) /当连续接收大量数据时,程序可能会不响应用户的输入, /因此需要加入消息循环代码. MSG msg; if (:PeekMessage(&msg,NULL,0,0,PM_REMOVE) :TranslateMessage(&msg); :DispatchMessage(&msg); if (!m_bGetRecvSize) /如果还没有接收完尺寸,继续接收尺寸数据 int result=Receive( (
24、BYTE *)&m_nRecvSize+m_nRecvSizeOffset, sizeof(long)-m_nRecvSizeOffset); if (result = SOCKET_ERROR) int err=GetLastError(); /如果不是阻塞引起的错误,则调用OnTransferClose, /并关闭连接. if (err != WSAEWOULDBLOCK) Close(); OnTransferClose(RECVERR,err); else m_nRecvSizeOffset+=result; /如果已收到数据块尺寸,准备接收起始标记 if (m_nRecvSizeOf
25、fset = sizeof(long) m_bGetRecvSize=TRUE; m_nRecvSizeOffset=0; else /已经得到数据块尺寸 if (!m_bGetRecvBeginTag) /如果没有接收完起始标记,继续接收 int result=Receive( m_szRecvBeginTag+m_nRecvBeginTagOffset, BEGIN_TAG_LENGTH-m_nRecvBeginTagOffset); if (result = SOCKET_ERROR) int err=GetLastError(); /如果不是阻塞引起的错误,则调用OnTransferC
26、lose, /并关闭连接. if (err != WSAEWOULDBLOCK) Close(); OnTransferClose(RECVERR,err); else m_nRecvBeginTagOffset+=result; if (m_nRecvBeginTagOffset = BEGIN_TAG_LENGTH) /如果已收到起始标记,判断是否正确. if (memcmp(m_szRecvBeginTag,BEGIN_TAG,BEGIN_TAG_LENGTH) = 0) /如果起始标记正确,分配接收缓存区, /准备接收数据正文 m_bGetRecvBeginTag=TRUE; m_nR
27、ecvBeginTagOffset=0; m_pRecvBuf=new BYTE m_nRecvSize; m_nRecvOffset=0; else /如果起始标记错误,调用OnTransferClose, /并关闭连接 Close(); OnTransferClose(RECVBEGINTAGERR); else /数据块尺寸和起始标记都已正确接收,开始接收数据正文 int nBufLen=min(4096,m_nRecvSize-m_nRecvOffset); int result=Receive(m_pRecvBuf+m_nRecvOffset,nBufLen); if (result
28、 = SOCKET_ERROR) int err=GetLastError(); /如果不是阻塞引起的错误,则调用OnTransferClose if (err != WSAEWOULDBLOCK) Close(); OnTransferClose(RECVERR,err); else m_nRecvOffset+=result; /如果数据块接收完毕,则准备接收下一个数据块 if (m_nRecvOffset = m_nRecvSize) /调用虚函数OnOneDataReceived. OnOneDataReceived(m_pRecvBuf,m_nRecvSize); m_bGetRec
29、vSize=FALSE; m_bGetRecvBeginTag=FALSE; delete m_pRecvBuf; m_pRecvBuf=NULL; CAsyncSocket:OnReceive(nErrorCode);void CTransferSocket_hawk:OnOneDataSent()void CTransferSocket_hawk:OnOneDataReceived(void *pData,long size)3.2.2客户端分析 在客户端程序的设计中,主要主要使用了基于TCP/IP协议的CConnectSocket类,CSendSocket类。首先客户端利用socket(
30、)函数创建客户端套接字,然后调用connect()函数向服务器端发送连接请求,服务器端响应连接请求,建立连接之后,客户端就可以调用recvive()函数接受服务器端的数据,客户端向服务器端发送数据可以调用send()函数,完成数据的发送或接收之后,调用closesocket()关闭套接字,释放资源。客户端流程图如图图3-4所示。 客户端 连接 建立 socket()建立流式套接字,返回socketsconnect(),将套接字与服务器连接recv( )/send( ),在套接字上读写数据closesocket(),关闭套接字,结束TCP对话图3-4 客户端程序流程图当客户端需要向服务器端传输数
31、据时,客户端会首先向服务器端发送连接请求。客户端调用CConnectSocket类向服务器端发送连接请求的详细程序如下所示。void CClientDlg:OnConnect() /连接服务器 CString strServerName; GetDlgItem(IDC_SERVERNAME)-GetWindowText(strServerName); m_socketClient.Create(); if (!m_socketClient.Connect(strServerName,2001) if (m_socketClient.GetLastError() != WSAEWOULDBLOC
32、K) MessageBox(服务器连不上。,错误,MB_OK | MB_ICONEXCLAMATION); m_socketClient.Close(); return; GetDlgItem(IDC_CONNECT)-EnableWindow(FALSE);LRESULT CClientDlg:OnConnected(WPARAM wParam,LPARAM lParam) int nErrorCode=wParam; if (nErrorCode) /连接失败 MessageBox(服务器连不上。,错误,MB_OK | MB_ICONEXCLAMATION); m_socketClient
33、.Close(); GetDlgItem(IDC_CONNECT)-EnableWindow(TRUE); else /连接成功 GetDlgItem(IDC_SEND)-EnableWindow(TRUE); GetDlgItem(IDC_CLOSE)-EnableWindow(TRUE); GetDlgItem(IDC_LINK_STATUS)-SetWindowText(已连接); return 0;当客户端与服务器端建立连接之后,客户端可以调用CsendSocket向服务器端传输文件。如下所示:void CTransferSocket_hawk:OnSend(int nErrorCod
34、e) if (m_hSocket != INVALID_SOCKET) /当连续发送大量数据时,程序可能会不响应用户的输入, /因此需要加入消息循环代码. MSG msg; if (:PeekMessage(&msg,NULL,0,0,PM_REMOVE) :TranslateMessage(&msg); :DispatchMessage(&msg); /如果数据队列不为空,则发送数据 if (m_DataQueue.GetSize() int nBufLen=min(4096,m_DataSizes0-m_nSendOffset); int result=Send(m_DataQueue0+
35、m_nSendOffset,nBufLen); if (result = SOCKET_ERROR) int err=GetLastError(); /如果不是阻塞引起的错误,则关闭SOCKET, /并调用OnTransferClose. /如果是阻塞引起的错误,系统会自己产生一次FD_WRITE事件, /可以继续发送数据. if (err != WSAEWOULDBLOCK) Close(); OnTransferClose(SENDERR,err); else m_nSendOffset+=result; /判断该数据块是否已传送完毕 if (m_nSendOffset = m_DataS
36、izes0) /传送完毕,则删除该数据块 delete m_DataQueue0; m_DataQueue.RemoveAt(0); m_DataSizes.RemoveAt(0); m_nSendOffset=0; /如果队列非空,提名FD_WRITE,准备传送下一个数据 if (m_DataQueue.GetSize() /之所以没用缺省参数,是因为如果又提名 /FD_CONNECT,会导致OnConnect被再次调用. AsyncSelect(FD_READ | FD_WRITE | FD_CLOSE); /调用虚函数OnOneDataSent,用户可根据需要重载该函数. /注意:用户有可能在OnOneDataSent()的执行过程中,