TCP協議實現文件傳輸
使用TCP協議實現傳輸文件
程序分爲發送端和接收端。首先在傳輸文件數據之前,發送端會把將裝有文件名稱和文件長度等
信息的數據包發送至接收端。接收端收到文件名稱和文件長度信息後會創建好空白文件。接着開始傳輸
文件數據。下面介紹實現功能的主要過程:
1.創建套接字、綁定、監聽、連接、接受連接
//創建TCP協議的套接字
m_Socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(SOCKET_ERROR == m_Socket)
AfxMessageBox("Create Socket Error! ", 0, 0);
//綁定與監聽
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.s_addr = inet_addr(sIP);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(Port);
int ret = bind(m_Socket, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR));
if(ret==SOCKET_ERROR)
AfxMessageBox("Bind Socket Error!", 0, 0);
//連接
SOCKADDR_IN ServerAddr;
ServerAddr.sin_addr.s_addr = inet_addr(ServerAddr_in);
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(ServerPort);
int Result = connect(m_Socket, (struct sockaddr*)&ServerAddr, sizeof(struct sockaddr));
if(SOCKET_ERROR == Result)
AfxMessageBox("Connet Failed!");
//接受連接
SOCKADDR_IN ClientAddr;
int len = sizeof(SOCKADDR_IN);
SOCKET ClientSock = accept(m_Socket, (struct sockaddr*)&ClientAddr, &len);
if(SOCKET_ERROR == ClientSock)
AfxMessageBox("Accept Failed!");
2.聲明宏和結構體
聲明套接字緩衝區和一次發送文件數據的緩衝區大小
#define SOCKET_BUFF 80000 //套接字緩衝區大小
#define PACK_BUFF 50000 //數據包緩衝區大小
聲明文件I/O緩衝區和最大文件路徑長度
#define FILE_NAME_MAX 100 //文件路徑最大長度
#define FILE_IO_BUFF PACK_BUFF //文件IO緩衝區
//文件信息
typedef struct _FileInfor
{
u_long ulFileLen;
char sFileName[ FILE_NAME_MAX ];
}_FileInfor;
//數據包
typedef struct _DataPack
{
char cType; //'D'爲數據 'M'爲文件信息
int nPackLen;
char sContent[ PACK_BUFF ]; //數據包緩衝區
u_long nPosition; //數據在文件中的位置
int nContentLen; //數據字節數
_FileInfor FileInfor; //文件信息
}_DataPack;
3.發送端
//發送線程需要的全局變量
char sPath[FILE_NAME_MAX]; //文件地址
u_long FileByteCount; //文件大小
SOCKET ClientSocket; //
(1)設置套接字發送緩衝區大小,在32位Windows XP環境下,系統爲每個套接字分配的默認發送數據緩
衝區爲8192字節。由於傳輸的文件很大,可能幾十兆,或者更大。那麼系統爲每個套接字分配的默認
緩衝區顯然過小。爲此在創建套接字之後,需要修改套接字發送數據緩衝尺寸。在這裏我修改爲80k,
差不多可以夠用了。
//設置套接字發送緩衝區
int nBuf = SOCKET_BUFF;
int nBufLen = sizeof(nBuf);
int nRe = setsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, (char*)&nBuf, nBufLen);
if(SOCKET_ERROR == nRe)
AfxMessageBox("setsockopt error!");
//檢查緩衝區是否設置成功
nRe = getsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, (char*)&nBuf, &nBufLen);
if(SOCKET_BUFF != nBuf)
AfxMessageBox("檢查緩衝區:setsockopt error!");
(2)測量文件大小併發送文件大小和名稱給客戶端
首先使用C庫函數對源文件進行測量
//得到文件地址
LPTSTR lpPath = m_sPath.GetBuffer( m_sPath.GetLength ());
//打開文件
FILE *File = fopen(lpPath, "rb");
if(NULL == File)
AfxMessageBox("打開文件失敗!");
//測量文件大小
char Buff[PACK_BUFF];
u_long ulFaceReadByte;
FileByteCount = 0;
fseek(File, 0, SEEK_SET);
while(!feof(File))
{
ulFaceReadByte = fread(Buff, 1, 1, File);
FileByteCount += ulFaceReadByte;
}
//關閉文件
int nRe = fclose(File);
if(nRe)
AfxMessageBox("關閉文件失敗!");
此時以獲取源文件的長度,我們將文件長度和文件名稱放到數據包中,設置數據包爲'M'類型。
//打包
_DataPack Pack;
Pack.cType = 'M';
Pack.nPackLen = sizeof(Pack);
//取得文件名
ZeroMemory(Pack.FileInfor.sFileName, FILE_NAME_MAX);
GetFIieNameFromPath(lpPath, Pack.FileInfor.sFileName);
Pack.FileInfor.ulFileLen = FileByteCount;
接着使用send()將打包完成的數據包發送給接收端,把發送線程的全局變量初始化,並創建發送線
程,文件數據將由發送線程負責發送
//發送數據包 文件大小和名稱
nRe = send(m_ClientSockFd.fd_array[0], (char*)&Pack, Pack.nPackLen, 0);
if(SOCKET_ERROR == nRe)
AfxMessageBox("Send File Size Failed!");
//線程準備全局變量
strcpy(sPath, m_sPath);
ClientSocket = m_ClientSockFd.fd_array[0];
//啓動線程發送文件
DWORD ID;
m_hSendThread = CreateThread(0, 0, SendDataThrad, 0, 0, &ID);
(3)發送文件數據線程。先打開源文件,爲了一次讀取大量數據將文件緩衝區設置爲FILE_IO_BUFF大小,
然後將讀取的數據打包併發送。這樣不斷地讀、不斷地發送,直到將整個文件發送完爲止。
DWORD __stdcall CServerDlg::SendDataThrad(LPVOID LpP)
{
//打開文件
FILE *File = fopen(sPath, "rb");
if(NULL == File)
{
AfxMessageBox("SendDataThrad中打開文件失敗!");
return 1;
}
//設置文件緩衝區
int nBuff = FILE_IO_BUFF;
if(setvbuf(File, (char*)&nBuff, _IOFBF, sizeof(nBuff)))
AfxMessageBox("設置文件緩衝區失敗!");
//讀取文件數據併發送
u_long ulFlagCount = 0; //記錄讀了多少數據
u_long FaceReadByte = 0; //一次實際讀取的字節數
char sBuff[PACK_BUFF];
ZeroMemory(sBuff, PACK_BUFF);
fseek(File, 0, SEEK_SET);
while (!feof(File))
{
FaceReadByte = fread(sBuff, 1, PACK_BUFF, File);
//打包
_DataPack DataPack;
DataPack.cType = 'D';
DataPack.nPackLen = sizeof(DataPack);
DataPack.nContentLen = FaceReadByte;
CopyMemory(DataPack.sContent, sBuff, FaceReadByte);
DataPack.nPosition = ulFlagCount;
//發送
int nResult = send(ClientSocket, (char*)&DataPack, DataPack.nPackLen, 0);
if (SOCKET_ERROR == nResult)
{
AfxMessageBox("SendDataThrad中發送數據失敗!");
}else
ulFlagCount += FaceReadByte; //記錄發送字節數
}
AfxMessageBox("發送結束");
//關閉
int nRe = fclose(File);
if(nRe)
AfxMessageBox("SendDataThrad中關閉文件失敗!");
return 0;
}
4.接收端
//接收線程用的全局變量
_FileInfor FileInfor; //文件信息
u_long ulWriteByte; //記錄總共寫入的字節
char lpPath[FILE_NAME_MAX]; //文件路徑
char sFilePathAndName[FILE_NAME_MAX]; //完整的文件路徑
(1)設置套接字接收緩衝區大小。
//設置套接字接收緩衝區
int nBuf = SOCKET_BUFF;
int nBufLen = sizeof(nBuf);
SOCKET ClientSock = m_Sock.GetSocket();
int nRe = setsockopt(ClientSock, SOL_SOCKET, SO_RCVBUF, (char*)&nBuf, nBufLen);
if(SOCKET_ERROR == nRe)
AfxMessageBox("setsockopt error!");
//檢查緩衝區是否設置成功
nRe = getsockopt(ClientSock, SOL_SOCKET, SO_RCVBUF, (char*)&nBuf, &nBufLen);
if(SOCKET_BUFF != nBuf)
AfxMessageBox("檢查緩衝區:setsockopt error!");
(2)接收文件信息和文件數據線程。先判斷數據包是屬於文件信息還是文件類型,如果是文件信息就根
據信息創建空文件,如果是文件數據就將數據已追加的方式寫進目的文件中。
DWORD __stdcall CClientDlg::ReceiveDataPro(LPVOID LpP)
{
CSocket_Win32 *pCSock = (CSocket_Win32*)LpP;
u_long ulWriteByteCount = 0;
while (1)
{
_DataPack DataPack;
ZeroMemory(&DataPack, sizeof(DataPack));
if(!(*pCSock).Receive(&DataPack, sizeof(DataPack)))
{
AfxMessageBox("Receive DataPack Failed!");
}
//判斷數據包類型
if('M' == DataPack.cType) //包爲文件信息
{
//接收文件信息
FileInfor.ulFileLen = DataPack.FileInfor.ulFileLen; //獲取文件長度
strcpy(FileInfor.sFileName, DataPack.FileInfor.sFileName); //獲取文件名稱
//得到文件目錄
char sFilePath[FILE_NAME_MAX];
ZeroMemory(sFilePath, FILE_NAME_MAX);
strcpy(sFilePath, lpPath);
strcat(sFilePath, FileInfor.sFileName);
strcat(sFilePathAndName, sFilePath); //保存完整的文件路徑
//創建文件
SECURITY_ATTRIBUTES attr;
attr.nLength = FileInfor.ulFileLen;
attr.lpSecurityDescriptor = NULL;
HANDLE hRe = CreateFile(sFilePath, GENERIC_WRITE, FILE_SHARE_WRITE, &attr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if(INVALID_HANDLE_VALUE == hRe)
AfxMessageBox("創建文件失敗!");
bool bRe = ::CloseHandle(hRe);
//可以開始接受文件
bIsStartReceive = true;
}else if('D' == DataPack.cType) //包爲文件數據
{
//打開文件
char sPath[FILE_NAME_MAX];
strcpy(sPath, sFilePathAndName);
FILE *File = fopen(sPath, "ab");
if(0 == File)
AfxMessageBox("打開文件失敗!");
//設置文件緩衝區
int nBuff = FILE_IO_BUFF;
if(setvbuf(File, (char*)&nBuff, _IOFBF, sizeof(nBuff)))
AfxMessageBox("設置文件緩衝區失敗!");
//定位文件
u_long nPosition = DataPack.nPosition;
int nRe = fseek(File, nPosition, SEEK_SET);
if(nRe)
AfxMessageBox("SendDataThrad中定位失敗!");
//寫文件
u_long nNumberOfBytesWritten = fwrite(&DataPack.sContent, 1, DataPack.nContentLen, File);
if(DataPack.nContentLen != nNumberOfBytesWritten)
AfxMessageBox("寫文件失敗!");
else
{
ulWriteByteCount += nNumberOfBytesWritten;
}
fflush(File); //清除文件緩衝區
//關閉文件
nRe = fclose(File);
if(nRe)
AfxMessageBox("關閉文件失敗!");
if(ulWriteByteCount >= FileInfor.ulFileLen)
{
AfxMessageBox("接收結束");
break;
}
}
}
return 0;
}