这个是我做的另一个毕业设计,使用的最基本的socket通信实现文件传输,使用该程序可同时给多个人发送不同的文件并支持断点续传。
由于只为毕业设计而写的,只实现了功能,没有在速度上优化,发上来只为给要做类似程序的朋友做个参考。以下是毕业论文中的节选。
源代码已经上传了。
第3章 设计文档
3.1 任务概述
本项任务要开发一款P2P文件传输软件,该软件可以在局域网和互连上使用,具有文件传输,断点续传,多线程连接等功能。
3.1.1 开发背景
随着网络的普及,在网络中交换信息,特别是文件成为使用网络时经常性工作,现有的文件传输工具如QQ、MSN等,主要功能是作为即时通讯工具使用,而且在局域网中使用时仍需连接到互连网。本文开发的软件基于P2P结构实现文件传输,不依靠服务器维护用户资料,所以,只要两台机器可以连通,就可以进行文件传输。
3.1.2 定义
P2P:即peer-to-peer,可以理解为点对点,或对等传输的意思。
3.1.3 开发工具
开发采用JAVA语言,开发工具为jbuilder 2005
3.2 文件传输业务流程
3.2.1 通讯顺序关系
在文件传输时,传输过程可以简单的分为4步,如图 :
1、 由文件发送者发起文件传输,他首先发送文件名,文件大小,验证消息等信息给文件接收者。
2、 文件接收者收到文件信息和发送者信息,决定是否接收,如不接收,则发送拒绝接收消息给文件发送者,文件传输结束。如同意接收,发送同意接收消息和文件传输起始字节(用于断点续传)给发送者。
3、 发送者开始发送文件数据包,直到最后一个文件数据包,发送者在最后一个文件数据包上加上标记。
4、 接收者识别最后一个文件数据包,文件传输成功。
3.2.2 文件发送端的详细业务流程
图中显示了发送文件时的详细业务流程。
1、首先,要选择要发送的文件,可以选择一个文件或同一个文件夹下的多个文件发送,当选择多个文件时,软件将为每个文件建立一个传输线程。选择文件后,发送者要填写目标IP和端口,由于是基于P2P的文件传输,不存在服务器维护好友列表,所以需要知道目标IP才能传输文件,用户也可以填写验证消息用于描述自己的身份和文件的介绍。
2、发送者收到确认包,判断是否接收文件,如果不接收,则传输结束,如果接收,则打开要发送的文件,设置起始传输字节。
3、发送者读取一个文件片段,加入是否是最后一个文件片段的标志,打包发给接收者。
4、发送完最后一个文件片段时,文件传输结束。
3.2.3 文件接收者详细业务流程
1、 从一打开软件开始,监听线程就会启动,这时用户就扮演者文件接收者的角色,当有文件发送者发来请求时,监听线程建立与发送者的连接,并创建传输线程,接收者得到发送者传来的文件信息和验证消息,用户还能得到发送者的IP地址,用户根据这些信息决定是否接收文件,并发送确认包给发送者,确认包里包含了是否接收的标志,和开始传输的起始字节。
2、 接收者开始接收数据包,解包,写入文件中,直到最后一个文件片段,关闭文件,文件传输结束。
3.3 软件的传输协议设计
为了更好的控制文件传输的流程,开发设计了文件传输协议,该协议属于应用层协议,为了不使传输过程过于复杂,协议尽量简单设计。主要的内容是传输包的格式。
3.3.1 传输包的格式
其中传输包的类型有3种:
PACKAGE_TYPE_FILENAME = 0x01
文件名包,该包用于文件发送者向接收者发送文件信息和验证消息
PACKAGE_TYPE_CONTEXT = 0x02
文件内容包,该包用于传输文件内容
PACKAGE_TYPE_CONFIRMRECEIVE = 0x03
文件传输确认包,该包用于文件接收者向发送者确认文件的接收
3.3.2 文件名包格式
3.3.3 文件传输包格式
其中MORE_DATA用于确认是否是文件片段的最后一个包,以结束文件的传输。0表示没有数据,1表示还有数据。
3. 3.4 文件接受确认包格式
其中CONFIRM_FLAG表示是否确认文件接收,1表示接收,0表示不接收,当接收时,OFF表示文件传输的起始字节,用来实现断点续传。
3.4 文件传输的状态机
文件传输线程的地层其实就是一个状态转换机,线程具有几个状态,线程从文件名传输状态开始,根据文件传输的不同情况,在各个状态间转换,直到传输成功或传输失败。
FILE_TRANS_STATUS_FILENAME = 0x01
文件名称传输状态,文件传输刚建立连接时,传输状态处于文件名传输状态
FILE_TRANS_STATUS_CONTEXT = 0x02
文件内容传输转台,在传输文件内容时,传输状态属于该状态
FILE_TRANS_STATUS_WAITFORCONFIRM = 0x03
等待确认状态,文件发送者,发送完文件信息和验证消息后,处于该状态,文件接收者接收到文件信息和验证消息后处于该状态
FILE_TRANS_STATUS_SUCCESS = 0x04
文件传输成功状态,表示文件已成功传输完毕
FILE_TRANS_STATUS_FAIL = 0x05
文件传输失败状态,表示文件传输已经失败,失败原因可能是对方取消了文件传输或网络错误
3.5 结构设计
软件主要由MainFrame,TransFileManager,SocketThread,Server,TransFilePanel五个类构成图 表示了这几个类之间的关系。
类功能介绍
MainFrame
MainFrame类是软件的主界面类,负责与用户的交互。
TransFileManager
TransFileManager类是文件传输的控制类,负责管理文件的传输,它维护一个传输线程(SocketThread)的列表,每个线程都表示一个正在传输的任务。TransFileManager类定时的扫描各个线程的状态,根据不同状态做出不同处理,如计算传输速度,显示传输进度等。
SocketThread
SocketThread类是文件传输线程类,它负责底层的具体传输工作,包括打包与解包,并且转换自己的状态,完成文件的传输。
Server
Server类是为TransFileManager类使用的,它负责本地端口的监听,一旦有用户连接,TransFileManager就创建一个传输线程,放入线程列表。而它继续监听端口。
TransFilePanel
TransFilePanel是一个面板,它用有按钮、进度条,标签等用来显示文件的传输状态。
3.6 类设计
3.6.1 MainFrame
该类是视图类,是软件的主界面。
成员变量:
contentPane
contentPane是JPanel类的对象,是主界面的面板。
jbtnSend
jbtnSend是JButton类对象,点击它将打开文件传输对话框。
jbtnSetting
jbtnSetting是JButton类对象,点击它将打开设置对话框。
jlblIP
jlblIP是JLable类对象,它显示本机的IP地址,方便文件的传输。
jtpTransFile
jtpTransFile是JTabbedPane类对象,是选项卡控件,用于显示多个文件同时传输。
tfm
tfm是TransFileManager类对象,负责控制文件的传输。
成员方法:
jbtnSend_anctionPerformed()
该方法为jbtnSend按钮的点击事件
jbtnSetting_actionPerformed()
该方法为jbtnSetting按钮的点击事件
This_windowClosed()
该方法为窗口关闭事件
界面截图:
3.6.2 TransFileManager类
该类负责传输过程中对各个传输线程的检测与控制。
成员变量:
jtp
jtp是选项卡控件对象,是MainFrame类中jtpTransFile对象的一个引用。
panelist
paneList是ArrayList类对象,它是一个链表结构,存储使用的选项卡。
running
running是一个boolean型的对象,用来表示线程是否在执行。
s
s是Server类的对象,用来坚听本地端口,等待用户的连接。
threadList
threadList是ArrayList类对象,它是一个链表结构,存储使用的传输线程。
maxThreadNum
maxTreadNum是整型变量,表示最大允许的线程数。
port
port是整型变量,表示使用的端口号。
成员方法:
close()
该方法用于关闭文件传输的控制。
sendFile()
该方法用于建立一个文件发送,要求输入参数为,目标IP,端口号,要发送的文件和验证消息。
3.6.3 SocketThread类
SocketThread类是文件传输的底层支持类,它提供文件传输的服务。
成员变量:
sendBuf
sendBuf是一个字节型数据,它是文件发送和接收时的缓冲区。
dis
dis是DataInputStream型对象,它是由Socket对象得到的输入流。
dos
dos是DataOutputStream型对象,它是由Socket对象得到的输出流。
fDis
fDis是DataInputStream型对象,它是要发送的文件的输入流。
raf
raf是RandomAccessFile型对象,该对象允许设置输出流的位置,以支持断点续传。
running
running是一个boolean型对象,用来表示线程是否在执行。
serverName
serverName是字符串型对象,表示服务器的地址,该对象只有在线程作为发送者时使用。
errorMessage
errorMessage是字符串型对象,表示出错的信息。
fileName
fileName是字符串型对象,表示文件名。
fileSender
fileSender是一个boolean型对象,表示是文件发送者还是文件接收者。
IP
IP是字符串型对象,用来存放目的机的IP地址。
message
message是字符串型对象,用来表示验证信息。
port
port是整型变量,表示连接的端口号。
transFileLength
transFileLength是长整型变量,表示已经传输的文件长度,TransFileManager可以用它来计算传输进度和传输速度
成员方法:
cancelTrans()
该方法用于取消传输
confirmReceiveFile()
该方法用于确认传输文件
参数flag用来表示是否同意传输
参数fileName用来表示保存的文件
参数off用来表示起始传输字节
getFileTransMessage()
该方法用于得到文件传输的基本信息。如:文件发送的目标,端口号等。
getStatus()
该方法返回一个整型变量,表示当前线程的传输状态。
run()
线程的执行方法,该方法中循环执行发送或接收方法,完成文件的发送或接收。
setError()
该方法通过一个字符串型的参数设置错误信息。
stopThread()
该方法用于停止线程的执行。
doPackage()
打包方法,该方法中,根据线程所处的不同状态,对数据加入不同的包头和其他信息,进行打包。
readFromFile()
该方法从要发送的文件中读取一个整型数。
readFromSocket()
该方法从Socket输入流读取一个整型数,它有一个重载版本读取一个字节数组的数据。
receiveFile()
该方法为接收文件的方法,是线程循环中,文件接收者执行的方法。
sendFile()
该方法为发送文件方法,是线程循环中,文件发送者执行的方法。
writeToFile()
该方法写一个字节数据的数据到保存的文件中。
writeToSocket()
该方法写数据到Socket输出流中,它有三个重载版本,分别是写入整型数,写入长整型数和写入字节数组数据。
3.6.3 TransFilePanel类
这个类属于视图类,它作为主界面上的一个对象使用,含有进度条,按钮,标签的对象,用于显示文件传输状态,每一个传输线程都拥有自己的TransFilePanel类对象。
成员变量:
fileName
fileName是一个字符串,表示传输文件的文件名。
fileSender
fileSender是boolean型对象,表示是文件发送者还是接收者。
isCanneled
isCanneled是boolean型对象,传输过程中,用户点了取消按钮后,该标志位置true,TransFileManager会循环检测isCanneled标志,当发现其为true后,它会关闭对应的传输线程,而对方会检测到传输错误,文件传输就停止了。
isClosed
isClosed是boolean型对象,它表示当传输过程已经停止时,用户点击了关闭按钮,TransFileManager检测到isClosed为true时,会在列表中去掉对应的传输线程和TransFilePanel。
isConfirm
isConfirm是boolean型对象,它表示文件接收者是否确认了文件接收后(包括同意和拒绝),TransFileManager检测到isConfirm为true时,会将确认状态发给文件发送者。
jbtnCannel
jbtnCannel是取消按钮。
jbtnOK
jbtnOK是确定按钮。
jpgbFileTrans
jpgbFileTrans是文件传输的进度条。
jtaFileTransStatus
jtaFileTransStatus是一个文本框,用来显示文件传输的状态。
transFileLength
transFileLength是一个长整型变量,用来表示已经传输的文件长度。
status
status是一个整行变量,表示文件传输的状态,所表示的状态与SocketThread中的对应。
界面截图
3.7 算法介绍
3.7.1 TransFileManager的循环监听
TransFileManger类继承了Thread类,实现多线程,避免循环监听时产生阻塞。
以下是在每次循环中做的工作
1、 得到传输线程(SocketThread)的数量和传输状态显示面板(TransFilePanel)的数量。
2、 从传输线程列表中依次取出一个传输线程。
3、 判断当前取出的线程是否有对应的传输状态显示面板,如果有执行5,如果没有执行4。
4、 增加传输状态显示面板,并且在主界面上增加选项卡。
5、 判断传输线程的状态,根据不同的状态做不同的处理。
6、 如果列表中的线程取完,执行1,没有取完执行2。
程序流程图:
3.7.2 断点续传
软件支持断点续传的功能,该功能主要算法如下:
1、 当文件传输中途停止时,文件接收者程序为接收的文件创建一个以该文件名+.tmp为文件名的临时文件,临时文件与该文件保存在同一个路径下。临时文件中保存文件以传输的字节数和文件的大小。
2、 当文件接收者接收该文件时,如果有对应的临时文件,程序会检测到该文件的临时文件,读取以传输的字节数和文件大小,并且比较传输的字节数与未完成传输的文件大小是否相等,文件大小和发送来的文件大小是否相等,如果都相等,执行3,否则执行4。
3、 将文件的输出流设置到应该继续传输的位置,并将起始传输字节数发给发送者,执行5。
4、 如果有未完成的传输文件,将其删除,执行5。
5、 进行文件传输。
程序流程图:
、
3.7.3 本地端口的多连接监听
Server类对象负责本地端口的监听,可以支持多了连接,具体数量由用户设置。
1、 用用户设置的端口创建监听,等待连接。
2、 当有用户连接上后,用该连接创建一个SocketThread传输线程,加入到线程列表。
3、 如果停止监听则退出,否则执行1。
程序流程图:
类设计
软件中起关键作用的类为CharCollector字符收集器类、SimpleDOMParser解析内核类和Manager中间层控制类。
字符收集器类
字符收集器类是软件的底层类,它与XML文件交互,读取文件中的字符信息,经过处理,形成标签,文本,属性等数据为上层提供服务。
成员变量介绍:
aReader:Reader对象,输入流为文件输入流,它将XML文件的字符数据以流的形式提供给类。
成员方法介绍:
comparedArray(): 输入:两个int型数组,输出:boolean型,返回两个数组是否相同。用于比较两个数组是否相同,在方法中,对两个数组的数逐一比较。
hasMoreChars(): 输入:无,输出:boolean型,表示文件中是否还有字符,该方法用于确定文件中是否还有字符。
peek(): 输入:int型数组。用于存储窥探到的字符,输出:无,该方法用于从文件中窥探数组长度的字符数据。窥探就是从文件中读取数据后,将输入流标记回以前的位置,以免丢失数据。
算法简述:
在aReader上标记数组的长度
aReader.mark(buffer.length);
2、将数据依次读入数组
for (int i = 0; i < buffer.length; i++) {
buffer[i] = aReader.read();
}
恢复aReader
aReader.reset();
peek(): 输入:无,输出:int型,是窥探到的字符,该方法从文件中窥探一个字符数据。
processCDATA(): 输入:StringBuffer型,用于存放CDATA数据,输出:无,用于处理XML文件中的CDATA节点。得到的CDATA节点存放到StringBuffer对象中。
算法简述:
预设两个数组
int[] cdataHead = {'<', '!', '[', 'C', 'D', 'A', 'T', 'A', '['};
int[] cdataTail = {']', ']', '>'};
窥探cdataHead长度的字符存在数组buf中
peek(buf);
比较buf与cdataHead数组的内容是否相同,如果不同返回falase,如果相同继续执行。
将aReader跳过cdataHead的长度,然后循环读取直到窥探的字符数组与cdataTail相同,将中间的字符存放到StringBuffer对象中。
while (true) {
peek(buf);
if (comparedArray(buf, cdataTail)) {
aReader.skip(cdataTail.length);
flag = true;
break;
} else {
bufChar = aReader.read();
if (bufChar == -1) {
throw new IOException("CDATA node without end tag");
}
sb.append((char) bufChar);
}
}
processEntity(): 输入:StringBuffer型,用于存放实体数据,输出:无,用语处理XML文件中的内建实体类型。得到的实体存放到StringBuffer对象中。
readTag(): 输入:无,输出:String型,是读到的标签,从XML文件中读取并整理成一个标签数据存放在String对象中。
算法简述:
窥探一个字符,如果不是’<’,抛出异常。否则继续执行。
int nextChar = peek();
if (nextChar != '<') {
throw new IOException("expect '<',but got '" + (char) nextChar +"'");
}
窥探一个字符aChar
int aChar = peek();
如果字符为’<’,进行CDATA节点处理
if (aChar == '<') {
if (processCDATA(sb)) {
aChar = peek();
continue;
}
}
如果字符为’&’,进行内建实体处理
if (aChar == '&') {
if (processEntity(sb)) {
aChar = peek();
continue;
}
}
如果字符为’>’或文件结束,返回StringBuffer对象。
如果是其它字符,加入到StringBuffer对象中。
readText(): 输入:无,输出:String型,是读到的文本内容,从XML文件中读取并整理成一个文本数据存放在Sting对象中。
skipOtherTag(): 输入:无,输出:无,跳过一个不处理的标签。
skipOtherTags(): 输入:无,输出:无,跳过多个不处理的标签。
skipWhitespace():输入:无,输出:无,跳过空格。
解析内核类
该类用于解析XML文件,包含parser()方法,是该类的核心方法。
成员变量介绍:
aCharCollector:CharCollector类对象,负责字符收集。
currentNode: SimpleElement类对象,表示当前处理的元素。
Elements: Stack类对象,用栈的存储结构,表示元素之间的层次关系。
成员方法介绍:
Parser(): 输入:无,输出:SimpleDocument类对象,是返回的整个文档树型结构。
算法简述:
1、读取一个标签
2、判断是开始标签还是结束标签,如果是结束标签,转入结束标签处理3
如果是开始标签,转入开始标签处理6
3、得到标签的名称,判断是否与currentNode的名称相同,如果相同,执行
4,否则抛出异常
4、判断是否还有标签,如果有继续执行5,否则退出。
5、从栈中弹出一个节点给currentNode,执行1
6、得到标签的名称,属性,文本内容,将标签加入到DOM树中,并且压栈。
执行1
算法的程序流程图
中间层管理类
中间层管理类用于控制和协调解析器与用户界面。
成员变量介绍:
isParsered: boolean型,判断是否已经解析过了。
document: SimpleDocument型对象,表示整个XML文档树,也是解析器返回的。
parser: SimpleDOMParser型对象,表示解析器。
成员方法介绍:
getTreeRoot(): 输入:无,输出:DefaultMutableTreeNode型对象,是得到的树的根节点,用于得到树的根节点。
parser(): 输入:无,输出:无,用于执行解析操作。
setInputStream(): 输入:InputStream型对象,是要设置的输入流,输出:无,用于设置解析源。
setXMLFile(): 输入:File型对象,是要设置的XML文件,输出:无,用于设置XML文件源。
getTreeNode(): 输入:SimpleElement型对象,是要转换的对象,输出:DefaultMutableTreeNode型对象,是转换后的对象,用
于转换相应元素为树的节点对象。
结构设计
各个类的功能介绍
Node、Element、Document、Attr为W3C的DOM规范要求实现的接口。还有其它接口未列出。
SimpleNode 继承Node接口,实现Node接口的基本功能。
SimpleElement继承Node和Element接口,对应DOM树中的标准节点,也就是XML文件中的一个标签。
SimpleDocument 继承Node和Document接口,对应整个DOM树,它是解析器解析完成后,返回的对象。用户可以使用该对象访问整个DOM树。
SimpleAttr 继承Node和Attr接口,对应DOM树中的属性节点,用于存放属性名称和属性值。
CharCollector 字符收集器类,它直接与XML文件接触,根据需要向调用者提供下一个标签或文本等数据。
SimpleDOMParser 解析器的核心类,它使用CharCollector类,得到标签、文本等数据。使用SimpleElement、SimpleAttr等类存放数据,生成SimpleDocumnet对象返回给调用者。
MainFrame 用户界面类,它将解析结果展示给用户,并且根据用户的操作,与用户进行交互。
Manager 中间层管理类,它协调用户界面(MainFrame)与解析器内核(SimpleDOMParser)之间的关系,实现解析器的控制。
内部接口设计
类名称
|
调用类
|
调用功能
|
SimpleDOMParser
|
SimpleElement
|
节点的存储,查找,删除。
|
SimpleDOMParser
|
SimpleAttr
|
属性节点的存储,查找,删除。
|
SimpleDOMParser
|
SimpleDocument
|
生成DOM树
|
SimpleDomParser
|
CharCollector
|
字符收集,得到标签与文本等。
|
Manager
|
SimpleDOMParser
|
解析XML文档
|
Manager
|
MainFrame
|
用户界面的相关操作
|
用例关系设计
用例介绍
用户: 参与者, 即软件的使用者。
XML文件: 参与者,XML文件。
链接的文件: 参与者,XML文件中链接的外部文件。
选择XML文件: 用户通过文件选择框选择一个本地的XML文件。
选择URL地址: 用户输入一个有效的URL,指向网络上的XML格式的网页。
解析XML文件: 用户确定对XML文件进行解析,该项操作要发生在选择XML文件或选择URL地址之后。
修改文件: 用户对XML文件进行修改
保存文件: 用户通过文件选择框,选择位置保存XML文件。
选择DOM树节点:用户选择解析后的DOM树型结构上的节点。
解析器解析: 解析器对XML进行解析
收集字符: 字符收集器负责处理XML的字符数据
界面展示: 通过树型结构和图表向用户展示数据。
字符收集器有限状态机
DFA M=({S,Ts,Tp,Tq,Cp,Ep,Er,Q},{LT,RT,A,C,EC,,EOF,NL,NLRA},f,S,{Q})
其中 字符集:
LT={<}
RT={/>}
A={&,<,>,&apos,&qout}
C={<!CDATA[[}
EC={]]>}
EOF代表文件结束
NL代表不是<的字符
NLRA 代表除去LT,RT,LTL,A,C的所有字符
状态:
S: 开始状态
Ts:标签处理开始状态
Tp:标签处理状态
Tq:标签处理结束状态
Cp:CDATA节点处理状态
Ep:内建实体处理状态
Er:错误状态
Q:结束状态
状态转换函数:
f(S,LT)=Ts
f(S,NL)=Er
f(Tp,RT)=Tq
f(Tp,A)=Cp
f(Tp,C)=Ep
f(Ep,EC)=Tp
f(Ep,EOF)=Er
f(Tq,LT)=Ts
f(Tq,EOF)=Q
f(Tq,NLRA)=Tq