一个基于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):
以上就是本文的全部内容,希望对大家的学习有所帮助。