Java Socket编程 - 基于TCP方式的二进制文件传输

一个基于Java Socket协议之上文件传输的完整示例,基于TCP通信完成。

除了基于TCP的二进制文件传输,还演示了JAVA Swing的一些编程技巧,Demo程序

实现主要功能有以下几点:

1.      基于Java Socket的二进制文件传输(包括图片,二进制文件,各种文档work,

         PDF)

2.      SwingWorker集合JProgressBar显示实时传输/接受完成的百分比

3.      其它一些Swing多线程编程技巧

首先来看一下整个Dome的Class之间的关系图:


下面按照上图来详细解释各个类的功能与代码实现:

服务器端:

FileTransferServer类的功能首先是在端口9999创建一个服务器套接字并

开始监听连接。相关代码如下:

 

private void startServer(int port) {  
    try {  
        serverSocket = new ServerSocket(port);  
        System.out.println("Server started at port :" + port);  
        while(true) {  
            Socket client = serverSocket.accept(); // blocked & waiting for income socket  
            System.out.println("Just connected to " + client.getRemoteSocketAddress());  
            FileReceiveTask task = new FileReceiveTask(client);  
            bar.setValue(0); // reset it now  
            task.addPropertyChangeListener(new PropertyChangeListener() {  
                public void propertyChange(PropertyChangeEvent evt) {  
                    if ("progress".equals(evt.getPropertyName())) {  
                        bar.setValue((Integer) evt.getNewValue());  
                    }  
                }  
            });  
              
            task.execute();  
        }  
  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
} 

 


关于PropertyChangeListener, Java提供了一个非常有力的工具类来

监控任意Bean Model的数据改变,程序通过添加该监听器实现对

SwingWorker的progress属性值改变的事件捕获,然后更新JProgressBar

实例对象,实现了UI的刷新。FileTransferServer类的完整源代码如下:

 

package com.gloomyfish.socket.tutorial.filetransfer;  
  
import java.awt.BorderLayout;  
import java.awt.FlowLayout;  
import java.awt.event.ActionEvent;  
import java.awt.event.ActionListener;  
import java.beans.PropertyChangeEvent;  
import java.beans.PropertyChangeListener;  
import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  
  
import javax.swing.BoxLayout;  
import javax.swing.JButton;  
import javax.swing.JFrame;  
import javax.swing.JOptionPane;  
import javax.swing.JPanel;  
import javax.swing.JProgressBar;  
  
public class FileTransferServer extends JFrame implements ActionListener {  
    /** 
     *  
     */  
    public final static String START_SVR = "Start";  
    public final static String SHUT_DOWN_SVR = "Shut Down";  
    public final static String END_FLAG = "EOF";  
    private static final long serialVersionUID = 1L;  
    private ServerSocket serverSocket;  
    private JButton startBtn;  
    private JProgressBar bar;  
    public FileTransferServer() {  
        super("File Server");  
        initComponent();  
        setupListener();  
    }  
  
    private void setupListener() {  
        startBtn.addActionListener(this);  
    }  
  
    private void initComponent() {  
        startBtn = new JButton(START_SVR);  
        JPanel progressPanel = new JPanel();  
        progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.Y_AXIS));  
        bar = new JProgressBar();  
        bar.setMinimum(0);  
        bar.setMaximum(100);  
        progressPanel.add(bar);  
        getContentPane().setLayout(new BorderLayout());  
        JPanel btnPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));  
        btnPanel.add(startBtn);  
        getContentPane().add(btnPanel, BorderLayout.SOUTH);  
        getContentPane().add(progressPanel, BorderLayout.CENTER);  
    }  
      
    private void startServer(int port) {  
        try {  
            serverSocket = new ServerSocket(port);  
            System.out.println("Server started at port :" + port);  
            while(true) {  
                Socket client = serverSocket.accept(); // blocked & waiting for income socket  
                System.out.println("Just connected to " + client.getRemoteSocketAddress());  
                FileReceiveTask task = new FileReceiveTask(client);  
                bar.setValue(0); // reset it now  
                task.addPropertyChangeListener(new PropertyChangeListener() {  
                    public void propertyChange(PropertyChangeEvent evt) {  
                        if ("progress".equals(evt.getPropertyName())) {  
                            bar.setValue((Integer) evt.getNewValue());  
                        }  
                    }  
                });  
                  
                task.execute();  
            }  
  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
      
    public void showSuccess() {  
        bar.setValue(100);  
        JOptionPane.showMessageDialog(this, "file received successfully!");  
    }  
  
    @Override  
    public void actionPerformed(ActionEvent e) {  
        if(START_SVR.equals(e.getActionCommand())) {  
            Thread startThread = new Thread(new Runnable() {  
                public void run() {  
                    startServer(9999);  
                }  
            });  
            startThread.start();  
            startBtn.setEnabled(false);  
        } else if(SHUT_DOWN_SVR.equals(e.getActionCommand())) {  
  
        } else {  
            // do nothing...  
        }  
    }  
      
    public static void main(String[] args) {  
        FileTransferServer server = new FileTransferServer();  
        server.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
        server.setSize(400, 400);  
        server.setResizable(false);  
        server.setVisible(true);  
    }  
}

 

 

FileReceiveTask是服务器端的文件接受类:

首先从建立的TCP流中得到文件名与文件大小,然后开始接受文件内容字节

并写入创建的文件对象流中,最后验证文件大小与写入的字节流是否相等

最后发送一条消息到文件发送方,告诉对方文件传输完成,可以关闭TCP流。

该类的完整源代码如下:

 

    package com.gloomyfish.socket.tutorial.filetransfer;  
      
    import java.io.BufferedOutputStream;  
    import java.io.BufferedWriter;  
    import java.io.DataInputStream;  
    import java.io.File;  
    import java.io.FileOutputStream;  
    import java.io.OutputStreamWriter;  
    import java.net.Socket;  
      
    import javax.swing.SwingWorker;  
      
    public class FileReceiveTask extends SwingWorker<Integer, Object> {  
        private Socket _mSocket;  
        public FileReceiveTask(Socket client) {  
            this._mSocket = client;  
        }  
      
        @Override  
        protected Integer doInBackground() throws Exception {  
            // get file meta information  
            DataInputStream input = new DataInputStream(_mSocket.getInputStream());  
            String fileName = input.readUTF();  
            int fileLength = (int)input.readLong(); // number of total bytes  
            File file = new File("C:\\Users\\fish\\Downloads" + File.separator + fileName);  
            BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(file));  
            System.out.println("Received File Name = " + fileName);  
            System.out.println("Received File size = " + fileLength/1024 + "KB");  
              
            // start to receive the content of the file and write them  
            byte[] content = new byte[2048];  
            int offset = 0;  
            int numReadBytes = 0;  
            while(offset < fileLength && (numReadBytes = input.read(content)) > 0) {  
                output.write(content, 0, numReadBytes);  
                float precent = 100.0f * ((float)offset)/((float)fileLength);  
                setProgress((int)precent);  
                offset += numReadBytes;  
            }  
            System.out.println("numReadBytes = " + numReadBytes);  
            if(offset < fileLength) {  
                numReadBytes = input.read(content);  
                System.out.println("numReadBytes = " + numReadBytes);  
                System.out.println("File content error at server side");  
            } else {  
                System.out.println("File Receive Task has done correctly");  
            }  
            setProgress(100);  
              
            // tell client to close the socket now, we already receive the file successfully!!  
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(_mSocket.getOutputStream()));  
            bufferedWriter.write("DONE\r\n");  
            bufferedWriter.flush();  
              
            // close the file and socket  
            output.close();  
            _mSocket.close();  
            return 100;  
        }  
      
    }  

 

