udp可靠檔案傳輸實現(基於滑動視窗機制)

來源:互聯網
上載者:User

老闆逼著讓寫一個基於UDP可靠通訊的demo以作為其網路課程的課件內容之一來給學生示範

 

 

dup是不可靠的,為了實現可靠檔案傳輸,需要加入額外的機制來確保可靠,在此選用了滑動視窗機制。

 

 

 

滑動視窗介紹:

 

滑動視窗協議的基本原理就是在任意時刻,發送方都維持了一個連續的允許發送的幀的序號,稱為發送視窗;同時,接收方也維持了一個連續的允許接收的幀的序號,稱為接收視窗。發送視窗和接收視窗的序號的上下界不一定要一樣,甚至大小也可以不同。不同的滑動視窗協議視窗大小一般不同。發送方視窗內的序號代表了那些已經被發送,但是還沒有被確認的幀,或者是那些可以被發送的幀。下面舉一個例子(假設發送視窗尺寸為2,接收視窗尺寸為1):

   分析:①初始態,發送方沒有幀發出,發送視窗前後沿相重合。接收方0號視窗開啟,等待接收0號幀;②發送方開啟0號視窗,表示已發出0幀但尚確認返回資訊。此時接收視窗狀態不變;③發送方開啟0、1號視窗,表示0、1號幀均在等待確認之列。至此,發送方開啟的視窗數已達規定限度,在未收到新的確認返回幀之前,發送方將暫停發送新的資料幀。接收視窗此時狀態仍未變;④接收方已收到0號幀,0號視窗關閉,1號視窗開啟,表示準備接收1號幀。此時發送視窗狀態不變;⑤發送方收到接收方發來的0號幀確認返回資訊,關閉0號視窗,表示從重發表中刪除0號幀。此時接收視窗狀態仍不變;⑥發送方繼續發送2號幀,2號視窗開啟,表示2號幀也納入待確認之列。至此,發送方開啟的視窗又已達規定限度,在未收到新的確認返回幀之前,發送方將暫停發送新的資料幀,此時接收視窗狀態仍不變;⑦接收方已收到1號幀,1號視窗關閉,2號視窗開啟,表示準備接收2號幀。此時發送視窗狀態不變;⑧發送方收到接收方發來的1號幀收畢的確認資訊,關閉1號視窗,表示從重發表中刪除1號幀。此時接收視窗狀態仍不變。

    若從滑動視窗的觀點來統一看待1位元滑動視窗、後退n及選擇重傳三種協議,它們的差別僅在於各自視窗尺寸的大小不同而已。1位元滑動視窗協議:發送視窗=1,接收視窗=1;後退n協議:發視窗>1,接收視窗>1;選擇重傳協議:發送視窗>1,接收視窗>1。

 

 

demo示範:

 

 

 

 

核心代碼:

發送方:(接收和發送線程函數)

 

