用Java开发局域网内文件传输软件遇到的一些问题
阅读原文时间:2023年07月08日阅读:1

项目地址:https://github.com/b84955189/FileTransfer

由于巨懒的我不太喜欢使用U盘操作文件,特此开发一个简易的文件传输程序。

目前仅限局域网内传输,后期会尝试写下跨网传输。

本软件主要功能模块如下:

(1) 文件收发模块

(2) 局域网同网段用户检测模块

(3) 用户配置模块

文件收发模块:任意文件类型传输,仅限单个文件传输,可打包传输。

局域网同网段用户检测模块:同网段用户广播,直接发送,无需目标IP,前提需要同一网段下,且没有限制广播发送。

用户配置模块:用户自主选择是否允许同网段用户发现,自主选择文件保存目录,配置文件通过软件根目录下Config.cnf保存。


注意:设置了无限超时后,也要注意连接时的超时,防止输入错误IP后,一直尝试连接。

即可以单独设置连接超时:socket.connect(address,timeout);

项目中示例(SendThread中):

 //0-为无限等待
 socket.setSoTimeout(0);
//TCP连接--设置了连接超时
socket.connect(address,3000);

Java已经提供好了网络接口类,非常方便。

详情见博文:https://blog.csdn.net/weixin_43670802/article/details/103166435

项目中涉及暴露线程关闭于重启问题。

由于同一线程对象的重启很麻烦,所以我省去了关闭过程,只是让其一直continue;wait()似乎也可以,可以尝试下)

也就是说此线程一直没关过。

//JVM默认初始化为false
private boolean sign;
/*
线程无法重启!!!所以这辈子我也不把它给关了!!
 */
        while(true){
            if(sign) {
                //重新获取广播地址
                broadcastAddress = CommonUtils.getBroadcastAddress();
                //重新打包
                DatagramPacket packet = new DatagramPacket(data, 0, data.length, broadcastAddress, R.Configs.UDP_PORT);
                System.out.println(packet.getAddress().getHostAddress());
                socket.send(packet);

                //1s一次
                Thread.sleep(1000);
            }else{
                //1s一次
                Thread.sleep(1000);
                continue;
            }

        }
        } catch(Exception e){
            e.printStackTrace();
            JOptionPane.showMessageDialog(null,R.Strings.NETWORK_CHANGE);
            //重新获取广播地址
            broadcastAddress=CommonUtils.getBroadcastAddress();
            //--test
            System.out.println(broadcastAddress.getHostAddress());
            //--test
            if(broadcastAddress!=null)
            this.run();
            else {
                JOptionPane.showConfirmDialog(null,R.Strings.NOT_NETWORK);
                System.exit(0);
            }
        }
    }
    /*
    线程开关
     */
    public void setSwitch(boolean sign){
        this.sign=sign;

    }

补充:https://blog.csdn.net/taoszu/article/details/82728405

获取到发送模块的文件名之后,使用File类的exist()方法检测是否已经存在。然后再利用字符串处理函数改名。

//输出文件路径+文件名
            thisFile=new File(fileDirectory,fileName);
            int fileCount=0;
            //判断文件名是否冲突
            while(thisFile.exists()){
                int position=fileName.lastIndexOf(".");
                String preFileName=fileName.substring(0,position);
                String latFileType=fileName.substring(position);//不用去除'.'了
                fileName=preFileName+"("+(++fileCount)+")"+latFileType;
                thisFile=new File(fileDirectory,fileName);

            }

另外,用户中止上传后(即接收模块会抛出异常),可以调用thisFile.delete();来删除无用文件。

项目异常处理代码块中:

 //删除失败文件
            thisFile.delete();

项目中将设置界面背景的函数写入了CommonUtils工具类,另外注意中间媒介(图床)JLabel的地址一直没有变。

JLabel为MainView成员变量。(虽然很鸡肋。。。)

  /*
    设置JFrame背景
    前提-需要内容面板透明
````框架-载体地址-图片
     */
    public static void setBackground(JFrame jFrame,JLabel jLabel,Image image){

        JLayeredPane jLayeredPane=jFrame.getLayeredPane();
        jLayeredPane.remove(jLabel);
        jLabel.setIcon(new ImageIcon(image));
        jLabel.setBounds(0,0,jFrame.getWidth(),jFrame.getHeight());
        jLayeredPane.add(jLabel,new Integer(Integer.MIN_VALUE));

        //jLayeredPane.add(jFrame.getContentPane(),new Integer(Integer.MAX_VALUE));
        //jFrame.remove(jFrame.getLayeredPane());
        //jFrame.setLayeredPane(jLayeredPane);

        jFrame.validate();
        jFrame.repaint();

    }

本项目为了美化,即将初始标题栏去掉后,将图标放置在了JMenuBar上,自己体会吧。

 menuBar.add(Box.createHorizontalStrut(400));//间隔

        menuBar.add(minimizeMenuItem);
        menuBar.add(closeMenuItem);

本项目创建了一个配置信息类,程序全局配置信息全部从此类中(静态方法)获取。

package transfer.data;

public class Configs {
    //暴露开关,默认为开
    private static int udpSwitch=0;
    //默认保存地址为当前目录
    private static String fileDirectory=".";

    public static int getUdpSwitch() {
        return udpSwitch;
    }

    public static void setUdpSwitch(int udpSwitch) {
        Configs.udpSwitch = udpSwitch;
    }

    public static String getFileDiretory() {
        return fileDirectory;
    }

    public static void setFileDirectory(String fileDirectory) {
        Configs.fileDirectory = fileDirectory;
    }
    public static String toFile(){
        return udpSwitch+","+fileDirectory;
    }
    public static void fromFile(String config){
        String[] data=config.split(",");
        Configs.setUdpSwitch(Integer.parseInt(data[0]));
        Configs.setFileDirectory(data[1]);
    }
}

正常关闭时写入本机文件Config,cnf

 /*
    ---之所以不直接用字符流,是因为不想让人随意查看配置文件--抬高一点逼格,然并卵...haha...
     */
    private void saveConfigs(){
        DataOutputStream dataOutputStream=null;
        try {
            dataOutputStream = new DataOutputStream(new FileOutputStream(configFile));
            dataOutputStream.writeUTF(Configs.toFile());
            dataOutputStream.close();

        }catch (Exception e){
            e.printStackTrace();

            try {
                if (dataOutputStream!=null)
                    dataOutputStream.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }

        }
    }

打开时读取文件至Config类中的静态变量中:

  /*
    初始化读取配置文件
     */
    private void initConfig(){
        DataInputStream dataInputStream=null;
        try{
            dataInputStream=new DataInputStream(new FileInputStream(configFile));
            String config=dataInputStream.readUTF();
            if(config==null||config.equals(""))
                    throw new Exception();
            else{
                Configs.fromFile(config);
            }
        }catch(Exception e){
            e.printStackTrace();
            JOptionPane.showMessageDialog(null,R.Strings.READ_CONFIG_ERROR,R.Strings.TIP_TITLE,JOptionPane.ERROR_MESSAGE);

        }
    }