应用层不管用的是什么语言,在网络传输层都是遵循相同的协议(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上编写,同一台电脑上,两端可以同时调试。