bool CUDPTrsmtFile::ServerSend()
{
 if(!s_SendHoleMsg())
  return false;

 // 設定檔案指標位置,指向上次已發送的大小
 SetFilePointer(m_hFile, m_dwSend, NULL, FILE_BEGIN);

 int  ret;
 int  nPacketCount = 0;
 DWORD dwRet;
 SendBuf sendbuf;
 DWORD dwRead;
 DWORD dwReadSize;

 SendBuf* pushbuf;

 SetEvent(m_hEvent);

 

 //若已發送大小小於檔案大小並且發送視窗前沿等於後沿,則繼續發送
 //否則退出迴圈
 m_sumPacket = (m_dwFileSize - m_dwSend)/(MAXBUF_SIZE);

 if(m_dwSend < m_dwFileSize)  // 檔案沒有傳輸完時才繼續傳輸
 {
  while(1)
  {
   dwRet = WaitForSingleObject(m_hEvent, 1000);
   if(dwRet == WAIT_FAILED)
   {
    CString temp="等待發送號誌出錯!";
    SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
    SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
    return false;
   }
   else if(dwRet == WAIT_TIMEOUT)
   {
    //重發
    int ackindex=0;
    ::EnterCriticalSection(&m_csQueue);  // 進入m_bufqueue的排斥區
    ackindex=m_bufqueue.front()->index;
    ret = m_hsocket.hsendto((char*)m_bufqueue.front(), sizeof(sendbuf));
    ::LeaveCriticalSection(&m_csQueue);  // 退出m_bufqueue的排斥區
    if(ret == SOCKET_ERROR)
    {
     CString temp="發送視窗中的資料包發送失敗,重發資料包!";
     SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
     continue;
    }

    CString temp;
    temp.Format("等待ACK確認訊息逾時,重發發送視窗的第 %d 個資料包",ackindex);
    SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

    ResetEvent(m_hEvent);
    continue;
   }

   //若發送視窗大小 < 預定大小 && 已讀檔案次數(nReadIndex) < 需要讀檔案的次數(nReadCount),則繼續讀取發送
   //否則,要發送的內容從m_bufqueue裡提取
   if(m_dwSend < m_dwFileSize)
   {
    dwReadSize = m_dwFileSize - m_dwSend;
    dwReadSize = dwReadSize < MAXBUF_SIZE ? dwReadSize : MAXBUF_SIZE;

    memset(sendbuf.buf, 0, sizeof(sendbuf.buf));
    if(!ReadFile(m_hFile, sendbuf.buf, dwReadSize, &dwRead, NULL))
    {
     CString temp="讀取檔案失敗,請確認檔案存在或有讀取許可權!";
     SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
     SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
     return false;
    }

    m_dwSend += dwRead;

    nPacketCount++;

    sendbuf.index = m_nSendIndexHead;
    m_nSendIndexHead = (m_nSendIndexHead + 1) % Sliding_Window_Size; // 發送視窗前沿向前移一格

    sendbuf.dwLen = dwRead;

    //儲存發送過的資料,以便重發
    ::EnterCriticalSection(&m_csQueue);   // 進入m_bufqueue的排斥區
    pushbuf = GetBufFromLookaside();

    memcpy(pushbuf, &sendbuf, sizeof(sendbuf));

    m_bufqueue.push(pushbuf);
    if(m_dwSend >= m_dwFileSize)    // 檔案已讀完,在隊列中加一File_End標誌,以便判斷是否需要繼續發送
    {
     pushbuf = GetBufFromLookaside();

     pushbuf->index = File_End;
     pushbuf->dwLen = File_End;
     memset(pushbuf->buf, 0, sizeof(pushbuf->buf));

     m_bufqueue.push(pushbuf);
    }
    ::LeaveCriticalSection(&m_csQueue);   // 退出m_bufqueue的排斥區
   }

   ::EnterCriticalSection(&m_csQueue);    // 進入m_bufqueue的排斥區
   if(m_bufqueue.front()->index == File_End)  // 所有資料包已發送完畢,退出迴圈
   {
    ::LeaveCriticalSection(&m_csQueue);   // 退出m_bufqueue的排斥區
    break;
   }
   else if(m_bufqueue.size() <= Send_Window_Size) // 發送視窗小於指定值,繼續發送
   {
    ret = m_hsocket.hsendto((char*)m_bufqueue.front(), sizeof(sendbuf));
    CString temp;
    temp.Format("發送:發送視窗第 %d 個資料包正被發送!",m_bufqueue.front()->index);
    SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
    if(ret == SOCKET_ERROR)
    {
     ::LeaveCriticalSection(&m_csQueue);  // 退出m_bufqueue的排斥區

     CString temp="發送資料包失敗,重發!";
     SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
     continue;
    }

    //延時,防止丟包
    Sleep(50);  
   }
   else           // 發送視窗大於指定值,等持接收線程接收確認訊息
   {
    ResetEvent(m_hEvent);
   }
   ::LeaveCriticalSection(&m_csQueue);    // 退出m_bufqueue的排斥區
  }
 }
 CString temp;
 temp.Format("共分成 %d 個資料包傳送檔案!",nPacketCount);
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

 CloseHandle(m_hEvent);

 CloseHandle(m_hFile);
 m_hFile = NULL;

 m_hsocket.hclosesocket();
 
 while(!m_bufLookaside.empty()) // clear
 {
  delete m_bufLookaside.front();
  m_bufLookaside.pop();
 }
 
 temp="-------檔案已經傳輸完畢-------!";
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
 SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);

 return true;
}

 

