天天看點

使用TCP協定實作檔案傳輸

使用TCP協定實作檔案傳輸。程式會分為伺服器端和用戶端,首先運作伺服器端,監聽來自用戶端的連接配接,用戶端運作後會通過程式内的伺服器端IP位址,向伺服器發送連接配接請求。雙方建立請求之後,用戶端将所需檔案的檔案名和絕對路徑傳輸給伺服器,如果伺服器找到此檔案,則将此檔案傳輸給用戶端,然後斷開連接配接。

具體算法描述如下:

【1】伺服器端:

1、初始化socket服務

2、監聽連接配接請求并做相應的處理

2.1建立監聽套接字

2.2監聽套接口

2.3接受套接字的連接配接

2.4接收用戶端傳來的資料

case 檔案絕對路徑:

按照路徑找到檔案,并打開。提取本地檔案名,發回給用戶端

發送檔案總長度給用戶端

case 已準備接收檔案完畢

if 發送緩沖區為空

讀取檔案,寫入緩沖區

将檔案流分成大小相同的組(最後一組可能會小一點),順次發送給用戶端

将緩沖區清空

case 檔案成功傳送

列印消息,退出

case 檔案已存在

列印消息,退出

2.5關閉同用戶端的連接配接

3、釋放socket服務

【2】用戶端:

1、初始化socket,winsock服務

2、連接配接伺服器,進行資料的傳輸

2.1初始化,建立套接字

2.2通過IP位址,向伺服器發送連接配接請求,建立連接配接

2.3主動發送所求檔案絕對路徑

2.4接受伺服器端資料并做相應處理

case 打開檔案錯誤:

重新發送檔案絕對路徑至伺服器,請求重發

case 檔案長度:

列印消息

case 檔案名:

if 檔案已經存在

發送“檔案已經存在”

else

配置設定緩沖區,并向伺服器發送“Ready”消息

case 檔案流:

為已接收檔案名建立檔案

打開檔案,将檔案流資料寫入檔案,直至接收所有分組資料

發送“成功接收“消息

3、關閉套接字

釋放服務

源程式:

【1】伺服器端:

頭檔案:

/*server.h*/
#pragma comment(lib, "WS2_32")
#include <WinSock2.h>
#include <iostream>
#include <assert.h>
#include<Windows.h>
#ifndef COMMONDEF_H
#define COMMONDEF_H
#define MAX_PACKET_SIZE   10240    // 資料包的最大長度,機關是sizeof(char)
#define MAXFILEDIRLENGTH 256     // 存放檔案路徑的最大長度
#define PORT     4096    // 端口号
//#define SERVER_IP    "127.0.0.1" // server端的IP位址
// 各種消息的宏定義
#define INVALID_MSG      -1   // 無效的消息辨別
#define MSG_FILENAME     1   // 檔案的名稱
#define MSG_FILELENGTH     2   // 傳送檔案的長度
#define MSG_CLIENT_READY    3   // 用戶端準備接收檔案
#define MSG_FILE      4   // 傳送檔案
#define MSG_SENDFILESUCCESS    5   // 傳送檔案成功
#define MSG_OPENFILE_ERROR    10   // 打開檔案失敗,可能是檔案路徑錯誤找不到檔案等原因
#define MSG_FILEALREADYEXIT_ERROR 11   // 要儲存的檔案已經存在了
class CCSDef
{
public:
#pragma pack(1)      // 使結構體的資料按照1位元組來對齊,省空間
// 消息頭
struct TMSG_HEADER
{
   char    cMsgID;    // 消息辨別
   TMSG_HEADER(char MsgID = INVALID_MSG)
    : cMsgID(MsgID)
   {
   }
};
// 請求傳送的檔案名
// 用戶端傳給伺服器端的是全路徑名稱
// 伺服器傳回給用戶端的是檔案名
struct TMSG_FILENAME : public TMSG_HEADER
{
   char szFileName[256];   // 儲存檔案名的字元數組
   TMSG_FILENAME()
    : TMSG_HEADER(MSG_FILENAME)
   {
   }
};
// 傳送檔案長度
struct TMSG_FILELENGTH : public TMSG_HEADER
{
   long lLength;
   TMSG_FILELENGTH(long length)
    : TMSG_HEADER(MSG_FILELENGTH), lLength(length) 
   {
   }
};
// Client端已經準備好了,要求Server端開始傳送檔案
struct TMSG_CLIENT_READY : public TMSG_HEADER
{
   TMSG_CLIENT_READY()
    : TMSG_HEADER(MSG_CLIENT_READY)
   {
   }
};
// 傳送檔案
struct TMSG_FILE : public TMSG_HEADER
{
   union     // 采用union保證了資料包的大小不大于MAX_PACKET_SIZE * sizeof(char)
   {
    char szBuff[MAX_PACKET_SIZE];
    struct
    {
     int nStart;
     int nSize;
     char szBuff[MAX_PACKET_SIZE - 2 * sizeof(int)];
    }tFile;
   };
   TMSG_FILE()
    : TMSG_HEADER(MSG_FILE)
   {
   }
};
// 傳送檔案成功
struct TMSG_SENDFILESUCCESS : public TMSG_HEADER
{
   TMSG_SENDFILESUCCESS()
    : TMSG_HEADER(MSG_SENDFILESUCCESS)
   {
   }
};
// 傳送出錯資訊,包括:
// MSG_OPENFILE_ERROR:打開檔案失敗
// MSG_FILEALREADYEXIT_ERROR:要儲存的檔案已經存在了
struct TMSG_ERROR_MSG : public TMSG_HEADER
{
   TMSG_ERROR_MSG(char cErrorMsg)
    : TMSG_HEADER(cErrorMsg)
   {
   }
};
#pragma pack()
};
#endif
           

