Java基于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; 
/** 
 * 我一般写英文注释,偶尔我也会写中文注释,只是觉得写英文 
 * 注释跟代码比较统一,无他。 
 */ 
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 
 */ 
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):

以上就是本文的全部内容,希望对大家的学习有所帮助。

若文章对您有帮助,帮忙点个赞!

0
-3
发布时间 2016-01-15 17:23:19
0 条回复(回复会通过微信通知作者)
点击加载更多评论
登录 后再进行评论
(微信扫码即可登录,无需注册)