客户端:

FileTransferClient是客户端UI类,用来实现到服务端的连接,然后选择

要传输的文件(图片,PDF,Word文档等各种二进制文件)。如果没有

输入服务器信息,会弹出提示要求输入。端口已经指定为:9999

【send File】按钮会打开文件选择框,用户选择要传输文件以后,创建

FileTransferTask线程,并开始执行文件传送。客户端UI代码如下:

 

    package com.gloomyfish.socket.tutorial.filetransfer;  
      
    import java.awt.BorderLayout;  
    import java.awt.FlowLayout;  
    import java.awt.GridLayout;  
    import java.awt.event.ActionEvent;  
    import java.awt.event.ActionListener;  
    import java.beans.PropertyChangeEvent;  
    import java.beans.PropertyChangeListener;  
    import java.io.File;  
    import java.net.InetSocketAddress;  
    import java.net.SocketAddress;  
      
    import javax.swing.BorderFactory;  
    import javax.swing.BoxLayout;  
    import javax.swing.JButton;  
    import javax.swing.JFileChooser;  
    import javax.swing.JFrame;  
    import javax.swing.JLabel;  
    import javax.swing.JOptionPane;  
    import javax.swing.JPanel;  
    import javax.swing.JProgressBar;  
    import javax.swing.JTextField;  
    /** 
     * 我一般写英文注释,偶尔我也会写中文注释,只是觉得写英文 
     * 注释跟代码比较统一,无他。 
     *  
     * @Date 2012-11-30 
     * @author fish 
     * 
     */  
    public class FileTransferClient extends JFrame implements ActionListener {  
        /** 
         *  
         */  
        private static final long serialVersionUID = 1L;  
        public final static String SEND_CMD = "Send File";  
        public final static int MINIMUM = 0;  
        public final static int MAXIMUM = 100;  
        // public final static String CONNECT_CMD = "Connect";  
        private JButton sendFileBtn;  
        private JTextField serverField;  
        private JTextField portField;  
        private JProgressBar bar;  
          
        public FileTransferClient() {  
            super("File Transfer Client");  
            initComponents();  
        }  
      
        private void initComponents() {  
            getContentPane().setLayout(new BorderLayout());  
            JPanel progressPanel = new JPanel();  
            progressPanel.setLayout(new BoxLayout(progressPanel, BoxLayout.Y_AXIS));  
            bar = new JProgressBar();  
            progressPanel.add(bar);  
            bar.setMinimum(MINIMUM);  
            bar.setMaximum(MAXIMUM);  
            JPanel serverSettingPanel = new JPanel();  
            serverSettingPanel.setLayout(new GridLayout(2,2,5,5));  
            serverSettingPanel.setBorder(BorderFactory.createTitledBorder("Server Setting"));  
            serverField = new JTextField();  
            portField = new JTextField();  
            serverSettingPanel.add(new JLabel("Server IP/Host:"));  
            serverSettingPanel.add(serverField);  
            serverSettingPanel.add(new JLabel("Server Port:"));  
            serverSettingPanel.add(portField);  
              
            sendFileBtn = new JButton(SEND_CMD);  
            JPanel btnPanel = new JPanel();  
            btnPanel.setLayout(new FlowLayout(FlowLayout.RIGHT));  
            btnPanel.add(sendFileBtn);  
            getContentPane().add(serverSettingPanel, BorderLayout.NORTH);  
            getContentPane().add(btnPanel, BorderLayout.SOUTH);  
            getContentPane().add(progressPanel, BorderLayout.CENTER);  
            sendFileBtn.addActionListener(this);  
        }  
      
        @Override  
        public void actionPerformed(ActionEvent e) {  
            String command = e.getActionCommand();  
            if(command.equals(SEND_CMD)) {  
                if(checkNull()) {  
                    JOptionPane.showMessageDialog(this, "Please enter server host and port in order to set up the connection!");  
                    return;  
                }  
                JFileChooser chooser = new JFileChooser();  
                int status = chooser.showOpenDialog(null);  
                if (status == JFileChooser.APPROVE_OPTION) {  
                    File f = chooser.getSelectedFile();  
                    SocketAddress address = new InetSocketAddress(getServer(), getPort());  
                    FileTransferTask task = new FileTransferTask(f, address, this);  
                    bar.setValue(0);  
                    task.addPropertyChangeListener(new PropertyChangeListener() {  
                        public void propertyChange(PropertyChangeEvent evt) {  
                            if ("progress".equals(evt.getPropertyName())) {  
                                bar.setValue((Integer) evt.getNewValue());  
                            }  
                        }  
                    });  
                    task.execute(); // 异步task执行  
                }  
            } else {  
                // do nothing  
            }  
        }  
          
        public void showSuccess() {  
            bar.setValue(100);  
            JOptionPane.showMessageDialog(this, "file send successfully!");  
        }  
          
        public String getServer() {  
            return serverField.getText().trim();  
        }  
          
        public int getPort() {  
            return Integer.parseInt(portField.getText().trim());  
        }  
        /** 
         * make sure the UI already have some correct input information here!!! 
         * @return 
         */  
        private boolean checkNull() {  
            String serverName = serverField.getText();  
            String port = portField.getText();  
            if(serverName == null || serverName.length() == 0 || port == null || port.length() == 0) {  
                return true;  
            }  
              
            try {  
                Integer.parseInt(port); // try to parse it as server port number , validation code.  
            } catch(NumberFormatException ne) {  
                ne.printStackTrace();  
                return true;  
            }  
            return false;  
        }  
          
        public static void main(String[] args) {  
            FileTransferClient client = new FileTransferClient();  
            client.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  
            client.setSize(400, 400);  
            client.setResizable(false);  
            // client.pack();  
            client.setVisible(true);  
        }  
      
    }  

 