bool CUDPTrsmtFile::ServerRecv()
{
 if(!s_RecvHoleSuccessMsg())
  return false;

 int ret;
 RecvBuf recvbuf;

 int recvSumPacket=0;

 while(m_hFile != NULL)
 {
  ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));  
  if(ret == SOCKET_ERROR)
  {
   ::EnterCriticalSection(&m_csQueue);
   if(m_bufqueue.front()->index == File_End) // 檔案傳輸完畢
   {
    ::LeaveCriticalSection(&m_csQueue);
    break;
   }
   ::LeaveCriticalSection(&m_csQueue);
   CString temp="接收檔案傳輸ACK訊息出錯!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
   return false;
  }

  if(recvbuf.flag == Flag_Ack && recvbuf.index == m_nSendIndexTail)
  {
   CString temp;
   temp.Format("接收:已接收到發送視窗第 %d 個資料包的ACK訊息!",recvbuf.index);
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

   recvSumPacket++;
   int progress=(int)((1.0*recvSumPacket/m_sumPacket)*100);
   SendMessage(the_hWnd,PROGRESS_MSG,0,LPARAM(&progress));

   m_nSendIndexTail = (m_nSendIndexTail + 1) % Sliding_Window_Size;
      
   //該結點已得到確認,將其加入旁視列表,以備再用
   ::EnterCriticalSection(&m_csQueue);
   m_bufLookaside.push(m_bufqueue.front());
   m_bufqueue.pop();
   ::LeaveCriticalSection(&m_csQueue);

   SetEvent(m_hEvent);
  }
 }

 return true;
}

bool CUDPTrsmtFile::s_SendHoleMsg(void)
{
 int  ret;
 int  count = 0;
 DWORD dwRet;
 char sendbuf[32];

 //發送串連訊息,最多發10次,若10次都失敗,則不能通訊

 while(1)
 {
  if(count == 10)
  {
   CString temp="串連對方逾時!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
   return false;
  }

  strcpy(sendbuf, "hole");
  ret = m_hsocket.hsendto(sendbuf, strlen(sendbuf));
  if(ret == SOCKET_ERROR)
  {
   CString temp="發送串連訊息失敗!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
   return false;
  }

  
  dwRet = WaitForSingleObject(m_hEvent, 2 * 1000); // 等待接收線程接收串連成功訊息
  if(dwRet == WAIT_FAILED)
  {
   return false;
  }
  else if(dwRet == WAIT_TIMEOUT)
  {   
   CString temp="重新給對方發送串連訊息!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   count++;
   continue; // 重發串連訊息
  }

  break;
 }
 
 //傳送檔案總大小
 FileInfo fi;
 fi.dwLen=m_dwFileSize;
 strcpy(fi.filename,m_hFileName);
 ret = m_hsocket.hsendto((char*)&fi,sizeof(fi));
 if(ret == SOCKET_ERROR)
 {
  CString temp="發送傳送檔案資訊失敗!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
  return false;
 } 
 
 ResetEvent(m_hEvent);       //設定m_hEvent為無訊號狀態,等待接收線程接收已傳送檔案大小
 
 dwRet = WaitForSingleObject(m_hEvent, 15 * 1000);
 if(dwRet == WAIT_FAILED)
 {
  CString temp="等待號誌出錯!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
  return false;
 }
 else if(dwRet == WAIT_TIMEOUT)
 {
  CString temp="對方沒響應或拒收發送的檔案,您可以再重發一次!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
  return false;
 }
 
 SetEvent(m_hEvent);  //設定m_hEvent為有訊號狀態,以便傳送檔案

 return true;
}

 

