1. 公共常量类
public class TransferConstants {
public static final int MAGIC_NUMBER = 0x12345678;
public static final int HEADER_SIZE = 20;
public static final int TYPE_META = 1;
public static final int TYPE_DATA = 2;
public static final int TYPE_ACK = 3;
public static final int TYPE_END = 4;
public static final int DEFAULT_CHUNK_SIZE = 1024 * 1024;
public static final int ACK_TIMEOUT = 5000;
public static final int MAX_RETRY = 3;
}
2. 数据包封装类
public class DataPacket {
public int type;
public int seq;
public byte[] data;
public DataPacket(int type, int seq, byte[] data) {
this.type = type;
this.seq = seq;
this.data = data;
}
public byte[] toBytes() {
ByteBuffer buffer = ByteBuffer.allocate(TransferConstants.HEADER_SIZE + (data != null ? data.length : 0) + 4);
buffer.putInt(TransferConstants.MAGIC_NUMBER);
buffer.putInt(type);
buffer.putInt(seq);
buffer.putInt(data != null ? data.length : 0);
if (data != null) {
buffer.put(data);
}
CRC32 crc = new CRC32();
crc.update(buffer.array(), 0, buffer.position());
buffer.putInt((int)crc.getValue());
return buffer.array();
}
public static DataPacket fromBytes(byte[] bytes) throws IOException {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
if (buffer.getInt() != TransferConstants.MAGIC_NUMBER) {
throw new IOException("Invalid magic number");
}
DataPacket packet = new DataPacket(
buffer.getInt(),
buffer.getInt(),
null
);
int length = buffer.getInt();
if (length > 0) {
packet.data = new byte[length];
buffer.get(packet.data);
}
CRC32 crc = new CRC32();
crc.update(bytes, 0, bytes.length - 4);
int expectedCrc = buffer.getInt();
if ((int)crc.getValue() != expectedCrc) {
throw new IOException("CRC check failed");
}
return packet;
}
}
3. 文件发送端实现
public class FileSender {
private Socket socket;
private File file;
private TransferListener listener;
private int chunkSize;
public FileSender(Socket socket, File file, TransferListener listener) {
this(socket, file, listener, TransferConstants.DEFAULT_CHUNK_SIZE);
}
public FileSender(Socket socket, File file, TransferListener listener, int chunkSize) {
this.socket = socket;
this.file = file;
this.listener = listener;
this.chunkSize = chunkSize;
}
public void send() throws IOException {
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataInputStream dis = new DataInputStream(socket.getInputStream());
try (FileInputStream fis = new FileInputStream(file)) {
sendMetadata(dos, file);
if (!waitForAck(dis, 0)) {
throw new IOException("Metadata ACK timeout");
}
byte[] buffer = new byte[chunkSize];
int bytesRead;
int seq = 1;
int totalChunks = (int) Math.ceil((double) file.length() / chunkSize);
while ((bytesRead = fis.read(buffer)) != -1) {
boolean ackReceived = false;
int retryCount = 0;
byte[] chunkData = Arrays.copyOf(buffer, bytesRead);
while (!ackReceived && retryCount < TransferConstants.MAX_RETRY) {
try {
sendDataPacket(dos, seq, chunkData);
if (listener != null) {
listener.onProgress(seq, totalChunks);
}
if (waitForAck(dis, seq)) {
ackReceived = true;
seq++;
} else {
retryCount++;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IOException("Transfer interrupted");
}
}
if (!ackReceived) {
throw new IOException("Failed after " + TransferConstants.MAX_RETRY + " retries");
}
}
sendEndPacket(dos);
if (!waitForAck(dis, 0)) {
throw new IOException("End ACK timeout");
}
if (listener != null) {
listener.onComplete();
}
}
}
private void sendMetadata(DataOutputStream dos, File file) throws IOException {
JSONObject meta = new JSONObject();
try {
meta.put("name", file.getName());
meta.put("size", file.length());
meta.put("chunks", (int) Math.ceil((double) file.length() / chunkSize));
} catch (JSONException e) {
throw new IOException("Failed to create metadata", e);
}
DataPacket packet = new DataPacket(
TransferConstants.TYPE_META,
0,
meta.toString().getBytes(StandardCharsets.UTF_8)
);
dos.write(packet.toBytes());
dos.flush();
}
private void sendDataPacket(DataOutputStream dos, int seq, byte[] data) throws IOException {
DataPacket packet = new DataPacket(
TransferConstants.TYPE_DATA,
seq,
data
);
dos.write(packet.toBytes());
dos.flush();
}
private void sendEndPacket(DataOutputStream dos) throws IOException {
DataPacket packet = new DataPacket(
TransferConstants.TYPE_END,
0,
null
);
dos.write(packet.toBytes());
dos.flush();
}
private boolean waitForAck(DataInputStream dis, int expectedSeq) throws IOException, InterruptedException {
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < TransferConstants.ACK_TIMEOUT) {
if (dis.available() > 0) {
byte[] header = new byte[TransferConstants.HEADER_SIZE];
dis.readFully(header);
ByteBuffer buffer = ByteBuffer.wrap(header);
if (buffer.getInt() == TransferConstants.MAGIC_NUMBER) {
int type = buffer.getInt();
int seq = buffer.getInt();
int length = buffer.getInt();
if (type == TransferConstants.TYPE_ACK && seq == expectedSeq) {
byte[] remaining = new byte[length + 4];
dis.readFully(remaining);
return true;
}
}
}
Thread.sleep(100);
}
return false;
}
public interface TransferListener {
void onProgress(int currentChunk, int totalChunks);
void onComplete();
void onError(String message);
}
}
4. 文件接收端实现
public class FileReceiver {
private Socket socket;
private String saveDir;
private TransferListener listener;
public FileReceiver(Socket socket, String saveDir, TransferListener listener) {
this.socket = socket;
this.saveDir = saveDir;
this.listener = listener;
}
public void receive() throws IOException {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
DataPacket metaPacket = readPacket(dis);
if (metaPacket.type != TransferConstants.TYPE_META) {
throw new IOException("Expected metadata packet");
}
JSONObject meta;
try {
meta = new JSONObject(new String(metaPacket.data, StandardCharsets.UTF_8));
} catch (JSONException e) {
throw new IOException("Invalid metadata format", e);
}
String fileName = meta.getString("name");
long fileSize = meta.getLong("size");
int totalChunks = meta.getInt("chunks");
sendAck(dos, 0);
File file = new File(saveDir, fileName);
try (FileOutputStream fos = new FileOutputStream(file)) {
int expectedSeq = 1;
Set<Integer> receivedChunks = new HashSet<>();
while (true) {
DataPacket packet = readPacket(dis);
switch (packet.type) {
case TransferConstants.TYPE_DATA:
if (packet.seq == expectedSeq) {
fos.write(packet.data);
receivedChunks.add(packet.seq);
expectedSeq++;
if (listener != null) {
listener.onProgress(receivedChunks.size(), totalChunks);
}
} else if (packet.seq < expectedSeq) {
}
sendAck(dos, packet.seq);
break;
case TransferConstants.TYPE_END:
sendAck(dos, 0);
if (listener != null) {
listener.onComplete();
}
return;
default:
throw new IOException("Unexpected packet type: " + packet.type);
}
}
}
}
private DataPacket readPacket(DataInputStream dis) throws IOException {
byte[] header = new byte[TransferConstants.HEADER_SIZE];
dis.readFully(header);
ByteBuffer buffer = ByteBuffer.wrap(header);
if (buffer.getInt() != TransferConstants.MAGIC_NUMBER) {
throw new IOException("Invalid magic number");
}
int type = buffer.getInt();
int seq = buffer.getInt();
int length = buffer.getInt();
byte[] data = null;
if (length > 0) {
data = new byte[length];
dis.readFully(data);
}
int crc = dis.readInt();
CRC32 crc32 = new CRC32();
crc32.update(header);
if (data != null) {
crc32.update(data);
}
if ((int)crc32.getValue() != crc) {
throw new IOException("CRC check failed");
}
return new DataPacket(type, seq, data);
}
private void sendAck(DataOutputStream dos, int seq) throws IOException {
DataPacket ack = new DataPacket(
TransferConstants.TYPE_ACK,
seq,
null
);
dos.write(ack.toBytes());
dos.flush();
}
public interface TransferListener {
void onProgress(int receivedChunks, int totalChunks);
void onComplete();
void onError(String message);
}
}
三、使用示例
1. 发送文件
new Thread(() -> {
try {
Socket socket = new Socket("192.168.1.100", 8888);
File file = new File("/sdcard/largefile.zip");
FileSender sender = new FileSender(socket, file, new FileSender.TransferListener() {
@Override
public void onProgress(int currentChunk, int totalChunks) {
runOnUiThread(() -> {
progressBar.setMax(totalChunks);
progressBar.setProgress(currentChunk);
});
}
@Override
public void onComplete() {
runOnUiThread(() -> Toast.makeText(this, "传输完成", Toast.LENGTH_SHORT).show());
}
@Override
public void onError(String message) {
runOnUiThread(() -> Toast.makeText(this, "错误: " + message, Toast.LENGTH_SHORT).show());
}
});
sender.send();
} catch (IOException e) {
e.printStackTrace();
}
}).start();
2. 接收文件
new Thread(() -> {
try {
ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
FileReceiver receiver = new FileReceiver(socket, Environment.getExternalStorageDirectory().getPath(),
new FileReceiver.TransferListener() {
@Override
public void onProgress(int receivedChunks, int totalChunks) {
runOnUiThread(() -> {
progressBar.setMax(totalChunks);
progressBar.setProgress(receivedChunks);
});
}
@Override
public void onComplete() {
runOnUiThread(() -> Toast.makeText(this, "接收完成", Toast.LENGTH_SHORT).show());
}
@Override
public void onError(String message) {
runOnUiThread(() -> Toast.makeText(this, "错误: " + message, Toast.LENGTH_SHORT).show());
}
});
receiver.receive();
} catch (IOException e) {
e.printStackTrace();
}
}).start();