JavaFX实现网络文件传输程序设计(互联网程序设计课程 第4讲)


教学与实践目的:学会基本的文件传输FTP程序设计技术

一、介绍

程序设计知识点:字节流(网络字节流和文件字节流)的读写技术。

前两讲我们学会了使用TCP套接字(Socket),能实现字符串的发送和接收功能,简单地做到了客户机和服务器的对话。

这次课,我们进一步学习TCP套接字,利用它的字节传输技术,实现网络文件传输。

文件传输协议规定(RFC 959),网络文件传输中用两个TCP端口来实现:

  • 一个端口(21号)用来对话,传递控制信息,总是开启;
  • 一个端口(20号)实现文件数据传递服务,有数据传输服务时开启。

在这里插入图片描述
网络对话和网络文件传输,使用TCP的socket编程,本质还是一样,对话过程,使用字符流来包装;而网络文件传输过程,则应该使用字节流来进行处理。

本讲实现一个简单的远程文件传输系统,实现基本的下载功能。

我们用2021端口实现对话服务,如身份验证、文件列表信息浏览等,用2020端口传递数据文件(即文件下载)。

基于C/S的主要程序结构如下:
在这里插入图片描述
客户端程序:

  • 主界面客户端程序FileClientFX.java;
  • 文件对话客户端程序(控制进程)FileDialogClient.java;
  • 文件数据客户端程序(数据传输进程)FileDataClient.java。

服务端程序:

  • 文件对话服务器程序FileDialogServer.java,开启2021端口;
    主要功能:身份验证、文件目录传送。
  • 文件数据服务器程序FileDataServer.java,开启2020端口。
    主要功能:传送文件名,接收文件。

二、程序设计第一步:创建客户端远程文件对话程序

该客户端程序主要功能:发送用户信息、实现和文件服务器的基本对话,文件浏览和下载。

注意:客户端发送信息后,能够看到什么内容,如何交互,这就和服务端的程序编写有关,客户端无法控制。例如,这一讲服务器就提供了文件列表浏览、传输文件等功能,也就有了更多约定:发送dir 显示文件列表、发送 help显示帮助信息、bye表示退出;发送文件名,服务器会回馈文件名是否正确、能否下载,再根据提示使用“下载”按钮完成下载动作。

  1. 新建一个程序包:chapter04,这个包下面再创建client和server两个程序包,本讲所有的客户端程序都创建在client包中,如图4.3所示:
    在这里插入图片描述
  2. 发送用户信息等功能和之前学习的程序要求基本一致,可以将上一讲的TCPClient.java程序借用过来,复制、重构命名为FileDialogClient.java,原有的方法保持不变。
  3. 创建FileClientFX窗体程序。该窗体界面如图4.4所示。
    在这里插入图片描述
    (1)程序界面和之前的任务大致相似,同样策略,可以复制、重构上一讲的TCPClientThreadFX程序,重命名为FileClientFX,并在代码中增加“下载”按钮。

注意,使用idea重构此类以及前面的类,可能会提示有多处需要重构重命名,这是idea比较智能的地方,因为窗体类多处使用了TCPClient,这个类重命名后,窗体中对应的变量名也需要修改,这样比自己手动修改方便很多(重构时会将TCPClient的调用改为FileDialogClient的调用,TCPClient类型的实例变量名也会自动重命名为fileDialogClient,如果有报错,说明有地方没有修改完全,则自行仔细检查,手动完成);

(2)在程序中增加ip和port两个String类型的成员变量,用来保存对应文本框的输入信息;

三、程序设计第二步:创建客户端数据传送进程FileDataClient.java

主要功能:连接服务器数据端口、发送文件名、保存下载的文件,文件传输完成后关闭数据连接。
该程序有2个方法:

(1)构造方法,FileDataClient(String ip, String port),主要功能是向服务器的数据端口请求连接;

(2)文件下载方法getFile(fileName)[见附录代码]。

该方法主要功能是先在本地新建一个空文件,并向服务器发送其文件名(基于字符串的字节流包装操作),然后接收网络文件数据并保存为本地的这个文件(基于文件的字节流包装操作),关闭数据套接字。

可以在窗体界面的“下载”按钮中调用getFile方法,例如:

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();
  }
});

