这周是学习,设计一个网络文件传输程序
总述
文件传输协议规定(RFC 959),网络文件传输中用两个TCP端口来实现:
一个端口(21号)用来对话,传递控制信息,总是开启;
一个端口(20号)实现文件数据传递服务,有数据传输服务时开启。
网络对话和网络文件传输,使用TCP的socket编程,本质还是一样,对话过程,使用字符流来包装;而网络文件传输过程,则应该使用字节流来进行处理。
再说一遍,两者本质一样!
都是客户端向服务器发送请求,服务器判断,当服务器接收到的是下载文件的请求的时候就会调用方法吧文件(可封装成其他类型)写入网络流中,然后客户端就接收这个文件。本质还是一样的。
设计第一步:创建窗体界面
如图所示,与前几周的窗体大致相似。增加了下载按钮
下载按钮的功能:
btnDownload.setOnAction(event -> { if(tfSend.getText().equals("")) //没有输入文件名则返回 return; String fName = tfSend.getText().trim(); tfSend.clear(); FileChooser fileChooser = new FileChooser(); fileChooser.setInitialFileName(fName); File saveFile = fileChooser.showSaveDialog(null); if (saveFile == null) { return;//用户放弃操作则返回 } try { //数据端口是2020 new FileDataClient(ip, "2020").getFile(saveFile); Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setContentText(saveFile.getName() + " 下载完毕!"); alert.showAndWait(); //通知服务器已经完成了下载动作,不发送的话,服务器不能提供有效反馈信息 fileDialogClient.send("客户端开启下载"); } catch (IOException e) { e.printStackTrace(); } });
还有一种锦上添花的功能,实现鼠标拖拽就能复制内容到信息输入框:
//信息显示区鼠标拖动高亮文字直接复制到信息输入框,方便选择文件名 //taDispaly为信息选择区的TextArea,tfSend为信息输入区的TextField //为taDisplay的选择范围属性添加监听器,当该属性值变化(选择文字时),会触发监听器中的代码 taDisplay.selectionProperty().addListener((observable, oldValue, newValue) -> { //只有当鼠标拖动选中了文字才复制内容 if(!taDisplay.getSelectedText().equals("")) tfSend.setText(taDisplay.getSelectedText()); });
设计第二步,创建客户端数据传输程序
我们应该想到这个程序应该有的功能:连接服务器数据端口、发送文件名、保存下载的文件,文件传输完成后关闭数据连接。
我们可以在构造方法中向服务器发起连接
public FileDataClient(String ip,String port) throws IOException{ dataSocket = new Socket(ip, Integer.parseInt(port));//服务器发起连接 }
剩下的功能我们可以封装成一个getFile方法:
public void getFile(File saveFile) throws IOException { if (dataSocket != null) { // dataSocket是Socket类型的成员变量 FileOutputStream fileOut = new FileOutputStream(saveFile);//新建本地空文件 byte[] buf = new byte[1024]; // 用来缓存接收的字节数据 //网络字节输入流 InputStream socketIn = dataSocket.getInputStream(); //网络字节输出流 OutputStream socketOut = dataSocket.getOutputStream(); //(2)向服务器发送请求的文件名,字符串读写功能 PrintWriter pw = new PrintWriter(new OutputStreamWriter(socketOut, "utf-8"), true); pw.println(saveFile.getName()); //(3)接收服务器的数据文件,字节读写功能 int size = 0; while ((size = socketIn.read(buf)) != -1) {//读一块到缓存,读取结束返回-1 fileOut.write(buf, 0, size); //写一块到文件 } fileOut.flush();//关闭前将缓存的数据全部推出 //文件传输完毕,关闭流 fileOut.close(); if (dataSocket != null) { dataSocket.close(); } } else { System.err.println("连接ftp数据服务器失败"); } }
众所周知,下载文件是一个交互的过程。
所以我们需要在一些关键的时间点提醒用户,或者说提示。如:文件开始下载时,完成下载时:
这里就是创建了一个对话框用来提示用户:
new FileDataClient(ip, "2020").getFile(saveFile);//数据端口为2020 Alert alert = new Alert(Alert.AlertType.INFORMATION); alert.setContentText(saveFile.getName() + " 下载完毕!"); alert.showAndWait(); //通知服务器已经完成了下载动作,不发送的话,服务器不能提供有效反馈信息 fileDialogClient.send("客户端开启下载");
这样我们的一个简易的网络文件传输程序就设计完成了。
实验中注意的点是在 把ip,port这两个成员变量使用的时候,不能将他们重新在连接按钮那里重新定义,不然ip和port的值就是null了。
错误示范:
正确如下:
或者直接把获得ip和port的方法放在类里面而非方法里。
本次的所有代码都放在:https://wws.lanzous.com/ivMzch0al7a