一個(gè)基于Java Socket協(xié)議之上文件傳輸?shù)耐暾纠赥CP通信完成。
除了基于TCP的二進(jìn)制文件傳輸,還演示了JAVA Swing的一些編程技巧,Demo程序
實(shí)現(xiàn)主要功能有以下幾點(diǎn):
- 1.基于Java Socket的二進(jìn)制文件傳輸(包括圖片,二進(jìn)制文件,各種文檔work,PDF)
- 2.SwingWorker集合JProgressBar顯示實(shí)時(shí)傳輸/接受完成的百分比
- 3.其它一些Swing多線程編程技巧
首先來(lái)看一下整個(gè)Dome的Class之間的關(guān)系圖:
下面按照上圖來(lái)詳細(xì)解釋各個(gè)類(lèi)的功能與代碼實(shí)現(xiàn):
服務(wù)器端:
FileTransferServer類(lèi)的功能首先是在端口9999創(chuàng)建一個(gè)服務(wù)器套接字并
開(kāi)始監(jiān)聽(tīng)連接。相關(guān)代碼如下:
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(); } } |
關(guān)于PropertyChangeListener, Java提供了一個(gè)非常有力的工具類(lèi)來(lái)
監(jiān)控任意Bean Model的數(shù)據(jù)改變,程序通過(guò)添加該監(jiān)聽(tīng)器實(shí)現(xiàn)對(duì)
SwingWorker的progress屬性值改變的事件捕獲,然后更新JProgressBar
實(shí)例對(duì)象,實(shí)現(xiàn)了UI的刷新。FileTransferServer類(lèi)的完整源代碼如下:
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是服務(wù)器端的文件接受類(lèi):
首先從建立的TCP流中得到文件名與文件大小,然后開(kāi)始接受文件內(nèi)容字節(jié)
并寫(xiě)入創(chuàng)建的文件對(duì)象流中,最后驗(yàn)證文件大小與寫(xiě)入的字節(jié)流是否相等
最后發(fā)送一條消息到文件發(fā)送方,告訴對(duì)方文件傳輸完成,可以關(guān)閉TCP流。
該類(lèi)的完整源代碼如下:
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類(lèi),用來(lái)實(shí)現(xiàn)到服務(wù)端的連接,然后選擇
要傳輸?shù)奈募?圖片,PDF,Word文檔等各種二進(jìn)制文件)。如果沒(méi)有
輸入服務(wù)器信息,會(huì)彈出提示要求輸入。端口已經(jīng)指定為:9999
【send File】按鈕會(huì)打開(kāi)文件選擇框,用戶選擇要傳輸文件以后,創(chuàng)建
FileTransferTask線程,并開(kāi)始執(zhí)行文件傳送。客戶端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; /** * 我一般寫(xiě)英文注釋?zhuān)紶栁乙矔?huì)寫(xiě)中文注釋?zhuān)皇怯X(jué)得寫(xiě)英文 * 注釋跟代碼比較統(tǒng)一,無(wú)他。 */ 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執(zhí)行 } } 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實(shí)現(xiàn)的功能主要有:
- 1. 發(fā)送文件meta信息到接受方(文件名與文件大小)
- 2. 讀取文件內(nèi)容字節(jié)寫(xiě)入Socket字節(jié)流中,發(fā)送到接受方
- 3. 從Socket字節(jié)流中讀取對(duì)方接受完成通知信息,調(diào)用彈出文件傳輸成功信息
該類(lèi)完全源代碼如下:
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 ; } } |
數(shù)據(jù)包類(lèi)如下,不解釋?zhuān)?/p>
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; // 數(shù)據(jù)包中數(shù)據(jù)長(zhǎng)度,兩個(gè)字節(jié) private byte [] databuff; // 數(shù)據(jù)包中數(shù)據(jù),meici最大不超過(guò)2048字節(jié) 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] = ' ' ; } } } } |
每次發(fā)送的最大字節(jié)數(shù)為2048個(gè)字節(jié)。程序最終運(yùn)行效果如下(win7 + JDK6u30):
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助。