cpp檔案:

/*Server.cpp*/
#include"Server.h"
char g_szNewFileName[MAXFILEDIRLENGTH];
char g_szBuff[MAX_PACKET_SIZE + 1];
long g_lLength;
char* g_pBuff = NULL;
//初始化socket庫
bool InitSocket();
//關閉socket庫
bool CloseSocket();
//解析消息并進行相應的處理
bool ProcessMsg(SOCKET sClient);
//監聽Client消息
void ListenToClient();
//打開檔案
bool OpenFile(CCSDef::TMSG_HEADER* pMagHeader,SOCKET sClient);
//傳送檔案
bool SendFile(SOCKET sClient);
//讀取檔案進緩沖區
bool ReadFile(SOCKET sClient);

int main()
{
	while(1)
	{
		InitSocket();
		ListenToClient();
		CloseSocket();
		system("del E:\\test1.A_exp");
	}
		//system("pause");
	return 0;
}

//初始化socket庫
bool InitSocket()
{
	WSADATA wsaData;
	WORD socketVersion=MAKEWORD(2,2);
	if(::WSAStartup(socketVersion,&wsaData)!=0)
	{//初始化WinSock服務
		printf("Init socket dll error\n");
		return false;
	}
	return true;
}
//關閉socket庫
bool CloseSocket()
{//釋放winsock庫
	::WSACleanup();
	if(g_pBuff != NULL)
	{
		delete [] g_pBuff;
		g_pBuff = NULL;
	}
	return true;
}
//解析消息并進行相應的處理
bool ProcessMsg(SOCKET sClient)
{
	//從套接口中接收資料,傳回copy的位元組數
	int nRecv = ::recv(sClient,g_szBuff,MAX_PACKET_SIZE+1,0);
	if(nRecv>0)
	{
		g_szBuff[nRecv]='\0';
	}
	//解析指令
	CCSDef::TMSG_HEADER* pMsgHeader=(CCSDef::TMSG_HEADER*)g_szBuff;

	switch(pMsgHeader->cMsgID)
	{
	case MSG_FILENAME://檔案名
		{
			OpenFile(pMsgHeader,sClient);
		}
		break;
	case MSG_CLIENT_READY://用戶端已準備完畢,開始傳送檔案
		{
			SendFile(sClient);
		}
		break;
	case MSG_SENDFILESUCCESS://傳送檔案成功
		{
			printf("Send File Success!\n");
			return false;
		}
		break;
	case MSG_FILEALREADYEXIT_ERROR://要儲存的檔案已經存在
		{
			printf("The file ready to send already exit!\n");
			return false;
		}
		break;
	}
	return true;
}
//監聽Client消息
void ListenToClient()
{
	//建立套接字
	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(sListen == SOCKET_ERROR)
	{
		printf("Init Socket Error!\n");
		return;
	}
	//綁定socket
	sockaddr_in sin;
	sin.sin_family=AF_INET;
	sin.sin_port=htons(PORT);
	sin.sin_addr.S_un.S_addr=INADDR_ANY;
	if (::bind(sListen, (LPSOCKADDR)&sin, sizeof(sockaddr_in)) == SOCKET_ERROR)
	{
	   printf("Bind Error!\n");
	   return;
	}
	// 設定socket進入監聽狀态
	if(::listen(sListen,10)==SOCKET_ERROR)
	{
		printf("Listen Error!\n");
		return;
	}
	printf("Listening To Client...\n");
	//循環接收client端的連接配接請求
	sockaddr_in ClientAddr;
	int nAddrLen = sizeof(sockaddr_in);
	SOCKET sClient;
	//取隊列最前端客戶連接配接請求,建立套接字連接配接通道
	while((sClient=::accept(sListen,(sockaddr*)&ClientAddr,&nAddrLen))==INVALID_SOCKET)
	{}
	//解析消息并進行相應的處理
	//int count=10;//作為定時當程式執行10s未完成時直接退出
	//while(ProcessMsg(sClient)==true&&count>0)
	//{
	//	Sleep(1000);
	//	count--;
	//}
	while(ProcessMsg(sClient)==true)
	{
		Sleep(1000);
	}
	//關閉同用戶端的連接配接
	::closesocket(sClient);
	::closesocket(sListen);
}
//打開檔案
bool OpenFile(CCSDef::TMSG_HEADER* pMsgHeader,SOCKET sClient)
{
	CCSDef::TMSG_FILENAME* pRequstFileNameMsg=(CCSDef::TMSG_FILENAME*)pMsgHeader;
	//對檔案名進行處理
	char *p1,*p2;
	for(p1=pRequstFileNameMsg->szFileName,p2=g_szNewFileName;*p1!='\0';p1++,p2++)
	{
		if(*p1!='\n')
		{
			*p2=*p1;
		}
		if(*p2=='\\')//将‘\’轉換為‘\\’
		{
			*(++p2)='\\';
		}
	}
	*p2='\0';
	ReadFile(sClient);
	return true;
}
//傳送檔案
bool SendFile(SOCKET sClient)
{
	if (NULL == g_pBuff)
	{//如果緩沖區為空
	   ReadFile(sClient);
	}
	int nPacketBufferSize = MAX_PACKET_SIZE - 2 * sizeof(int); // 每個資料包存放檔案的buffer大小
	// 如果檔案的長度大于每個資料包所能傳送的buffer長度那麼就分塊傳送
	for (int i = 0; i < g_lLength; i += nPacketBufferSize)
	{  
	   CCSDef::TMSG_FILE tMsgFile;
	   tMsgFile.tFile.nStart = i;
	   if (i + nPacketBufferSize + 1> g_lLength)
	   {//檔案塊已經是最後一塊
		tMsgFile.tFile.nSize = g_lLength - i;
	   }
	   else
	   {
		tMsgFile.tFile.nSize = nPacketBufferSize;
	   }
	   memcpy(tMsgFile.tFile.szBuff, g_pBuff + tMsgFile.tFile.nStart, tMsgFile.tFile.nSize);//copy到緩沖區
	   ::send(sClient, (char*)(&tMsgFile), sizeof(CCSDef::TMSG_FILE), 0);
	   Sleep(0.5);
	}
	delete [] g_pBuff;
	g_pBuff = NULL;
	return true;
}
//讀取檔案進緩沖區
bool ReadFile(SOCKET sClient)
{
	if(g_pBuff!=NULL)
	{//如果緩沖區不為空
		return true;
	}
	//打開檔案
	FILE *pFile;
	if((pFile = fopen(g_szNewFileName, "rb"))==NULL)
	{//檔案打開失敗,發送錯誤報告
		 printf("Cannot find the file, request the client input file name again\n");
		 CCSDef::TMSG_ERROR_MSG tMsgErrorMsg(MSG_OPENFILE_ERROR);
		 ::send(sClient, (char*)(&tMsgErrorMsg), sizeof(CCSDef::TMSG_ERROR_MSG), 0);
		 return false;
	}
	//傳送檔案長度到Client
	fseek(pFile,0,SEEK_END);//重定位指針到檔案末尾
	g_lLength=ftell(pFile);//傳回檔案指針相對于檔案頭的偏移量
	printf("File Length = %d\n", g_lLength);
	CCSDef::TMSG_FILELENGTH tMsgFileLength(g_lLength);
	::send(sClient,(char*)(&tMsgFileLength), sizeof(CCSDef::TMSG_FILELENGTH), 0);
	// 處理檔案全路徑名,把檔案名分解出來
	//磁盤号,目錄,檔案名,字尾名
	char szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szFname[_MAX_FNAME], szExt[_MAX_EXT];
	_splitpath(g_szNewFileName, szDrive, szDir, szFname, szExt);
	strcat(szFname,szExt);
	CCSDef::TMSG_FILENAME tMsgFileName;
	strcpy(tMsgFileName.szFileName, szFname);
	printf("Send File Name: %s\n", tMsgFileName.szFileName);
	::send(sClient, (char*)(&tMsgFileName), sizeof(CCSDef::TMSG_FILENAME), 0);
	//配置設定緩沖區,讀取檔案内容
	g_pBuff = new char[g_lLength + 1];
	if (g_pBuff == NULL)
	{
	   return false;
	}
	fseek(pFile, 0, SEEK_SET);
	fread(g_pBuff, sizeof(char), g_lLength, pFile);
	g_pBuff[g_lLength] = '\0';
	fclose(pFile);
	return true;
}
           

