清明节放完假刚回来,马上进入到写博文的节奏,今天继续来学习关于传统的Socket编程 - 客户端与服务器端如何进行文件传送的。
在这之前,先来回顾一下上一篇‘Java Socket 01- 常识篇之消息处理’所讲到的内容: 1) 发送消息前如何对消息进行处理 2) 对消息如何进行编码 3)接收消息时如何对消息进行处理 4)发送端与接收端如何进行“对话”式的交互。其中讲到两个重要的概念:① 显式长度(Explicit length) ②基于定界符(Delimiter-based)。
文件的传送包括了两部分:文件的发送与文件的接收。文件发送端、文件的接收端这里分别简称为发送端、接收端。文件的传送也需要发送端与接收端协议好如何传送,比如,文件名,文件内容如何发送,谁先谁后,怎么编码等等。
这里用简单的方法来完成文件的传送:首先发送文件名的长度,然后使用文件名,再发送文件内容的长度,最后发送文件的内容。这里先所以下规则,文件名的长度占4个字节,文件内容的长度占8个字节。如下图:
附件会带上一个完整的源码:(下图是效果图)
下面来一段主要的源码(发送端):
public void uploadFile(String sPath) throws IOException {
long lStart = System.currentTimeMillis();
// check the precondition
FileInputStream oFileInput = new FileInputStream(new File(sPath));
FileChannel oChannel = oFileInput.getChannel();
// the File length
long lLen = oChannel.size();
// the file name
String sFileName = sPath.substring(sPath.lastIndexOf(File.separator) + 1);
byte[] bFileName = sFileName.getBytes("utf-8");
// 发送文件名的长度和文件名到接收端
m_handler.out().write(BasicTypeConverter.intToByte(bFileName.length));
m_handler.out().write(bFileName);
m_handler.out().flush();
// 发送文件内容的长度和文件内容到接收端
m_handler.out().write(BasicTypeConverter.longToByte(lLen));
m_handler.out().flush();
ByteBuffer oBuffer = ByteBuffer.allocate(1024);
while (oChannel.read(oBuffer) != -1) {
oBuffer.flip();
if (oBuffer.hasRemaining()) {
m_handler.out().write(oBuffer.array(), 0, oBuffer.limit());
m_handler.out().flush();
}
oBuffer.clear();
}
oFileInput.close();
oChannel.close();
long lEnd = System.currentTimeMillis();
long lUseTime = lEnd - lStart;
ClientFrame.self.setMsg("文件已经发送完成 ;文件大小:" + lLen/1024 + "KB; 共耗时:"+ lUseTime + "ms ----> "+ sPath);
}
如果对FileChannel 和ByteBuffer不了解,请看一下之前我写的博文,Java NIO里面有提到。这里给个链接http://jimmyhr.iteye.com/admin/categories/272010
下面是一段主要的源码(接收端):
@Override
public void run() {
try {
// 文件名的长度
byte[] bHeadLength = new byte[4];
if (m_oIn.read(bHeadLength) == -1) {
return;
}
// 获得文件名的长度
int iLen = BasicTypeConverter.bytesToInt(bHeadLength);
// 获得文件名
ByteBuffer oBuffer = ByteBuffer.allocate(iLen);
int iR = 0;
while (oBuffer.hasRemaining()) {
byte[] b = new byte[iLen - iR];
iR += m_oIn.read(b);
oBuffer.put(b);
}
String sFileName = new String(oBuffer.array(), "UTF-8");
// 文件的长度
bHeadLength = new byte[8];
m_oIn.read(bHeadLength);
long lFileLen = BasicTypeConverter.byteToLong(bHeadLength);
// 接收文件并保存
long lHasRev = 0;
File oFile = new File(SAVEPATH + sFileName);
if (!oFile.exists()) {
File oDir = oFile.getParentFile();
if (!oDir.exists()) {
oDir.mkdirs();
}
oFile.createNewFile();
}
FileOutputStream oOut = new FileOutputStream(oFile);
while (lHasRev < lFileLen) {
int iAvail = m_oIn.available();
byte[] b = new byte[Math.min((int) (lFileLen - lHasRev), iAvail)];
int iRev = m_oIn.read(b);
lHasRev += iRev;
oOut.write(b, 0, iRev);
oOut.flush();
}
oOut.close();
ServerFrame.m_oFrame.setMsg("文件已经保存在:" + SAVEPATH + sFileName);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
m_oSocket.close();
if (m_oIn != null) {
m_oIn.close();
}
if (m_oOut != null) {
m_oOut.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里可能大家注意到 接收完成后,关闭Socket连接。所以例子中现在只支持单文件传送。大家不妨试试搞一下如何多文件传送。