今天又在论坛上看到一位兄弟,想用Socket编程实现文件传输。
说起来,有好多朋友都想用Socket来传输文件。
如果偏底层一些的话,还要涉及到网络协议的制定和实现,这个对于初学者难度要稍大一些。
于是,我写了一段利用ObjectOutputStream和ObjectInputStream传输文件的代码。
当然,其中我还抄的楼主的一点代码,因为swing方面确实懒得再想了。
有两个帖子,一个因为结贴太早,我就没参与进去。
原帖分别是:http://topic.csdn.net/u/20091031/01/a3f6aab3-c151-46fb-b1fa-40b91e8ec5c9.html?89697
和http://topic.csdn.net/u/20091028/17/a384a213-269d-40aa-b368-d244bc12e198.html?60199
我的代码欢迎大家多提意见。
思路介绍:
采用CS模式设计,客户端采用端连接形式进行消息传递(就是说发一个请求接收一个应答,然后断开连接)。
服务端为简化起见,没有身份验证和Session方面的处理,只做了请求的响答操作。
下面开始秀代码:
既然打算通过传输对象的方式来传输文件,那么,就应该有相应的自定义的Java类,因为,我们知道JDK当中的File对象,没有实现Serializable接口,所以,是不能序列化并传输的。
而C/S的交互过程,很像命令的处理过程,客户端发送命令,服务端执行并应答命令执行的结果。
(一、C/S的公共代码) 于是,开始介绍用于传输的对象,的相关代码。
package houlei.csdn.net.fileTrans.cmd; import java.io.Serializable; /** * 用于表示客户端发起命令的抽象类。 * 程序流程采用客户端发送一个命令, * 到服务端执行一个命令,并返回该命令的执行结果。 *
* 创建时间:2009-10-31 下午01:41:56 * @author 侯磊 * @since 1.0 */ public interface Command extends Serializable{ }package houlei.csdn.net.fileTrans.cmd; /** * 客户端发起的命令请求,要求服务端传出已存在的文件。 *
* 创建时间:2009-10-31 下午02:00:19 * @author 侯磊 * @since 1.0 */ public class TransFileOnExistRequest implements Command { private static final long serialVersionUID = 1L; private String filePath; public TransFileOnExistRequest() { } public TransFileOnExistRequest(String filePath) { this.filePath = filePath; } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } } package houlei.csdn.net.fileTrans.cmd; import java.io.IOException; /** * 服务端对命令的应答。
* 内容包括:文件是否存在,以及文件的byte数组。
* 注意,只能响应小文件,大文件,要重新编写两三个类。 *
* 创建时间:2009-10-31 下午02:00:53 * * @author 侯磊 * @since 1.0 */ public class TransFileOnExistResponse implements Command { private static final long serialVersionUID = 1L; private boolean fileHasExist = false; private byte[] transFile = null; public TransFileOnExistResponse() { } public TransFileOnExistResponse(boolean fileHasExist) { this.fileHasExist = fileHasExist; } public byte[] getTransFile() { return transFile; } public void setTransFile(byte[] transFile) { this.transFile = transFile; } public boolean fileHasExist() { return fileHasExist; } public void setFileHasExist(boolean fileHasExist) { this.fileHasExist = fileHasExist; } private void writeObject(java.io.ObjectOutputStream s) throws IOException { s.defaultWriteObject(); if(fileHasExist==false)return; s.writeInt(transFile.length); s.write(transFile); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); if(fileHasExist==false)return; int size = s.readInt(); transFile = new byte[size]; s.read(transFile); } }
下面介绍从网络读取对象的功能类,当初写这三个类是想增加程序的可扩展性。实际上可以忽略。
package houlei.csdn.net.fileTrans.cmd.reader; import houlei.csdn.net.fileTrans.cmd.Command; import java.io.IOException; /** * 用于程序从网络读取对象功能的抽象。 *
* 创建时间:2009-10-31 下午02:20:28 * @author 侯磊 * @since 1.0 */ public interface CommandReader { public abstract Command read() throws IOException; public abstract boolean tryClose(); } package houlei.csdn.net.fileTrans.cmd.reader; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; /** * 辅助实现程序从网络中读取响应的对象 *
* 创建时间:2009-10-31 下午02:24:38 * @author 侯磊 * @since 1.0 */ public class AbstractCommandReader { protected ObjectInputStream in; public AbstractCommandReader() { super(); } public InputStream getInputStream() { return in; } public void setInputStream(ObjectInputStream in) { this.in = in; } public void setInputStream(InputStream in) throws IOException { this.in = new ObjectInputStream(in); } public boolean tryClose(){ try { in.close(); return true; } catch (Exception e) { return false; } } }package houlei.csdn.net.fileTrans.cmd.reader; import houlei.csdn.net.fileTrans.cmd.Command; import java.io.IOException; /** * 具体读取对象的实现类 *
* 创建时间:2009-10-31 下午02:57:22 * * @author 侯磊 * @since 1.0 */ public class DefaultCommandReader extends AbstractCommandReader implements CommandReader { /* * (非 Javadoc) * * @see houlei.csdn.net.fileTrans.cmd.reader.CommandReader#reader() */ public Command read() throws IOException { try { return (Command) in.readObject(); } catch (ClassNotFoundException e) { throw new IOException(e.getMessage()); } } }
下面是将对象写入网络的功能类。也是想增加程序的可扩展性。也是可以忽略的。下面将这三处代码进行折叠。
package houlei.csdn.net.fileTrans.cmd.writer; import java.io.IOException; import houlei.csdn.net.fileTrans.cmd.Command; /** * 用于程序将对象写入网络功能的抽象 *
* 创建时间:2009-10-31 下午02:22:02 * @author 侯磊 * @since 1.0 */ public interface CommandWriter { public abstract void write(Command cmd) throws IOException; public abstract boolean tryClose(); } package houlei.csdn.net.fileTrans.cmd.writer; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.OutputStream; /** * 辅助实现程序将对象写入网络的功能。 *
* 创建时间:2009-10-31 下午03:05:39 * @author 侯磊 * @since 1.0 */ public abstract class AbstractCommandWriter { protected ObjectOutputStream out; public OutputStream getOutputStream() { return out; } public void setOutputStream(ObjectOutputStream out) { this.out = out; } public void setOutputStream(OutputStream out) throws IOException{ this.out = new ObjectOutputStream(out); } public boolean tryClose(){ try { out.close(); return true; } catch (Exception e) { return false; } } } package houlei.csdn.net.fileTrans.cmd.writer; import houlei.csdn.net.fileTrans.cmd.Command; import java.io.IOException; /** * 具体写入对象操作的实现类。 *
* 创建时间:2009-10-31 下午03:02:58 * * @author 侯磊 * @since 1.0 */ public class DefaultCommandWriter extends AbstractCommandWriter implements CommandWriter { /* (非 Javadoc) * @see houlei.csdn.net.fileTrans.cmd.writer.CommandWriter#write(houlei.csdn.net.fileTrans.cmd.Command) */ public void write(Command cmd) throws IOException { out.writeObject(cmd); out.flush(); } }
(二、服务端的代码)其实网络编程里面,我感觉最难的还是服务端程序的设计与实现。
下面介绍服务端程序执行命令的功能代码。
package houlei.csdn.net.fileTrans.server.action; import houlei.csdn.net.fileTrans.cmd.Command; /** * 服务端处理命令功能的抽象 *
* 创建时间:2009-10-31 下午02:09:57 * @author 侯磊 * @since 1.0 */ public interface CommandAction { public abstract Command doAction(Command request); } package houlei.csdn.net.fileTrans.server.action; import houlei.csdn.net.fileTrans.cmd.Command; import houlei.csdn.net.fileTrans.cmd.TransFileOnExistRequest; import houlei.csdn.net.fileTrans.cmd.TransFileOnExistResponse; import java.io.File; import java.io.FileInputStream; /** * 处理“传输已存在文件”命令功能的实现类 *
* 创建时间:2009-10-31 下午03:22:05 * @author 侯磊 * @since 1.0 */ public class TransFileOnExistAction implements CommandAction { /* (非 Javadoc) * @see houlei.csdn.net.fileTrans.server.action.CommandAction#doAction(houlei.csdn.net.fileTrans.cmd.CommandRequest) */ public Command doAction(Command cmd) { TransFileOnExistRequest request = (TransFileOnExistRequest)cmd; File file = new File(request.getFilePath()); TransFileOnExistResponse resp = new TransFileOnExistResponse(); if(file.exists() && file.isFile() && Integer.MAX_VALUE>file.length()){ resp.setFileHasExist(true); byte [] buff = new byte [(int)file.length()]; FileInputStream fis = null; try { if((fis = new FileInputStream(file)).read(buff)!=buff.length){ resp.setFileHasExist(false); } resp.setTransFile(buff); } catch (Exception e) { resp.setFileHasExist(false); } finally{ try{if(fis!=null)fis.close();}catch(Exception e){} } } return resp; } }
然后应该是将上述代码综合执行的程序了。服务端的程序,为了功能的可扩展性以及可调整性,一般都会读取一些配置文件。
我这里为简化起见,就没写那些代码,只写了一个简单的程序,能看懂,能抛砖引玉就行。
当然,程序当中用“//”注释的三行代码,其实是,如果功能要扩充,注释部分就会自然变动的。这个稍后我会谈到。
package houlei.csdn.net.fileTrans.server; import houlei.csdn.net.fileTrans.cmd.Command; //import houlei.csdn.net.fileTrans.cmd.FileExistsRequest; import houlei.csdn.net.fileTrans.cmd.TransFileOnExistRequest; import houlei.csdn.net.fileTrans.cmd.reader.DefaultCommandReader; import houlei.csdn.net.fileTrans.cmd.writer.DefaultCommandWriter; import houlei.csdn.net.fileTrans.server.action.CommandAction; //import houlei.csdn.net.fileTrans.server.action.FileExistsAction; import houlei.csdn.net.fileTrans.server.action.TransFileOnExistAction; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * 写了一个简单的服务端入口程序,用于实现执行命令的功能。 *
* 创建时间:2009-10-31 下午03:33:29 * @author 侯磊 * @since 1.0 */ public class SimpleServer implements Runnable{ protected Map
(三、客户端的代码)服务端的代码基本介绍完了,下面是客户端的代码。客户端的代码,写到最后已经感觉没啥意思了。
所以,就越发简单了。其实客户端涉及IO(包括网络)操作的代码,都应该采用多线程方式进行的,而并非我的代码那样,
使用swing自己的绘制图形的线程,否则,IO方面的一些问题有可能会影响到用户界面的显示。这个,大家一定要注意。
我这里就简单的写了两个类。
package houlei.csdn.net.fileTrans.client; import java.io.IOException; import java.net.Socket; import java.net.UnknownHostException; import houlei.csdn.net.fileTrans.cmd.Command; import houlei.csdn.net.fileTrans.cmd.reader.DefaultCommandReader; import houlei.csdn.net.fileTrans.cmd.writer.DefaultCommandWriter; /** * 用于完成向服务端发送命令并得到应答的功能。 *
* 创建时间:2009-10-31 下午04:09:34 * @author 侯磊 * @since 1.0 */ public class ClientProxy { private String destHost = "127.0.0.1"; private int port = 8000; public Command send(Command request) throws UnknownHostException, IOException{ Socket socket = new Socket(destHost,port); DefaultCommandReader reader = new DefaultCommandReader(); DefaultCommandWriter writer = new DefaultCommandWriter(); try{ writer.setOutputStream(socket.getOutputStream()); reader.setInputStream(socket.getInputStream()); writer.write(request); return reader.read(); }catch(IOException e){ throw e; }finally{ writer.tryClose(); reader.tryClose(); } } } package houlei.csdn.net.fileTrans.client; import houlei.csdn.net.fileTrans.cmd.TransFileOnExistRequest; import houlei.csdn.net.fileTrans.cmd.TransFileOnExistResponse; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.FileOutputStream; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; /** * 这个是一个简单的客户端图形界面。这里面有些代码是抄别人的。 * 所以,没啥好说的。运行一下,就知道啥功能了。 *
* 创建时间:2009-10-31 下午04:04:07 * @author 侯磊 * @since 1.0 */ public class ClientFrame extends JFrame implements ActionListener{ private static final long serialVersionUID = 1L; private JTextField jtf = new JTextField(); private JTextArea jta = new JTextArea(); private JButton button = new JButton("Enter"); public static void main(String[] args){ new ClientFrame(); } public ClientFrame(){ JPanel p = new JPanel(); p.setLayout(new BorderLayout()); p.add(new JLabel("Enter path: "), BorderLayout.WEST); p.add(jtf, BorderLayout.CENTER); p.add(button, BorderLayout.EAST); jtf.setHorizontalAlignment(JTextField.LEFT); getContentPane().setLayout(new BorderLayout()); getContentPane().add(p, BorderLayout.NORTH); getContentPane().add(new JScrollPane(jta), BorderLayout.CENTER); jtf.addActionListener(this); setTitle("Client"); setSize(500, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setVisible(true); button.addActionListener(this); } public void actionPerformed(ActionEvent e) { if(e.getSource() == button) { String path = jtf.getText(); if(path.equals("")){ jta.append("Please fill the path up" + "/n");return; } ClientProxy cp = new ClientProxy(); TransFileOnExistRequest request = new TransFileOnExistRequest(); request.setFilePath(path); try { TransFileOnExistResponse rsp = (TransFileOnExistResponse)cp.send(request); System.out.println(rsp.fileHasExist());System.out.println(rsp.getTransFile()); if(rsp.fileHasExist()==false){ jta.append("file not found" + "/n"); return; }else{ File file = new File("C:/fileFromServer.txt"); FileOutputStream fos = new FileOutputStream(file); fos.write(rsp.getTransFile()); fos.flush(); fos.close(); jta.append("Successful! The file has been saved in C:" + File.separator + "fileFromServer.txt"+"/n"); } } catch (Exception ex) { jta.append(ex.getMessage()+"/n"); } } } }
最后,介绍一下,服务端功能的扩展。
基于上述代码的程序框架,如果想要多添加一个命令的功能。您所要做的事情,只有三四件。
首先,是该命令的请求和应答类,这两个类的代码您是肯定要写的。
其次,服务端执行该命令的功能类,也是必须要写的。
第三,服务端的配置也要改一下,以便新增的功能类,能够融合进现有的框架中。
第四,恐怕就是客户端,新功能界面的添加了。
对以上四步,我这里举个例子。上面的功能不是要传输以存在的文件么?
我这里举一个更简单的:判断服务端是否存在指定的文件或文件夹。
由于这些代码都不是关键部分,所以,都折叠了。
package houlei.csdn.net.fileTrans.cmd; /** *客户端发起的命令请求, *要求服务端检查是否存在filePath对应的文件或文件夹。 *
* 创建时间:2009-10-31 下午01:51:41 * @author 侯磊 * @since 1.0 */ public class FileExistsRequest implements Command { private static final long serialVersionUID = 1L; private String filePath; public FileExistsRequest() { } public FileExistsRequest(String filePath) { this.filePath = filePath; } public String getFilePath() { return filePath; } public void setFilePath(String filePath) { this.filePath = filePath; } }package houlei.csdn.net.fileTrans.cmd; /** * 服务端对命令的应答。
* 告诉检查结果。 *
* 创建时间:2009-10-31 下午01:55:54 * @author 侯磊 * @since 1.0 */ public class FileExistsResponse implements Command { private static final long serialVersionUID = 1L; private boolean exists; public FileExistsResponse() { } public FileExistsResponse(boolean exists) { this.exists = exists; } public boolean exists() { return exists; } public void setExists(boolean exists) { this.exists = exists; } }package houlei.csdn.net.fileTrans.server.action; import houlei.csdn.net.fileTrans.cmd.Command; import houlei.csdn.net.fileTrans.cmd.FileExistsRequest; import houlei.csdn.net.fileTrans.cmd.FileExistsResponse; import java.io.File; /** * 处理“指定文件(夹)是否存在”命令功能的实现类 *
* 创建时间:2009-10-31 下午03:18:51 * @author 侯磊 * @since 1.0 */ public class FileExistsAction implements CommandAction { /* (非 Javadoc) * @see houlei.csdn.net.fileTrans.server.action.CommandAction#doAction(houlei.csdn.net.fileTrans.cmd.CommandRequest) */ public Command doAction(Command cmd) { FileExistsRequest request =(FileExistsRequest)cmd; boolean exist = new File(request.getFilePath()).exists(); return new FileExistsResponse(exist); } }
至于配置部分,只要将上述的SimpleServer类中用“//”注释的代码恢复,就可以了。
客户端呢,涉及到的应该只是swing方面的一些代码了。我就不献丑了。
感谢大家能够有足够的耐心看我上面的代码。希望能够提出宝贵的意见与建议。