UDP可靠文件传输实现(基于滑动窗口机制)

本文为转载,作者原地址:http://blog.csdn.net/yysdsyl/article/details/4260252

UDP是不可靠的,为了实现可靠文件传输,需要加入额外的机制来确保可靠,在此选用了滑动窗口机制。

滑动窗口介绍:滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。下面举一个例子(假设发送窗口尺寸为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;
}

 

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
UDP协议中,由于其不可靠性,可能存在数据包丢失或乱序等问题。因此,为了实现可靠数据传输,需要在UDP的基础上进行一些改进。下面是一份基于UDP协议的可靠数据传输的Java代码实现。 首先,定义一个Packet类,用于封装数据包: ```java public class Packet implements Serializable { private int seqNum; //序列号 private byte[] data; //数据 private long timeStamp; //时间戳 private boolean ack; //是否是ACK包 public Packet(int seqNum, byte[] data, long timeStamp, boolean ack) { this.seqNum = seqNum; this.data = data; this.timeStamp = timeStamp; this.ack = ack; } public int getSeqNum() { return seqNum; } public byte[] getData() { return data; } public long getTimeStamp() { return timeStamp; } public boolean isAck() { return ack; } } ``` 然后,定义一个UDPClient类,用于发送数据包和接收ACK包: ```java public class UDPClient { private DatagramSocket socket; private InetAddress address; private int port; public UDPClient(String ipAddress, int port) throws UnknownHostException, SocketException { this.socket = new DatagramSocket(); this.address = InetAddress.getByName(ipAddress); this.port = port; } public void send(byte[] data) throws IOException { int seqNum = 0; int windowSize = 4; long timeout = 1000; //超时时间为1秒 int base = 0; int nextSeqNum = 0; List<Packet> packets = new ArrayList<>(); while (seqNum < data.length) { byte[] buffer = new byte[Math.min(data.length - seqNum, 1024)]; System.arraycopy(data, seqNum, buffer, 0, buffer.length); Packet packet = new Packet(seqNum, buffer, System.currentTimeMillis(), false); packets.add(packet); seqNum += buffer.length; } while (base < packets.size()) { while (nextSeqNum < base + windowSize && nextSeqNum < packets.size()) { Packet packet = packets.get(nextSeqNum); sendPacket(packet, socket, address, port); nextSeqNum++; } boolean timeoutOccurred = false; byte[] receiveData = new byte[1024]; DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); try { socket.setSoTimeout((int) timeout); socket.receive(receivePacket); Packet ackPacket = (Packet) deserialize(receivePacket.getData()); if (ackPacket.isAck()) { base = Math.max(base, ackPacket.getSeqNum() + 1); } } catch (SocketTimeoutException e) { timeoutOccurred = true; } if (timeoutOccurred) { for (int i = base; i < nextSeqNum; i++) { Packet packet = packets.get(i); sendPacket(packet, socket, address, port); } } } } private void sendPacket(Packet packet, DatagramSocket socket, InetAddress address, int port) throws IOException { byte[] sendData = serialize(packet); DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, address, port); socket.send(sendPacket); } private byte[] serialize(Object obj) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); oos.flush(); return baos.toByteArray(); } private Object deserialize(byte[] data) throws IOException, ClassNotFoundException { ByteArrayInputStream bais = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bais); return ois.readObject(); } } ``` 在send方法中,首先将数据分成若干个Packet对象。然后,采用滑动窗口的方式发送Packet对象,每发送一个Packet对象,就将nextSeqNum加1。接着,等待ACK包的到来。如果ACK包到来,则将base设置为ACK包的序列号加1。如果超时,则重传从base到nextSeqNum-1的Packet对象。直到所有的Packet对象都被发送并且ACK包都被接收到为止。 最后,可以编写一个测试用例: ```java public class TestUDPClient { public static void main(String[] args) throws IOException { String ipAddress = "127.0.0.1"; int port = 9999; UDPClient client = new UDPClient(ipAddress, port); byte[] data = "Hello, world!".getBytes(); client.send(data); } } ``` 这里,我们将数据设置为“Hello, world!”,然后调用client.send方法将数据发送出去。在实际应用中,可以在UDPClient类中添加回调接口来处理接收到的数据

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值