應用層不管用的是什麼語言,在網絡傳輸層都是遵循相同的協議(TCP/UDP......)。本文通過一個小例子測試了在同一臺機器上,C++程序和Java程序之間傳輸大文件。Java程序作爲服務器,監聽本地端口號:12345。C++程序作爲客戶端,連接上服務器後發送傳輸文件請求,服務器接收請求後把一個大文件發送給客戶端。(使用TCP協議)
數據包包頭部分的定義特別重要,由於這裏不考慮那麼複雜,每個數據包的第一個字節定義爲不同的請求類型,如下:
#pragma once
enum{
C2S_FILE = 1,
};
enum{
S2C_FILE = 1,
};
enum {
S2C_FILE_INFO = 1,
S2C_FILE_BUFF,
S2C_FILE_ERROR,
};
enum{
C2S_FILE_ASK = 1,
C2S_FILE_READY,
};
struct FileInfo
{
char szName[MAX_PATH];
UINT nSize;
};
C2S_是客戶端發送給服務器的請求標識,S2C_則是服務器返回給客戶端的標識。不同的標識,其對應的後面的數據傳輸結構也不一樣。
Java代碼
public class socketserver {
public static final int C2S_FILE = 0x01;
public static final int C2S_FILE_ASK = 1;
public static final int C2S_FILE_READY = 2;
public static final int S2C_FILE = 1;
public static final int S2C_FILE_INFO = 1;
public static final int S2C_FILE_BUFF = 2;
public static final int S2C_FILE_END = 3;
public static void main(String arv[]){
String str = "123";
str += 4 + 5;
System.out.println("Hello world!");
ServerSocket server = null;
Socket client = null;
InputStream is = null;
OutputStream os = null;
FileInputStream fis = null;
try {
server = new ServerSocket(12345);
//InetSocketAddress addr = new InetSocketAddress(12345);
client = server.accept();
is = client.getInputStream();
os = client.getOutputStream();
int nSize = 1024, nSendSize = 1024+100;
byte szBuffer[] = new byte[nSize];
byte szSendBuffer[] = new byte[nSendSize];
int nRead = 0;
byte bHeader[] = new byte[6];
bHeader[0] = (byte)S2C_FILE;
bHeader[1] = (byte)S2C_FILE_INFO;
byte szFile[] = new byte[264];
String strFile = new String("D:\\CefCode.zip");
String strName = new String("CefCode.zip");
File file = new File(strFile);
int nFileSize = (int)(file.length());
fis = new FileInputStream(file);
boolean bFinish = false;
while( !bFinish ){
nRead = is.read(szBuffer, 0, nSize);
if ( nRead == -1 )
break;
System.out.println("接收到" + nRead + "個字節的數據");
int nFlag = (int)szBuffer[0];
int nStatus = (int)szBuffer[1];
switch( nFlag )
{
case C2S_FILE: {// 客戶端請求文件
if ( nStatus == C2S_FILE_ASK ){
System.arraycopy(strName.getBytes(), 0, szFile, 0, strName.length());
byte b[] = IntToByteArray(nFileSize);
System.arraycopy(b, 0, szFile, 260, 4);
System.arraycopy(bHeader, 0, szSendBuffer, 0, 6);
System.arraycopy(szFile, 0, szSendBuffer, 6, 264);
os.write(szSendBuffer, 0, 270);
break;
}
if ( nStatus == C2S_FILE_READY ){
int nReadBytes;
bHeader[1] = (byte)S2C_FILE_BUFF;
byte bFileBuff[] = new byte[1024];
while( ( nReadBytes = fis.read(bFileBuff, 0, 1024) ) != -1 ){
byte bSize[] = IntToByteArray(nReadBytes);
System.arraycopy(bSize, 0, bHeader, 2, 4);
//寫入發送緩衝區
System.arraycopy(bHeader, 0, szSendBuffer, 0, 6);
System.arraycopy(bFileBuff, 0, szSendBuffer, 6, nReadBytes);
os.write(szSendBuffer, 0, 6+nReadBytes);
System.out.println("本次發送出去" + 6+nReadBytes + "字節的數據!");
}
System.out.println("文件發送完畢!");
bFinish = true;
break;
}
}
default:
break;
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try{
if ( server != null )
server.close();
if ( client != null )
client.close();
if ( is != null )
is.close();
if ( os != null )
os.close();
if ( fis != null )
fis.close();
}
catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static byte[] IntToByteArray(int i) {
byte[] result = new byte[4];
//由高位到低位
// result[0] = (byte)((i >> 24) & 0xFF);
// result[1] = (byte)((i >> 16) & 0xFF);
// result[2] = (byte)((i >> 8) & 0xFF);
// result[3] = (byte)(i & 0xFF);
result[2] = (byte)((i >> 16) & 0xFF);
result[3] = (byte)((i >> 24) & 0xFF);
result[0] = (byte)(i & 0xFF);
result[1] = (byte)((i >> 8) & 0xFF);;
return result;
}
public static int ByteArrayToInt(byte[] bytes) {
int value= 0;
//由高位到低位
for (int i = 0; i < 4; i++) {
int shift= (4 - 1 - i) * 8;
value +=(bytes[i] & 0x000000FF) << shift;//往高位遊
}
return value;
}
C++代碼
#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32")
#include <iostream>
using std::cout;
using std::endl;
#include <process.h>
#include "Define.h"
SOCKET g_skBeat = INVALID_SOCKET;
SOCKET g_skClient = INVALID_SOCKET;
HANDLE g_hBeatThread = NULL;
HANDLE g_hBeatEvent = NULL;
bool WriteRecvBuff(const char* pFileName, byte* lpBuff, UINT nBuffLen, OUT byte** ppRemain, OUT UINT* nRemainLen);
UINT __stdcall HeartbeatThread(void* lpParam)
{
const char* pBeat = "1";
int nSendCount = 0, nRet = 0, nRecvCount = 0;
char szBuffer[10];
while (true)
{
if (SOCKET_ERROR == send(g_skBeat, pBeat, 1, 0))
{
nSendCount++;
cout << "發送心跳包失敗" << endl;
if (nSendCount > 10)
{
cout << "發送失敗超過10次,心跳線程退出!" << endl;
break;
}
continue;
}
nSendCount = 0;
nRet = recv(g_skBeat, szBuffer, 10, 0);
if (SOCKET_ERROR == nRet)
{
nRecvCount++;
cout << "接收心跳包失敗" << endl;
if (nRecvCount > 10)
{
cout << "接收失敗次數超過10次,心跳線程退出!" << endl;
break;
}
continue;
}
if (nRet != 1 || szBuffer[0] != '1')
{
cout << "心跳標識不正確,心跳線程退出!" << endl;
break;
}
nRet = WaitForSingleObject(g_hBeatEvent, 3000);
if (nRet != WAIT_TIMEOUT)
{
cout << "事件有信號,心跳線程退出!";
break;
}
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
int k = 0xfff0;
int* pk = &k;
WSAData data;
WSAStartup(MAKEWORD(2, 2), &data);
SOCKET sk = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
SOCKADDR_IN addr;
int nRet, nError;
const char* pServer = "127.0.0.1";
const unsigned short nPort = 12345;
const int nRecvSize = 1024 * 4;
char szBuffer[100];
byte* pRecv = (byte*)malloc(nRecvSize), *pRemain = NULL;
byte* pRemainBuff = (byte*)malloc(nRecvSize * 2);
UINT nWriteSize = 0, nRemainLen = 0;
FileInfo fi;
if (sk == INVALID_SOCKET)
{
cout << "創建套接字失敗,系統錯誤碼:" << WSAGetLastError() << endl;
goto __end;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(nPort);
addr.sin_addr.S_un.S_addr = inet_addr(pServer);
while (true)
{
nRet = WSAConnect(sk, (sockaddr*)&addr, sizeof(SOCKADDR_IN), NULL, NULL, NULL, NULL);
if (nRet != SOCKET_ERROR)
{
cout << "成功連接到服務器:" << pServer << ",端口號:" << nPort << endl;
break;
}
nError = WSAGetLastError();
Sleep(1000);
}
//發送文件請求
byte bflag[2] = { C2S_FILE, C2S_FILE_ASK };
memcpy(szBuffer, &bflag, 2);
nRet = send(sk, szBuffer, 2, 0);
if (SOCKET_ERROR == nRet)
{
cout << "發送請求失敗!" << endl;
goto __end;
}
bool bFile = false;
//接收文件
while (true)
{
nRet = recv(sk, (char*)pRecv, nRecvSize, 0);
if (SOCKET_ERROR == nRet)
break;
if (!bFile)
{
int nLen = sizeof(FileInfo);
ZeroMemory(&fi, nLen);
memcpy(&fi, pRecv + 6, nLen);
cout << "文件名稱:" << fi.szName << "文件大小:" << fi.nSize << endl;
bflag[1] = C2S_FILE_READY;
memcpy(szBuffer, &bflag, 2);
nRet = send(sk, szBuffer, 2, 0);
if (SOCKET_ERROR == nRet)
{
cout << "發送請求失敗!" << endl;
goto __end;
}
bFile = true;
}
else
{
if (nRemainLen > 0)
{
memcpy(pRemainBuff, pRemain, nRemainLen);
memcpy(pRemainBuff + nRemainLen, pRecv, nRet);
WriteRecvBuff(fi.szName, pRemainBuff, nRet + nRemainLen, &pRemain, &nRemainLen);
}
else
WriteRecvBuff(fi.szName, pRecv, nRet, &pRemain, &nRemainLen);
}
}
__end:
//SetEvent(g_hBeatEvent);
//WaitForSingleObject(g_hBeatThread, INFINITE);
//CloseHandle(g_hBeatThread);
if (sk != INVALID_SOCKET)
closesocket(sk);
WSACleanup();
free(pRemainBuff);
free(pRecv);
return 0;
}
bool WriteRecvBuff(const char* pFileName, byte* lpBuff, UINT nBuffLen, OUT byte** ppRemain, OUT UINT* nRemainLen )
{
UINT nPkgSize = 0, nPos = 0;
while ( true )
{
memcpy(&nPkgSize, lpBuff + 2, 4);
if (nPos+nPkgSize+6 > nBuffLen)
{
*nRemainLen = nBuffLen - nPos;
*ppRemain = (byte*)malloc(*nRemainLen);
memcpy(*ppRemain, lpBuff+nPos, *nRemainLen);
return true;
}
FILE* fp = fopen(pFileName, "ab+");
fwrite(lpBuff + 6 + nPos, 1, nPkgSize, fp);
fclose(fp);
nPos += 6 + nPkgSize;
if (nPos >= nBuffLen)
break;
}
*ppRemain = NULL;
*nRemainLen = 0;
return false;
}
有一個地方需要特別注意:Big Endian 和 Little Endian的區別。在Windows上x86架構的CPU一般都是Little Endian,也就是說0xFFF0在內存中,地址從低到高存儲的是 F0 FF;在手機上ARM架構的COU一般都是Big Endian,0XFFF0在內存中地址從低到高存儲的是 FF F0。因此,兩個端在傳遞數值數據時一定要對這個進行統一處理!
C++程序在Visual Studio上編寫,Java程序在Eclipse上編寫,同一臺電腦上,兩端可以同時調試。