注意,若下载文件为0字节,首先就要检查是否做了flush和关闭文件的操作。一些逻辑判断、ip地址、端口号等各种错误都可能造成错误,这种情况就善用idea的断点调试功能,慢慢观察各个变量值,找出bug。另外,为了避免不小心带入空白符,建议程序中获取文本信息都使用trim()方法消除空白。
下载完成后,第20行代码给服务器发送了“客户端开启下载”,这就类似bye一样都是服务端和客户端的约定,服务端的代码已经对这个字符串做了判断,接收到这个字符串就知道客户端开始了下载,这样就可以给客户端发送一些反馈信息。

四、程序设计第三步:文件对话服务器程序FileDialogServer.java

文件对话服务器程序开启2021端口,用来传送双方的对话信息,如文件列表等信息,本质上和第二讲的TCPServer类似,但要额外提供发送文件列表的功能,而且服务器要使用多线程模式,本讲不强制要求同学制作该程序(该程序运行在教师机上,同学们也可自行制作类似的服务器,提升程序设计能力),附录中有部分代码。

五、程序设计第四步:文件数据服务器程序FileDataServer.java

文件数据服务器程序专门提供文件下载服务,开启2020端口,提供文件下载服务,不强制要求同学制作该程序(该程序运行在教师机上,同学们也可自行制作类似的服务器,提升程序设计能力),附录中有部分下载文件的代码,和客户端的getFile方法配合使用提供下载功能。
请认真对照客户端getFile方法和下载文件的代码,思考文件下载和之前章节的网络对话有本质的区别吗?

扩展一

增加一个便捷功能:鼠标拖动加亮信息显示区的文字,拖动的同时把加亮的文字同步复制到信息输入框中,这个功能是为了方便下载文件(实现点击下载按钮则读取信息输入框中的文件名,并下载该文件,手动输入显然不如鼠标操作方便);
这个功能的实现比较特别,这种情况一般会处理鼠标拖动事件来解决(mouse dragged),例如,把文本框TextField的内容复制到文本域TextArea,只要setOnMouseDragged 处理鼠标拖动事件就可以。但从TextArea复制内容到TextField,这种方式却不适用。文本域是多行文本,鼠标拖动加亮文字不会触发鼠标拖动事件。解决方案是为文本域的选择属性selectionProperty添加监听器(相当于是为输入文本框和显示文本域做了有条件的属性绑定):

//信息显示区鼠标拖动高亮文字直接复制到信息输入框,方便选择文件名
//taDispaly为信息选择区的TextArea,tfSend为信息输入区的TextField
//为taDisplay的选择范围属性添加监听器,当该属性值变化(选择文字时),会触发监听器中的代码
taDisplay.selectionProperty().addListener((observable, oldValue, newValue) -> {
  //只有当鼠标拖动选中了文字才复制内容
  if(!taDisplay.getSelectedText().equals(""))
    tfSend.setText(taDisplay.getSelectedText());
});

扩展二

对于新开线程,使用lambda表达式比较方便,但如果新线程的代码较多,则新线程中的任务和其它代码混在一起比较混乱,不方便阅读和查错(例如连接按钮中新开线程用于读取信息的相关代码)。
设计一个成员内部类ReceiveHandler,用于封装新线程读取信息的功能,类似:

class ReceiveHandler implements Runnable{……}

连接按钮中新开线程的代码就可以简写为:

//用于接收服务器信息的单独线程
receiveThread = new Thread(new ReceiveHandler(), "my-receiveThread");

这样代码就比较清晰易读。其它部分代码大家也可以优化,例如下载按钮动作事件的代码也较多,也可以封装为一个方法,再在下载按钮动作事件处理中调用。

FileDataClient.java中的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数据服务器失败");
  }
}

文件对话服务器程序中的文件列表服务模块(供参考)

public void fileListPushToClient(PrintWriter pw) {
      String path = "d:/ftpserver"; //给出服务器下载目录路径
      File filePath = new File(path);
      if (!filePath.exists()) { //路径不存在则返回
        System.out.println("ftp下载目录不存在");
        return;
      }
      //如果不是一个目录就返回
      if (!filePath.isDirectory()) {
        System.out.println("不是一个目录");
        return;
      }
      //开始显示目录下的文件,不包括子目录
      fileNames = filePath.list();
      File tempFile;
      // 格式化文件大小输出,不保留小数,不用四舍五入,有小数位就进1
      DecimalFormat formater = new DecimalFormat();
      formater.setMaximumFractionDigits(0);
      formater.setRoundingMode(RoundingMode.CEILING);

      for (String fileName : fileNames) {
        tempFile = new File(filePath + "/" + fileName);
        if (tempFile.isFile()) {
          pw.println("                               " +
              fileName + "  " + formater.format(tempFile.length() / 1024.0) + "KB");
        }
      }
    }

