跨語言文件傳輸的核心在於:**統一數據表示為字節流**,Java 用 byte[],C++ 用 char[],雙方約定協議頭(命令類型、文件名、長度)+ 文件數據體。本文提供完整的 C++ 服務端(Windows Socket API)與 Java 客戶端代碼,詳細講解協議設計、四大常見坑點及解決方案,可直接用於生產環境參考。
+------------------+------------------+------------------+ | 協議頭 (固定長度) | 文件信息頭 | 文件數據體 | +------------------+------------------+------------------+ | CMD(4B) | LEN(4B) | FILE_LEN(8B) | FILE_NAME(260B) | 文件二進制內容 | +------------------+------------------+------------------+ CMD 定義: 0x01 SendFile - 開始傳輸文件 0x02 StopSendFile - 停止傳輸 0x03 SendString - 發送字符串 0x04 FileInfo - 文件元信息
TAG.h)#pragma once
#include
#define BUF_LEN 2048
#define CMD_SEND_FILE 0x01
#define CMD_STOP_SEND 0x02
#define CMD_SEND_STRING 0x03
#define CMD_FILE_INFO 0x04
typedef struct {
int ID; // 命令類型
BYTE lparam[BUF_LEN]; // 參數載荷
} COMMAND;
typedef struct {
long long fileLength; // 文件長度(8 字節,匹配 Java long)
char fileName[MAX_PATH]; // 文件名
} FILEINFO;
SocketHead.h)#pragma once
#include
#pragma comment(lib, "WS2_32.lib")
class CInitSock {
public:
CInitSock(BYTE minorVer = 2, BYTE majorVer = 2) {
WSADATA wsaData;
WORD ver = MAKEWORD(minorVer, majorVer);
if (WSAStartup(ver, &wsaData) != 0) exit(0);
}
~CInitSock() { WSACleanup(); }
};
CPlusServer.cpp)#include "stdafx.h"
#include "SocketHead.h"
#include "TAG.h"
#include
#include
#include
#include // _beginthreadex
using namespace std;
CInitSock initSock;
const int PORT = 8888;
sockaddr_in initAddr() {
sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
return addr;
}
unsigned __stdcall ClientThread(void* param) {
SOCKET clientSock = (SOCKET)param;
COMMAND cmd;
FILEINFO fInfo;
char buffer[BUF_LEN];
// 1. 接收文件信息頭
int ret = recv(clientSock, (char*)&fInfo, sizeof(fInfo), 0);
if (ret <= 0) { closesocket(clientSock); return 0; }
// 2. 發送確認(可選,此處省略)
// 3. 接收文件數據並寫入
string filePath = "upload_" + string(fInfo.fileName);
ofstream outfile(filePath, ios::out | ios::binary); // 關鍵:二進制模式!
if (!outfile) { closesocket(clientSock); return 0; }
long long remaining = fInfo.fileLength;
while (remaining > 0) {
int toRecv = (int)min(remaining, BUF_LEN);
ret = recv(clientSock, buffer, toRecv, 0);
if (ret <= 0) break;
outfile.write(buffer, ret); // 關鍵:write 而非 <<
remaining -= ret;
}
outfile.close();
cout << "File received: " << filePath << " (" << fInfo.fileLength << " bytes)" << endl;
closesocket(clientSock);
return 0;
}
int main() {
SOCKET serverSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in serverAddr = initAddr();
bind(serverSock, (sockaddr*)&serverAddr, sizeof(serverAddr));
listen(serverSock, 5);
cout << "Server started on port " << PORT << endl;
sockaddr_in clientAddr;
int addrLen = sizeof(clientAddr);
while (true) {
SOCKET clientSock = accept(serverSock, (sockaddr*)&clientAddr, &addrLen);
if (clientSock == INVALID_SOCKET) continue;
cout << "Client connected: " << inet_ntoa(clientAddr.sin_addr) << endl;
_beginthreadex(nullptr, 0, ClientThread, (void*)clientSock, 0, nullptr);
}
closesocket(serverSock);
return 0;
}
NetDataTypeTransform.java)package client;
import java.io.UnsupportedEncodingException;
public class NetDataTypeTransform {
public static final String CODING = "GB2312"; // 與 C++ 一致
// int -> 4 字節小端序
public byte[] intToByteArray(int n) {
return new byte[] {
(byte)(n & 0xFF),
(byte)((n >> 8) & 0xFF),
(byte)((n >> 16) & 0xFF),
(byte)((n >> 24) & 0xFF)
};
}
// long -> 8 字節小端序
public byte[] longToByteArray(long n) {
byte[] b = new byte[8];
for (int i = 0; i < 8; i++) b[i] = (byte)((n >> (8*i)) & 0xFF);
return b;
}
// byte[] -> int (小端序)
public int byteArrayToInt(byte[] b) {
if (b.length != 4) return -1;
return ((b[3]&0xFF)<<24) | ((b[2]&0xFF)<<16) | ((b[1]&0xFF)<<8) | (b[0]&0xFF);
}
// byte[] -> long
public long byteArrayToLong(byte[] b) {
if (b.length != 8) return -1;
long v = 0;
for (int i = 7; i >= 0; i--) v = (v<<8) | (b[i]&0xFF);
return v;
}
// String <-> byte[] (GBK)
public String byteArrayToString(byte[] b, int maxLen) {
int len = 0;
while (len < b.length && len < maxLen && b[len] != 0) len++;
try { return new String(b, 0, len, CODING); }
catch (UnsupportedEncodingException e) { return ""; }
}
public byte[] stringToByteArray(String s) {
try { return s.getBytes(CODING); }
catch (UnsupportedEncodingException e) { return new byte[0]; }
}
}
NetDataCommand.java)package client;
public class NetDataCommand {
private int id;
private String fileName;
private NetDataTypeTransform transformer = new NetDataTypeTransform();
public NetDataCommand(int id, String fileName) {
this.id = id;
this.fileName = fileName;
}
// 序列化為:ID(4B) + fileName(260B, GBK, 補0)
public byte[] getByteArrayData() {
byte[] data = new byte[4 + 260];
byte[] idBytes = transformer.intToByteArray(id);
byte[] nameBytes = transformer.stringToByteArray(fileName);
System.arraycopy(idBytes, 0, data, 0, 4);
System.arraycopy(nameBytes, 0, data, 4, Math.min(nameBytes.length, 260));
return data;
}
}
SendFileThread.java)package client;
import java.io.*;
import java.net.Socket;
public class SendFileThread extends Thread {
private Socket socket;
private NetDataTypeTransform transformer = new NetDataTypeTransform();
public SendFileThread(Socket socket) { this.socket = socket; }
@Override
public void run() {
try (DataOutputStream out = new DataOutputStream(socket.getOutputStream());
DataInputStream in = new DataInputStream(socket.getInputStream());
FileInputStream fis = new FileInputStream("F:\\Android\\4.jpg")) {
File file = new File("F:\\Android\\4.jpg");
long fileLen = file.length();
// 1. 發送文件長度 (8 字節 long)
out.write(transformer.longToByteArray(fileLen));
out.flush();
// 2. 等待服務端確認(可選)
// boolean ok = in.readBoolean();
// 3. 分塊發送文件數據
byte[] buffer = new byte[2048];
int len;
long sent = 0;
while ((len = fis.read(buffer)) > 0) {
out.write(buffer, 0, len); // 關鍵:寫實際讀取長度 len,而非 buffer.length!
out.flush();
sent += len;
System.out.printf("Progress: %.2f%%%n", sent * 100.0 / fileLen);
}
System.out.println("Send complete: " + sent + " bytes");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
Client.java)package client;
import java.net.Socket;
import java.io.DataOutputStream;
public class Client {
public static void main(String[] args) {
try (Socket socket = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(socket.getOutputStream())) {
// 發送文件信息命令
NetDataCommand cmd = new NetDataCommand(1, "4.jpg");
out.write(cmd.getByteArrayData());
out.flush();
// 啟動發送線程
new SendFileThread(socket).start();
} catch (Exception e) { e.printStackTrace(); }
}
}
| # | 現象 | 根因分析 | 解決方案 |
|---|---|---|---|
| 1 | 文本文件傳輸出現亂碼 | C++ 用 outfile << buffer 而非 outfile.write(buffer, ret),<< 遇 \0 截斷或格式化 | 統一使用 outfile.write(buffer, ret) 二進制寫入 |
| 2 | 接收文件比發送文件大 | Java 發送時寫 buffer.length 而非實際讀取長度 len,導致填充垃圾數據 | Java 端 out.write(buffer, 0, len);C++ 端 outfile.write(buffer, ret) |
| 3 | 圖片無法打開,多 4 字節 | Java long 8 字節,C++ long 4 字節(32/64 位均為 4B),接收時只讀 4B,剩 4B 滯留緩衝區混入文件數據 | C++ 端 FILEINFO 中 fileLength 改為 long long (8B),recv 大小 sizeof(long long) |
| 4 | 二進制文件(圖片)損壞 | C++ 以文本模式 ios::out 打開文件,Windows 下 \n 被轉為 \r\n | 以二進制模式打開:ios::out | ios::binary |
send/write(buf, actual_len),recv/read(buf, max_len) 返回值為實際收發長度。long(8B) ↔ C++ long long(8B);Java int(4B) ↔ C++ int(4B);字符編碼統一。recv/send 返回值(SOCKET_ERROR/0/>0),網絡斷開時優雅退出。