摘要:
本文通过Java中提供的Serialization机制,通过网络(Socket)实现任意文件在不同机器之间的传输。本文将包含两个实验的代码:
1. 将文件从一个电脑(客户端),传送到另外一个电脑(服务端);
2. 将文件从一个Android 2.1手机(客户端),通过WiFi传送到电脑(服务端)。
很多朋友发现在两台机器之间,通过TCP socket传送文件的时候,所得到的文件要么比源文件大,要么比源文件小。尤其是在传递多媒体文件的时候,由于文件大小的变化,最终导致多媒体文件无法正常播放。造成这种情况的原因有很多,但100%是程序自身的问题。同样,解决的办法也有很多,本文所介绍的两个小实验,就是试图采用Java中提供的Serialization机制,来解决这样的问题。
实验一:
1. 先创建一个Java项目,项目名为ObjectClient,作为客户端应用。在这个项目中创建一个实现了Serializable接口的类FilePojo,代码如下:
package com.pnf.transfer;
import java.io.Serializable;
// 必须实现Serializable接口。否则无法调用ObjectOutputStream的
// writeObject方法,或者ObjectInputStream中的readObject方法
public class FilePojo implements Serializable
{
private static final long serialVersionUID = 1L;
private String fileName; // 文件名称
private long fileLength; // 文件长度
private byte[] fileContent; // 文件内容
public String getFileName()
{
return fileName;
}
public void setFileName(String fileName)
{
this.fileName = fileName;
}
public long getFileLength() {
return fileLength;
}
public void setFileLength(long fileLength){
this.fileLength = fileLength;
}
public byte[] getFileContent() {
return fileContent;
}
public void setFileContent(byte[] fileContent) {
this.fileContent = fileContent;
}
}
这个类将在服务器端也会被用到。
2. 编写客户端代码如下
package com.pnf.transfer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
public class ObjectClient{
public static void main(String[] args)
{
String serverName = "192.168.0.10"; // 服务器IP
int port = 6666; // 服务器端口
String filePath = "E:\\"; // 要传递文件的路径
String fileName = "full.jpg"; // 要传递文件的名称
try
{
// 连接服务器
System.out.println("Connecting to " + serverName + " on port " + port);
Socket client = new Socket(serverName, port);
System.out.println("Just connected to " + client.getRemoteSocketAddress());
// 创建FilePojo对象
FilePojo fpo = new FilePojo();
// 设定文件名
fpo.setFileName(fileName);
// 设定文件大小
File f = new File(filePath + fileName);
long fileLength = f.length();
fpo.setFileLength(fileLength);
// 读取文件内容,并将其转换为byte[]
FileInputStream fis = new FileInputStream(filePath + fileName);
byte[] fileContent = new byte[(int) fileLength];
fis.read(fileContent, 0, (int) fileLength);
fpo.setFileContent(fileContent);
// 将FilePojo对象fpo写到Socket client指定的输出流
long start = System.currentTimeMillis();
ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
oos.writeObject(fpo);
long end = System.currentTimeMillis();
System.out.println("It takes " + (end - start) + "ms");
oos.flush();
oos.close();
} catch(IOException e) {
e.printStackTrace();
}
}
}
3. 创建另外一个Java项目,项目名为ObjectServer,作为服务端应用。把第1步中的FilePojo.java拷贝到这个项目中来。
4. 编写服务端代码如下:
package com.pnf.transfer;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ObjectServer{
public static void main(String[] args) throws ClassNotFoundException {
ServerSocket serverSocket;
FileOutputStream fos;
try {
serverSocket = new ServerSocket(6666);
while(true) {
Socket clientSocket = serverSocket.accept();
// 从clientSocket获取ObjectInputStream对象
ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream());
// 读取从客户端传递过来的FilePojo对象
FilePojo fpo = (FilePojo) ois.readObject();
System.out.println(fpo.getFileName());
System.out.println(fpo.getFileLength());
// 初始化FileOutputStream对象fos
fos = new FileOutputStream("D:\\" + fpo.getFileName());
// 将fpo中的内容写入fpo
fos.write(fpo.getFileContent(), 0, (int)fpo.getFileLength());
fos.close();
ois.close();
}
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
}
5. 测试。
先运行服务端,在运行客户端。
在局域网上,传输的速度大致为:传输162149bytes需要20ms,即8KB/ms或8MB/s
实验二:
服务端的代码和实验一中的一样。
1. 创建一个Android项目,项目名为AndroidObjectClient作为客户端。并将实验一中的FilePojo.java拷贝到该项目中。
2. 修改main.xml如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button android:id="@+id/btn_send"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Send a picture to PC!"
>
</Button>
</LinearLayout>
3. 编写Activity的代码如下:
package com.pnf.transfer;
import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class AndroidObjectClientActivity extends Activity
implements
OnClickListener
{
private Button btn_send;
private String filePath = "/sdcard/image/";
private String fileName = "bln.jpg";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn_send = (Button)this.findViewById(R.id.btn_send);
btn_send.setOnClickListener(this);
}
public void onClick(View v)
{
try
{
// 建立和服务器的连接
Socket socket = new Socket("192.168.0.10", 6666);
// 创建FilePojo对象
FilePojo fpo = new FilePojo();
// 设定文件名
fpo.setFileName(fileName);
// 设定文件大小
File f = new File(filePath + fileName);
long fileLength = f.length();
fpo.setFileLength(fileLength);
// 读取文件内容,并将其转换为byte[]
FileInputStream fis = new FileInputStream(filePath + fileName);
byte[] fileContent = new byte[(int) fileLength];
fis.read(fileContent, 0, (int) fileLength);
fpo.setFileContent(fileContent);
// 将FilePojo对象fpo写到Socket client指定的输出流
long start = System.currentTimeMillis();
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(fpo);
long end = System.currentTimeMillis();
Toast.makeText(this.getApplicationContext(), "It takes " + (end - start) + "ms", Toast.LENGTH_LONG).show();
oos.flush();
oos.close();
}
catch(Exception ioe) {
ioe.printStackTrace();
}
}
}
4. 在AndroidManifest.xml中增加permission如下(粗体字部分):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.pnf.transfer"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AndroidObjectClientActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="7" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
第一个permission,表示该程序可以读取sd卡上的内容,第二个permission,表示该程序可以联网(因为我们用到了TCP/IP)。
5. 在手机上安装编译好的AndroidObjectClient.apk。
6. 运行。
先运行服务端,再在手机上运行客户端。
在WLAN(802.11g)网上,传输的速度大致为:传输218089bytes需要200ms,即1.1KB/ms或1.1MB/s。如果是802.11n的话,传输速度应该会快很多。
当然,也可以将一个文件,通过DDMS放到模拟器中模拟的SD卡里面,然后在模拟器中运行客户端,这点在实验中也经过验证了的。