FileTransferTask实现的功能主要有:

1. 发送文件meta信息到接受方(文件名与文件大小)

2. 读取文件内容字节写入Socket字节流中,发送到接受方

3. 从Socket字节流中读取对方接受完成通知信息,调用弹出文件传输成功信息

该类完全源代码如下:

 

    package com.gloomyfish.socket.tutorial.filetransfer;  
      
    import java.io.BufferedInputStream;  
    import java.io.BufferedReader;  
    import java.io.DataInputStream;  
    import java.io.DataOutputStream;  
    import java.io.File;  
    import java.io.FileInputStream;  
    import java.io.IOException;  
    import java.io.InputStreamReader;  
    import java.net.Socket;  
    import java.net.SocketAddress;  
      
    import javax.swing.SwingWorker;  
      
    public class FileTransferTask extends SwingWorker<Integer, Object> {  
        private File selectedFile;  
        private Socket mSocket;  
        private SocketAddress address;  
        private FileTransferClient parent;  
          
        public FileTransferTask(File file, SocketAddress address, FileTransferClient owner /*, JProgressBar progress*/) {  
            this.address = address;  
            this.selectedFile = file;  
            mSocket = new Socket();  
            this.parent = owner;  
        }  
          
        @Override  
        protected Integer doInBackground() throws Exception {  
            // Get the size of the file  
            long length = selectedFile.length();  
            if (length > Integer.MAX_VALUE) {  
                throw new IOException("Could not completely read file " + selectedFile.getName() + " as it is too long (" + length + " bytes, max supported " + Integer.MAX_VALUE + ")");  
            }  
              
            mSocket.connect(address);  
              
            // Create the byte array to hold the file data  
            mSocket.setSoLinger(true, 60);  
            DataOutputStream dout = new DataOutputStream(mSocket.getOutputStream());  
            // now we start to send the file meta info.  
            dout.writeUTF(selectedFile.getName());  
            dout.writeLong(length);  
            dout.flush();  
            // end comment  
            FileDataPackage pData = new FileDataPackage();  
            DataInputStream is = new DataInputStream(new FileInputStream(selectedFile));  
            byte[] bytes = new byte[2048];  
      
            // Read in the bytes  
            int offset = 0;  
            int numRead = 0;  
            int fsize = (int)length;  
            while (offset < fsize && (numRead=is.read(bytes, 0, bytes.length)) >= 0) {  
                pData.setData(bytes, numRead);  
                dout.write(pData.getPackageData(), 0, pData.getPackageData().length);  
                dout.flush();  
                offset += numRead;  
                float precent = 100.0f * ((float)offset)/((float)fsize);  
                setProgress((int)precent);  
            }  
            System.out.println("total send bytes = " + offset);  
            // Ensure all the bytes have been read in  
            if (offset < fsize) {  
                throw new IOException("Could not completely transfer file " + selectedFile.getName());  
            }  
            mSocket.shutdownOutput();  
              
            // receive the file transfer successfully message from connection  
              
            BufferedInputStream streamReader = new BufferedInputStream(mSocket.getInputStream());  
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(streamReader));  
            String doneMsg = bufferedReader.readLine();  
            if("DONE".equals(doneMsg)) {  
                parent.showSuccess();  
            }  
            // Close the file input stream   
            setProgress(100);  
            // dout.close();  
            mSocket.close();  
            is.close();  
            System.out.println("close it now......");  
            return 100;  
        }  
    }  

 