文件数据服务器程序部分代码

try {
  InputStream socketIn = socket.getInputStream();
  BufferedReader br = new BufferedReader(new    InputStreamReader(socketIn, "utf-8"));

  String downFileName = br.readLine().trim();
  System.out.println("要下载的文件为:" + downFileName);

  String filePath = "d:/ftpserver";
  if (downFileName == null || !isValidFileName(downFileName, filePath)) {
    socket.close(); //文件名无效,关闭连接
    return;
  }
  downFileName = filePath + "/" + downFileName;
  //读取ftp服务器上的文件,写出到网络字节流
  OutputStream socketOut = socket.getOutputStream();
  FileInputStream fileIn = new FileInputStream(downFileName);
  byte[] buf = new byte[1024]; //用来缓存字节数据
  int size;
  while ((size = fileIn.read(buf)) != -1) { //读取结束返回-1
    socketOut.write(buf, 0, size);
  }
  socketOut.flush();
  socketOut.close();
  fileIn.close();

  System.out.println(downFileName + " 文件传输结束");
} catch (IOException e) {
  e.printStackTrace();
} finally {
  if (socket != null) {
    try {
      socket.close(); //关闭socket及其关联的输入输出流
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

项目结构

在这里插入图片描述

完整代码

chapter04/client/FileClientFX.java

package chapter04.client;

import chapter01.TextFileIO;
//import chapter03.TCPClient;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.File;
import java.io.IOException;

/**
 * @projectName: NetworkApp
 * @package: chapter02
 * @className: TCPClientFX
 * @author: GCT
 * @description: TODO
 * @date: 2022/9/4 18:08
 * @version: 1.0
 */
public class FileClientFX extends Application {
    private Button btnExit = new Button("退出");
    private Button btnSend = new Button("发送");
    private Button btnConnect = new Button("连接");
    private Button btnDownload = new Button("下载");




    Thread receiveThread; //定义成员变量,读取服务器信息的线程

//    private Button btnOpen = new Button("加载");
//    private Button btnSave = new Button("保存");
    //待发送信息的文本框

    private TextField tfip = new TextField();
    private TextField tfport = new TextField();

    private TextField tfSend = new TextField();
    //显示信息的文本区域
    private TextArea taDisplay = new TextArea();

//    private TCPClient tcpClient;

    private String ip;
    private String port;

    private FileDialogClient fileDialogClient;


    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) {

        //    将TextFileIO类实例化为textFileIO
        TextFileIO textFileIO = new TextFileIO();

        BorderPane mainPane = new BorderPane();

//        顶部的ip和端口输入框区域
        HBox topHBox = new HBox();
        topHBox.setSpacing(10);
        topHBox.setPadding(new Insets(10,20,10,20));
        topHBox.setAlignment(Pos.CENTER);
        topHBox.getChildren().addAll(new Label("IP: "),tfip,new Label("端口号:"),tfport,btnConnect);
        mainPane.setTop(topHBox);


        //内容显示区域
        VBox vBox = new VBox();
        vBox.setSpacing(10);//各控件之间的间隔
        //VBox面板中的内容距离四周的留空区域
        vBox.setPadding(new Insets(10,20,10,20));
        vBox.getChildren().addAll(new Label("信息显示区:"),
                taDisplay,new Label("信息输入区:"), tfSend);
        //设置显示信息区的文本区域可以纵向自动扩充范围
        VBox.setVgrow(taDisplay, Priority.ALWAYS);
        mainPane.setCenter(vBox);
        //底部按钮区域
        HBox hBox = new HBox();
        hBox.setSpacing(10);
        hBox.setPadding(new Insets(10,20,10,20));
        hBox.setAlignment(Pos.CENTER_RIGHT);
//        hBox.getChildren().addAll(btnSend,btnSave,btnOpen,btnExit);
        hBox.getChildren().addAll(btnSend,btnDownload,btnExit);
        mainPane.setBottom(hBox);
        Scene scene = new Scene(mainPane,700,400);
        primaryStage.setScene(scene);
        primaryStage.show();
//……
--------事件处理代码部分--------
//……

//        连接
        btnConnect.setOnAction(event -> {
            ip = tfip.getText().trim();
            port = tfport.getText().trim();

            try {
                //tcpClient不是局部变量,是本程序定义的一个TCPClient类型的成员变量
                fileDialogClient = new FileDialogClient(ip,port);
                //成功连接服务器,接收服务器发来的第一条欢迎信息
//                String firstMsg = tcpClient.receive();
//                taDisplay.appendText(firstMsg + "\n");

                //用于接收服务器信息的单独线程
                receiveThread = new Thread(()->{
                    String msg = null;
                    //不知道服务器有多少回传信息,就持续不断接收
                    //由于在另外一个线程,不会阻塞主线程的正常运行
                    while ((msg = fileDialogClient.receive()) != null) {
                        //runLater中的lambda表达式不能直接访问外部非final类型局部变量
                        //所以这里使用了一个临时常量,可以省略final,但本质还是会作为常量使用
                        final String msgTemp = msg; //msgTemp实质是final类型
                        Platform.runLater(()->{
                            taDisplay.appendText( msgTemp + "\n");
                        });
                    }
//跳出了循环,说明服务器已关闭,读取为null,提示对话关闭
                    Platform.runLater(()->{
                        taDisplay.appendText("对话已关闭!\n" );
                    });
                }, "my-readServerThread"); //给新线程取别名,方便识别
                receiveThread.start(); //启动线程
            } catch (Exception e) {
                taDisplay.appendText("服务器连接失败!" + e.getMessage() + "\n");
            }
        });

//        退出
        btnExit.setOnAction(event -> {
//            if(tcpClient != null){
//                //向服务器发送关闭连接的约定信息
//                tcpClient.send("bye");
//                tcpClient.close();
//            }
//            System.exit(0);
            endSystem();
        });


//        设置taDisplay自动换行
        taDisplay.setWrapText(true);
//        设置taDisplay只读
        taDisplay.setEditable(false);
//        退出按钮事件
//        btnExit.setOnAction(event -> {System.exit(0);});
        btnExit.setOnAction(event -> endSystem());
//        发送按钮事件
        btnSend.setOnAction(event -> {
            String sendMsg = tfSend.getText();
            if (sendMsg.equals("bye")){
                btnConnect.setDisable(false);
                btnSend.setDisable(true);
            }

            //9.20
            fileDialogClient.send(sendMsg);//向服务器发送一串字符

//            tcpClient.send(sendMsg);//向服务器发送一串字符
            taDisplay.appendText("客户端发送:" + sendMsg + "\n");


//            String receiveMsg = tcpClient.receive();//从服务器接收一行字符
//            taDisplay.appendText(receiveMsg + "\n");
        });

        taDisplay.selectionProperty().addListener((observable,oldValue,newValue)->{
            if(!taDisplay.getSelectedText().equals(""))
                tfSend.setText(taDisplay.getSelectedText());
        });

//        下载事件
        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();
            }
        });



//        tfSend.setOnKeyPressed(event -> {
//            if (event.getCode() == KeyCode.ENTER){
//                if (event.isShiftDown()){
//                    String msg = tfSend.getText();
//                    taDisplay.appendText("echo: "+ msg + "\n");
//                    tfSend.clear();
//                }
//                else{
//                    String msg = tfSend.getText();
//                    taDisplay.appendText(msg + "\n");
//                    tfSend.clear();
//                }
//            }
//
//        });

//        btnSave.setOnAction(event -> {
//            //添加当前时间信息进行保存
//            textFileIO.append(
//                    LocalDateTime.now().withNano(0) +" "+ taDisplay.getText());
//        });
//
//        btnOpen.setOnAction(event -> {
//            String msg = textFileIO.load();
//            if(msg != null){
//                taDisplay.clear();
//                taDisplay.setText(msg);
//            }
//        });



    }

    private void endSystem(){
        if (fileDialogClient!=null){
            fileDialogClient.send("bye");
            fileDialogClient.close();
        }

        System.exit(0);
    }

}

