C++程序與Java程序網絡傳輸文件測試

應用層不管用的是什麼語言,在網絡傳輸層都是遵循相同的協議(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上編寫,同一臺電腦上,兩端可以同時調試。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章