bool CUDPTrsmtFile::s_RecvHoleSuccessMsg(void)
{
 int ret;

 char szbuf[64];
 RecvBuf recvbuf;

 while(1)
 {
    ret = m_hsocket.hrecvfrom(szbuf, sizeof(szbuf));  //接收串連成功訊息
  if(ret == SOCKET_ERROR)
  {
   CString temp="接收串連訊息不成功!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   Sleep(1000);
   continue;
  }
  else
  {
   szbuf[ret]='/0';
   if(strcmp(szbuf,"holesuccess")==0)
   {
    CString temp="串連成功,開始準備傳送檔案!";
    SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   }
   break;
  }
 }
 
 SetEvent(m_hEvent);  //接收成功,設定m_hEvent為有訊號狀態,以便發送線程退出發送串連訊息迴圈
 
 ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));  //接收已傳輸送檔案大小
 if(ret == SOCKET_ERROR)
 {
  CString temp="接收已傳送檔案大小資訊出錯!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  SendMessage(the_hWnd,FINISHEDTRANS_MSG,0,0);
  return false;
 }

 if(recvbuf.flag == Flag_Refuse)
 {
  CString temp="Sorry,對方拒收傳送的檔案!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  CloseHandle(m_hEvent);
  
  CloseHandle(m_hFile);
  m_hFile = NULL;
  
  m_hsocket.hclosesocket();

  return false;
 }

 m_dwSend = recvbuf.dwFileSize;

 CString temp;
 temp.Format("上次斷點已傳送 %ld Byte!",m_dwSend);
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

 
 SetEvent(m_hEvent);  //接收成功,設定m_hEvent為有訊號狀態,發送線程開始傳送檔案

 return true;
}

 

 

 

接收方:(核心線程函數)

 

bool CUDPTrsmtFile::ClientRecv()
{
  if(!c_RecvHole_SendHoleSuccessMsg())
   return false;

 CString bstr="開始接收檔案!";
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&bstr));

 int  ret;
 DWORD dwWritten;
 SendBuf recvbuf;
 RecvBuf sendbuf;
 int nerror = 0;

 // 設定檔案指標位置,指向上次已發送的大小
 SetFilePointer(m_hFile, 0, NULL, FILE_END);

 m_sumPacket = (m_dwFileSize - m_dwSend)/(MAXBUF_SIZE)+1;
 int recvPacket = 0;

 //若已接收檔案大小小於需要接收的大小,則繼續

 while(m_dwSend < m_dwFileSize)
 {
  //接收
  memset(&recvbuf, 0, sizeof(recvbuf));
  ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));
  if(ret == SOCKET_ERROR)
  {
   CString temp="接收檔案資料包出現錯誤,終止接收!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   return false;
  }

  //不是希望接收的,丟棄,繼續接收
  if(recvbuf.index != (m_nRecvIndex) % Sliding_Window_Size)
  {
   nerror++;
   CString temp="接收的資料報文非當前所需,丟棄!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   continue;
  }

  if(!WriteFile(m_hFile, recvbuf.buf, recvbuf.dwLen, &dwWritten, NULL))
  {
   CString temp="寫檔案出現錯誤,檔案接收終止!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   return false;
  }

  //已接收檔案大小
  m_dwSend += dwWritten;

  //發送確認訊息
  sendbuf.flag = Flag_Ack;
  sendbuf.index = m_nRecvIndex;
  
  ret = m_hsocket.hsendto((char*)&sendbuf, sizeof(sendbuf));
  if(ret == SOCKET_ERROR)
  {
   CString temp="發送ACK確認報文出錯,檔案接收終止!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   return false;
  }

  /////////////
  recvPacket++;
  int progress=(int)((1.0*recvPacket/m_sumPacket)*100);
  SendMessage(the_hWnd,PROGRESS_MSG,0,LPARAM(&progress));

  //接收視窗前移一格
  m_nRecvIndex = (m_nRecvIndex + 1) % Sliding_Window_Size;
 }

 CString temp;
 temp.Format("-------檔案接收完畢!--------/n>總共接收資料包個數 %ld 個!/n>傳輸過程中出錯的資料包個數: %d 個!",m_sumPacket,nerror);
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

 CloseHandle(m_hFile);
 m_hFile = NULL;

 m_hsocket.hclosesocket();

 SendMessage(the_hWnd,LISTEN_MSG,0,0);

 return true;
}

 