chapter04/client/FileDataClient.java

package chapter04.client;

import java.io.*;
import java.net.Socket;

/**
 * @projectName: NetworkApp
 * @package: chapter04.client
 * @className: FileDataClient
 * @author: GCT
 * @description: TODO
 * @date: 2022/9/20 19:17
 * @version: 1.0
 */
public class FileDataClient {
    Socket dataSocket;
    FileDataClient(String ip,String port) throws IOException {
        dataSocket = new Socket(ip, Integer.parseInt(port));
        //得到网络输出字节流地址,并封装成网络输出字符流

    }

    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数据服务器失败");
        }
    }

}

chapter04/client/FileDialogClient.java

package chapter04.client;

import java.io.*;
import java.net.Socket;

/**
 * @projectName: NetworkApp
 * @package: chapter02
 * @className: TCPCilent
 * @author: GCT
 * @description: TODO
 * @date: 2022/9/4 18:06
 * @version: 1.0
 */
public class FileDialogClient {
    private Socket socket; //定义套接字
    //定义字符输入流和输出流
    private PrintWriter pw;
    private BufferedReader br;

    public FileDialogClient(String ip, String port) throws IOException {
        //主动向服务器发起连接,实现TCP的三次握手过程
        //如果不成功,则抛出错误信息,其错误信息交由调用者处理
        socket = new Socket(ip, Integer.parseInt(port));

        //得到网络输出字节流地址,并封装成网络输出字符流
        OutputStream socketOut = socket.getOutputStream();
        pw = new PrintWriter( // 设置最后一个参数为true,表示自动flush数据
                new OutputStreamWriter(//设置utf-8编码
                        socketOut, "utf-8"), true);

        //得到网络输入字节流地址,并封装成网络输入字符流
        InputStream socketIn = socket.getInputStream();
        br = new BufferedReader(
                new InputStreamReader(socketIn, "utf-8"));
    }

