一個基于Java Socket協議之上文件傳輸的完整示例,基于TCP通信完成。
除了基于TCP的二進制文件傳輸,還演示了JAVA Swing的一些編程技巧,Demo程序
實現主要功能有以下幾點:
- 1.基于Java Socket的二進制文件傳輸(包括圖片,二進制文件,各種文檔work,PDF)
- 2.SwingWorker集合JProgressBar顯示實時傳輸/接受完成的百分比
- 3.其它一些Swing多線程編程技巧
首先來看一下整個Dome的Class之間的關系圖:
下面按照上圖來詳細解釋各個類的功能與代碼實現:
服務器端:
FileTransferServer類的功能首先是在端口9999創建一個服務器套接字并
開始監聽連接。相關代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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類的完整源代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
|
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流。
該類的完整源代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
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代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
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字節流中讀取對方接受完成通知信息,調用彈出文件傳輸成功信息
該類完全源代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
|
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 ; } } |
數據包類如下,不解釋!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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):
以上就是本文的全部內容,希望對大家的學習有所幫助。