【2】用戶端:

頭檔案同伺服器端頭檔案

源程式檔案:

/*Client.cpp*/
#include"Client.h"
long g_lLength = 0;
char* g_pBuff = NULL;
char g_szFileName[MAXFILEDIRLENGTH];
char g_szBuff[MAX_PACKET_SIZE+1];
SOCKET g_sClient;
// 初始化socket庫
bool InitSocket();
// 關閉socket庫
bool CloseSocket();
// 把使用者輸入的檔案路徑傳送到server端
bool SendFileNameToServer();
// 與server端連接配接
bool ConectToServer();
// 打開檔案失敗
bool OpenFileError(CCSDef::TMSG_HEADER *pMsgHeader);
// 配置設定空間以便寫入檔案
bool AllocateMemoryForFile(CCSDef::TMSG_HEADER *pMsgHeader);
// 寫入檔案
bool WriteToFile(CCSDef::TMSG_HEADER *pMsgHeader);
// 處理server端傳送過來的消息
bool ProcessMsg();

int main()
{
	while(1)
	{
		InitSocket();
		ConectToServer();
		CloseSocket();
	}
	//system("pause");
	return 0;
}

// 初始化socket庫
bool InitSocket()
{
	//初始化SOCKET
	WSADATA wsaData;
	WORD socketVersion=MAKEWORD(2,2);
	if(::WSAStartup(socketVersion,&wsaData)!=0)
	{
		printf("Init socket dll error\n");
		exit(-1);
	}
	return true;
}
// 關閉socket庫
bool CloseSocket()
{
	// 關閉套接字
	::closesocket(g_sClient);
	// 釋放winsock庫
	::WSACleanup();
	return true;
}
// 把使用者輸入的檔案路徑傳送到server端
bool SendFileNameToServer()
{
	char szFileName[MAXFILEDIRLENGTH];
	printf("Input the File Directory: ");
	//fgets(szFileName, MAXFILEDIRLENGTH, stdin);
	strcpy(szFileName,"E:\\test1.A_exp");
	// 把檔案路徑發到server端
	CCSDef::TMSG_FILENAME tMsgRequestFileName;
	strcpy(tMsgRequestFileName.szFileName, szFileName);
	if (::send(g_sClient, (char*)(&tMsgRequestFileName), sizeof(CCSDef::TMSG_FILENAME), 0) == SOCKET_ERROR)
	{
	   printf("Send File Name Error!\n");
	   exit(-1);
}
return true;
}
// 與server端連接配接
bool ConectToServer()
{
	// 初始化socket套接字
	if ((g_sClient = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == SOCKET_ERROR)
	{
	   printf("Init Socket Error!\n");
	   exit(-1);
	}
	sockaddr_in servAddr;
	servAddr.sin_family = AF_INET;
	servAddr.sin_port = htons(PORT);
	servAddr.sin_addr.S_un.S_addr = ::inet_addr(SERVER_IP);
	if ((::connect(g_sClient, (sockaddr*)&servAddr, sizeof(sockaddr_in))) == INVALID_SOCKET)
	{
	   printf("Connect to Server Error!\n");
	   exit(-1);
	}
	// 輸入檔案路徑傳輸到server端
	SendFileNameToServer();
	// 接收server端傳過來的資訊,直到儲存檔案成功為止
	while (ProcessMsg() == true)
	{
		Sleep(1000);
	}
	return true;
}
// 打開檔案失敗
bool OpenFileError(CCSDef::TMSG_HEADER *pMsgHeader)
{
	if (g_pBuff != NULL)//如果緩沖區内有資料
	   return true;
	assert(pMsgHeader != NULL);
	printf("Cannot find file!\n");
	// 重新輸入檔案名稱
	SendFileNameToServer();
	return true;
}
// 配置設定空間以便寫入檔案
bool AllocateMemoryForFile(CCSDef::TMSG_HEADER *pMsgHeader)
{
	assert(pMsgHeader != NULL);
	if (g_pBuff != NULL)
	{
	   return true;
	}
	CCSDef::TMSG_FILENAME* pRequestFilenameMsg = (CCSDef::TMSG_FILENAME*)pMsgHeader;
	printf("File Name: %s\n", pRequestFilenameMsg->szFileName);
	// 把檔案的路徑設定為D盤根目錄下
	strcpy(g_szFileName, "D:\\");
	strcat(g_szFileName, "test2.B_imp");
	//strcat(g_szFileName, pRequestFilenameMsg->szFileName);
	// 查找相同檔案名的檔案是否已經存在,如果存在報錯退出
	FILE* pFile;
	if ((pFile = fopen(g_szFileName, "r")) != NULL)
	{
	   // 檔案已經存在,要求重新輸入一個檔案
	   printf("The file already exist!\n");
	   CCSDef::TMSG_ERROR_MSG tMsgErrorMsg(MSG_FILEALREADYEXIT_ERROR);
	   ::send(g_sClient, (char*)(&tMsgErrorMsg), sizeof(CCSDef::TMSG_ERROR_MSG), 0);
	   fclose(pFile);
	   return false;
	}
	// 配置設定緩沖區開始接收檔案,如果配置設定成功就給server端發送開始傳送檔案的要求
	g_pBuff = new char[g_lLength + 1];
	if (g_pBuff != NULL)
	{
	   memset(g_pBuff, '\0', g_lLength + 1);
	   printf("Now ready to get the file %s!\n", pRequestFilenameMsg->szFileName);
	   CCSDef::TMSG_CLIENT_READY tMsgClientReady;
	   if (::send(g_sClient, (char*)(&tMsgClientReady), sizeof(CCSDef::TMSG_CLIENT_READY), 0) == SOCKET_ERROR)
	   {
		printf("Send Error!\n");
		exit(-1);
	   }
	}
	else
	{
	   printf("Alloc memory for file error!\n");
	   exit(-1);
	}
	return true;
}
// 寫入檔案
bool WriteToFile(CCSDef::TMSG_HEADER *pMsgHeader)
{
	assert(pMsgHeader != NULL);
	CCSDef::TMSG_FILE* pMsgFile = (CCSDef::TMSG_FILE*)pMsgHeader;
	int nStart = pMsgFile->tFile.nStart;
	int nSize = pMsgFile->tFile.nSize;
	memcpy(g_pBuff + nStart, pMsgFile->tFile.szBuff, nSize);
	if (nStart == 0)
	{
	   printf("Saving file into buffer...\n");
	}
	memcpy(g_pBuff + nStart, pMsgFile->tFile.szBuff, nSize);
	
	// 如果已經儲存到緩沖區完畢就寫入檔案
	if (nStart + nSize >= g_lLength)
	{
	   printf("Writing to disk....\n");
	   // 寫入檔案
	   FILE* pFile;
	   pFile = fopen(g_szFileName, "w+b");
	   fwrite(g_pBuff, sizeof(char), g_lLength, pFile);
	   delete [] g_pBuff;
	   g_pBuff = NULL;
	   fclose(pFile);
	   // 儲存檔案成功傳送消息給server退出server
	   CCSDef::TMSG_SENDFILESUCCESS tMsgSendFileSuccess;
	   while (::send(g_sClient, (char*)(&tMsgSendFileSuccess), sizeof(CCSDef::TMSG_SENDFILESUCCESS), 0) == SOCKET_ERROR)
	   {
	   }
	   printf("Save the file %s success!\n", g_szFileName);
	   return true;
	}
	else
	{
	   return false;
	}
}
// 處理server端傳送過來的消息
bool ProcessMsg()
{
	CCSDef::TMSG_HEADER *pMsgHeader;
	int nRecv = ::recv(g_sClient, g_szBuff, MAX_PACKET_SIZE + 1, 0);

	pMsgHeader = (CCSDef::TMSG_HEADER*)g_szBuff;
	switch (pMsgHeader->cMsgID)
	{
	case MSG_OPENFILE_ERROR:   // 打開檔案錯誤
	   {
		OpenFileError(pMsgHeader);
	   }
	   break;
	case MSG_FILELENGTH:    // 檔案的長度
	   {
		if (g_lLength == 0)
		{
		 g_lLength = ((CCSDef::TMSG_FILELENGTH*)pMsgHeader)->lLength;
		 printf("File Length: %d\n", g_lLength);
		}
	   }
	   break;
	case MSG_FILENAME:     // 檔案名
	   {
		return AllocateMemoryForFile(pMsgHeader);
	   }
	   break;
	case MSG_FILE:      // 傳送檔案,寫入檔案成功之後退出這個函數
	   {
		if (WriteToFile(pMsgHeader))
		{
			/*Sleep(1000);*/
		 return false;
		}
	   }
	   break;
	}
	return true;
}
           

繼續閱讀