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