数据包类如下,不解释!

 

    package com.gloomyfish.socket.tutorial.filetransfer;  
    /** 
     * this is very simple file transfer protocol over TCP socket 
     * @date 2012-12-01 
     * @author zhigang jia 
     * 
     */  
    public class FileDataPackage {  
      
        private int dataLength; // 数据包中数据长度,两个字节  
        private byte[] databuff; // 数据包中数据,meici最大不超过2048字节  
          
        public final static byte[] EOF = new byte[]{'E', 'O','F'};  
          
        public FileDataPackage() {  
            dataLength = 0;  
            databuff = new byte[2048];  
        }  
          
        public byte[] getPackageData() {  
            byte[] pData = new byte[dataLength];  
            // end comment  
            System.arraycopy(databuff, 0, pData, 0, dataLength);  
            return pData;  
        }  
          
        public void setData(byte[] data, int bsize) {  
            dataLength = bsize;  
            for(int i=0; i<databuff.length; i++) {  
                if(i<bsize) {  
                    databuff[i] = data[i];  
                } else {  
                    databuff[i] = ' ';  
                }  
            }  
        }  
    }  

 

每次发送的最大字节数为2048个字节。程序最终运行效果如下(win7 + JDK6u30):



觉得不错,请顶一下啊,谢谢!

posted @ 2012-12-03 10:18  if_only  阅读(1836)  评论(1编辑  收藏  举报
回到顶部