    public void send(String msg) {
        //输出字符流,由Socket调用系统底层函数,经网卡发送字节流
        pw.println(msg);
    }

    public String receive() {
        String msg = null;
        try {
            //从网络输入字符流中读信息,每次只能接受一行信息
            //如果不够一行(无行结束符),则该语句阻塞等待,
            // 直到条件满足,程序才往下运行
            msg = br.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return msg;
    }
    public void close() {
        try {
            if (socket != null) {
                //关闭socket连接及相关的输入输出流,实现四次握手断开
                socket.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException{
        FileDialogClient fileDialogClient = new FileDialogClient("127.0.0.1", "8008");
        fileDialogClient.send("hello");
        System.out.println(fileDialogClient.receive());

    }
}

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
对于JavaFX文件传输,你可以使用Java的Socket编程来实现。以下是一个简单的示例代码,用于在客户端和服务器之间传输文件: 在服务器端: ```java import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(8888); System.out.println("等待客户端连接..."); Socket socket = serverSocket.accept(); System.out.println("客户端已连接"); DataInputStream dis = new DataInputStream(socket.getInputStream()); String fileName = dis.readUTF(); long fileSize = dis.readLong(); FileOutputStream fos = new FileOutputStream("服务器保存的文件路径" + fileName); byte[] buffer = new byte[1024]; int bytesRead; long totalBytesRead = 0; while ((bytesRead = dis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); totalBytesRead += bytesRead; if (totalBytesRead == fileSize) { break; } } System.out.println("文件接收完成"); fos.close(); dis.close(); socket.close(); } } ``` 在客户端: ```java import java.io.*; import java.net.Socket; public class Client { public static void main(String[] args) throws IOException { Socket socket = new Socket("服务器IP地址", 8888); File file = new File("要传输的文件路径"); FileInputStream fis = new FileInputStream(file); DataOutputStream dos = new DataOutputStream(socket.getOutputStream()); dos.writeUTF(file.getName()); dos.writeLong(file.length()); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { dos.write(buffer, 0, bytesRead); } System.out.println("文件发送完成"); dos.close(); fis.close(); socket.close(); } } ``` 请确保在代码中替换以下部分: - 服务器端中的"服务器保存的文件路径":用于指定服务器上保存接收文件的路径。 - 客户端中的"服务器IP地址":用于指定服务器的IP地址。 - 客户端中的"要传输的文件路径":用于指定需要传输的文件路径。 这只是一个基本示例,你可以根据自己的需求进行修改和扩展。在实际应用中,你可能还需要处理异常、添加进度条等功能

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GCTTTTTT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值