bool CUDPTrsmtFile::c_RecvHole_SendHoleSuccessMsg(void)
{
 int  ret;
 char recvbuf[64];

 while(1)
 {
  ret = m_hsocket.hrecvfrom(recvbuf, sizeof(recvbuf));
  if(ret == SOCKET_ERROR)
  {
//    CString temp="接收串連訊息失敗!";
//    SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   return false;
  }

  recvbuf[ret] = '/0';

  //收到對方發來的串連訊息,結束迴圈。同時建立通訊端,向發對方發送串連成功訊息,否則繼續等待接收
  if(strcmp(recvbuf, "hole") == 0)
  {
   //發送串連成功訊息
   char szsend[] = "holesuccess";
   m_hsocket.makesendsockaddr(m_hsocket.getsockaddr(), m_hsocket.getsockport());
   ret = m_hsocket.hsendto(szsend, sizeof(szsend));
   if(ret == SOCKET_ERROR)
   {

    CString temp="發送串連成功確認訊息失敗!";
    SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
    return false;
   }
   
   break;
  }
  else
  {
   CString temp="再次接收串連訊息!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   continue;
  }
 }

 Sleep(10); // 等待對方接收

 CString temp="串連成功!";
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
 FileInfo fi;
 ret = m_hsocket.hrecvfrom((char*)&fi,sizeof(fi));
 if(ret == SOCKET_ERROR)
 {
  CString temp="接收檔案資訊失敗!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  return false;
 }

 m_dwFileSize=fi.dwLen;
  strcpy(m_hFileName,fi.filename);

 CString fName=CString(m_hFileName);
 fName.Replace('//','/');
 int index=fName.ReverseFind('/');
 CString namestr=fName.Right(fName.GetLength()-index-1);

  //SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&namestr));

 if(IDNO == AfxMessageBox("有檔案發送過來,是否接收?",MB_YESNO))
 {

  RecvBuf sendbuf;
  sendbuf.index = -1;
  sendbuf.flag = Flag_Refuse;
  
  ret = m_hsocket.hsendto((char*)&sendbuf, sizeof(sendbuf));
  if(ret == SOCKET_ERROR)
  {
   CString temp="發送拒收訊息失敗!";
   SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
   return false;
  }
  
  CString temp="發送拒收訊息成功,本機不接收該檔案!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  
  m_hsocket.hclosesocket();

  SendMessage(the_hWnd,LISTEN_MSG,0,0);

  return false;

 }

 char szName[255];
 strcpy(szName,LPCTSTR(namestr));
 saveDlg.m_ofn.lpstrFile=new char[255];
 memset(saveDlg.m_ofn.lpstrFile,0,255);
 //strcpy(saveDlg.m_ofn.lpstrFile,(LPCTSTR)namestr);
 saveDlg.m_ofn.lpstrFile=LPSTR((LPCTSTR)namestr);
 if (saveDlg.DoModal() == IDOK)
 {
  CString fullname=saveDlg.GetPathName();
  strcpy(m_hFileName,LPCTSTR(fullname));
  CString temp;
  temp.Format("待接收的檔案: ");
  temp+=fullname;
  temp+="!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
 }
 delete[] saveDlg.m_ofn.lpstrFile;

 SendMessage(the_hWnd,UPDATEINFO_MSG,0,0);

 SetFile(m_hFileName);

 temp.Format("待接收檔案的總大小為 %ld Byte!",fi.dwLen);
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

 //發送已傳輸檔案大小
 RecvBuf sendbuf;
 sendbuf.dwFileSize = m_dwSend;
  sendbuf.flag = Flag_ReSend;

 ret = m_hsocket.hsendto((char*)&sendbuf, sizeof(sendbuf));
 if(ret == SOCKET_ERROR)
 {
  CString temp="發送已傳送檔案大小失敗!";
  SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));
  return false;
 }

 temp.Format("發送斷點資訊:上次斷點已經接收 %ld Byte!",m_dwSend);
 SendMessage(the_hWnd,ECHO_MSG,0,LPARAM(&temp));

 return true;
}

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.