用TClientSocket做HTTP文件下载(Norton LiveUpdate,C++Builder)

原文:http://blog.csdn.net/unsigned/archive/2008/09/22/2960982.aspx
用TClientSocket做HTTP文件下载(Norton LiveUpdate,C++Builder)

    • //---------------------------------------------------------------------------

    • #include <vcl.h>
    • #pragma hdrstop

    • #include "Unit1.h"
    • //---------------------------------------------------------------------------
    • #pragma package(smart_init)
    • #pragma resource "*.dfm"
    • TForm1 *Form1;
    • //---------------------------------------------------------------------------
    • bool __fastcall FileExists(AnsiString FileName)
    • {
    •    HANDLE FileHandle = CreateFile(FileName.c_str(),
    •                                   NULL,
    •                                   FILE_SHARE_READ |
    •                                   FILE_SHARE_WRITE,
    •                                   NULL,
    •                                   OPEN_EXISTING,
    •                                   FILE_ATTRIBUTE_ARCHIVE |
    •                                   FILE_ATTRIBUTE_HIDDEN |
    •                                   FILE_ATTRIBUTE_NORMAL |
    •                                   FILE_ATTRIBUTE_READONLY |
    •                                   FILE_ATTRIBUTE_SYSTEM,
    •                                   NULL
    •                                  );
    •    if (FileHandle == INVALID_HANDLE_VALUE)
    •       return false;
    •    CloseHandle(FileHandle);
    •    return true;
    • }
    • __int64 SwapPos(AnsiString strSource,AnsiString strFind)
    • {
    •    __int64 pPos,pSLen,pFLen;

    •    pPos=0;
    •    pSLen=strSource.Length();
    •    pFLen=strFind.Length();

    •    if(pSLen==pFLen&&strSource==strFind)
    •       return 1;

    •    if(pSLen<pFLen)
    •       return 0;

    •    for(__int64 i=0;i<pSLen;i++)
    •    {
    •       AnsiString strTmp=strSource.SubString(pSLen-pFLen+1-i,pFLen);
    •       if(strTmp==strFind)
    •       {
    •          pPos=pSLen-pFLen+1-i;
    •          break;
    •       }
    •    }
    •    return pPos;
    • }

    • __int64 HttpDataPos(TStream *strm)
    • {
    •    __int64 Pos=strm->Position;
    •    char ch[5];
    •    ch[0]='/0';
    •    ch[1]='/0';
    •    ch[2]='/0';
    •    ch[3]='/0';
    •    ch[4]='/0';

    •    for(__int64 i=0;i<strm->Size;i++)
    •    {
    •       strm->Position=i;
    •       strm->Read(ch,4);
    •       if(AnsiString(ch)=="/r/n/r/n")
    •       {
    •          strm->Position=Pos;
    •          return i;
    •       }
    •    }
    •    return 0;
    • }

    • __fastcall TForm1::TForm1(TComponent* Owner)
    •         : TForm(Owner)
    • {
    • }
    • __fastcall TForm1::~TForm1()
    • {
    •    DownMEM->Free();
    • }
    • //---------------------------------------------------------------------------


    • void __fastcall TForm1::FormCreate(TObject *Sender)
    • {
    •    SourceHTML="";
    •    Type=0;
    •    IsAuto=false;
    •    caption=Caption;

    •    width=GetSystemMetrics(SM_CXSCREEN);
    •    height=GetSystemMetrics(SM_CYSCREEN);

    •    DownMEM=new TMemoryStream();

    •    if(ParamCount()>0)
    •       IsAuto=ParamStr(1).UpperCase()=="-A"||ParamStr(1).UpperCase()=="/A";

    •    if(ParamCount()>1)
    •       Flg_Q=ParamStr(2).UpperCase()=="-Q"||ParamStr(2).UpperCase()=="/Q";

    •    if(IsAuto)
    •    {
    •       SetWindowPos(Handle,HWND_TOPMOST,
    •                   0,0,GetSystemMetrics(SM_CXSCREEN),56,SWP_DRAWFRAME);
    •       Button1->Click();
    •    }
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
    •       TCustomWinSocket *Socket)
    • {
    •    TMemoryStream *tmpStrm;
    •    TFileStream *tmpFStrm;
    •    __int64 lSize,RecvCount;

    •    int len=Socket->ReceiveLength();

    •    if(len<=1)
    •    {
    •       AnsiString ssss=Socket->ReceiveText();
    •       return;
    •    }

    •    switch(Type)
    •    {
    •       case 1:
    •            DownMEM->Clear();
    •            DownMEM->SetSize((int)len);
    •            HeadLength=0;
    •            DataLength=0;
    •            FileName="";
    •            Url="";
    •            RecvCount = Socket->ReceiveBuf(DownMEM->Memory,len);
    •            DownMEM->SetSize((int)RecvCount);
    •            HeadLength=HttpDataPos(DownMEM);
    •            if(HeadLength>0)
    •               CheckHead();
    •            if(HeadLength>0&&DownMEM->Size>=HeadLength+4+DataLength)
    •               Type=0;
    •            else
    •               Type++;
    •            break;

    •       case 2:
    •            lSize=DownMEM->Size;
    •            DownMEM->Position=lSize;
    •            tmpStrm=new TMemoryStream();
    •            try
    •            {
    •               tmpStrm->SetSize(len);
    •               __int64 iRecvd = Socket->ReceiveBuf(tmpStrm->Memory,len);
    •               tmpStrm->Position = 0;
    •               DownMEM->CopyFrom(tmpStrm,iRecvd);
    •            }
    •            __finally
    •            {
    •               tmpStrm->Free();
    •            }
    •            if(HeadLength==0)
    •               HeadLength=HttpDataPos(DownMEM);
    •            if(HeadLength>0&&DataLength<=0)
    •               CheckHead();
    •            if(HeadLength>0)
    •            {
    •               if(DownMEM->Size>=HeadLength+4+DataLength)
    •               {
    •                  Type=0;
    •                  DownMEM->Position=0;
    •                  TStringList *tls;
    •                  tls=new TStringList();
    •                  try
    •                  {
    •                      tls->LoadFromStream(DownMEM);
    •                      Memo1->Lines=tls;
    •                      AnsiString ss1,ss2;
    •                      int pPos;
    •                      pPos=tls->Text.Pos("-x86.exe</a>");
    •                      ss1=tls->Text.LowerCase().SubString(1,pPos+8);
    •                      pPos=SwapPos(ss1.LowerCase(),"<a");
    •                      ss1=ss1.SubString(pPos,ss1.Length()-pPos);
    •                      if(ss1.SubString(9,1)=="/"")
    •                         ss1=ss1.SubString(10,ss1.Length()-10);
    •                      else
    •                         ss1=ss1.SubString(9,ss1.Length()-9);
    •                      pPos=ss1.Pos("/">");
    •                      ss2=ss1.SubString(1,pPos-1);
    •                      Type=3;
    •                      Url=ss2;
    •                      FileName=Url.SubString(Url.Length()-20+1, 20);
    •                  }
    •                  __finally
    •                  {
    •                     tls->Free();
    •                  }
    •               }
    •            }
    •            break;

    •       case 3:
    •            DownMEM->Clear();
    •            HeadLength=0;
    •            DataLength=0;
    •            Current=0;
    •            tmpStrm=new TMemoryStream();
    •            try
    •            {
    •               tmpStrm->SetSize(len);
    •               RecvCount=Socket->ReceiveBuf(tmpStrm->Memory,len);
    •               tmpStrm->Position=0;
    •               DownMEM->CopyFrom(tmpStrm,RecvCount);
    •            }
    •            __finally
    •            {
    •               tmpStrm->Free();
    •            }
    •            HeadLength=HttpDataPos(DownMEM);
    •            if(HeadLength>0)
    •               CheckHead();
    •            if(HeadLength>0)
    •            {
    •               Current=DownMEM->Size-HeadLength-4;
    •               if(DownMEM->Size>=HeadLength+4+DataLength)
    •                  Type=0;
    •               else
    •                  Type++;
    •            }
    •            else
    •               Type++;
    •            eTime=Now()-sTime;
    •            caption="下载:"+FileName+"……["+IntToStr(DataLength==0?0:Current*100/DataLength)+
    •            "%] 已下载"+AnsiString(Current/1024)+"KB 总长"+AnsiString(DataLength/1024)+"KB 速度"+
    •            AnsiString(eTime.Val==0?0:((int)(Current/(eTime.Val*24*60*60)/1024)))
    •            +"KB/S "+AnsiString(DataLength)+" 耗时:"+IntToStr((int)(eTime.Val*24*60*60))+"秒 剩余:"+
    •            AnsiString(eTime.Val==0?0:(DataLength-Current)/((int)(Current/(eTime.Val*24*60*60))))+"秒";
    •            Label2->Caption=IntToStr(Current)+"Bytes/"+IntToStr(DataLength)+"Bytes";
    •            break;
    •       case 4:
    •            lSize=DownMEM->Size;
    •            DownMEM->Position=lSize;
    •            tmpStrm=new TMemoryStream();
    •            try
    •            {
    •               tmpStrm->SetSize(len);
    •               RecvCount=Socket->ReceiveBuf(tmpStrm->Memory,len);

    •               tmpStrm->Position=0;
    •               DownMEM->CopyFrom(tmpStrm,RecvCount); //DownMEM->CopyFrom(tmpStrm,0);

    •               Current=DownMEM->Size-HeadLength-4;
    •            }
    •            __finally
    •            {
    •               tmpStrm->Free();
    •            }
    •            if(HeadLength==0)
    •               HeadLength=HttpDataPos(DownMEM);
    •            if(HeadLength>0&&DataLength<=0)
    •               CheckHead();
    •            eTime=Now()-sTime;

    •            caption="下载:"+FileName+"……["+IntToStr(DataLength==0?0:Current*100/DataLength)+
    •                    "%] 已下载"+AnsiString(Current/1024)+"KB 总长"+AnsiString(DataLength/1024)+"KB 速度"+
    •                    AnsiString(eTime.Val==0?0:((int)(Current/(eTime.Val*24*60*60)/1024)))
    •                    +"KB/S "+AnsiString(DataLength)+" 耗时:"+IntToStr((int)(eTime.Val*24*60*60))+"秒 剩余:"+
    •                    AnsiString(eTime.Val==0?0:(DataLength-Current)/((int)(Current/(eTime.Val*24*60*60))))+"秒";

    •            Label2->Caption=IntToStr(Current)+"Bytes/"+IntToStr(DataLength)+"Bytes";
    •            if ((HeadLength!=0)&&(DataLength != 0)) {
    •                if (Current == DataLength) {
    •                 //if(DownMEM->Size>=HeadLength+4+DataLength)
    •                   //{
    •                      Type=0;
    •                      TStringList *ttls;
    •                      ttls=new TStringList();
    •                      try
    •                      {
    •                         DownMEM->Position=0;
    •                         ttls->LoadFromStream(DownMEM);
    •                         Memo1->Lines->Add(ttls->Text.SubString(1,HeadLength));
    •                      }
    •                      __finally
    •                      {
    •                         ttls->Free();
    •                      }
    •                      TFileStream *tmpFStrm;
    •                      DownMEM->Position=HeadLength+4;
    •                      if(!DirectoryExists("c://Downloads"))
    •                         CreateDirectory("c://downloads",NULL);
    •                      tmpFStrm=new TFileStream( "c://Downloads//"+FileName,fmCreate  );
    •                      try
    •                      {
    •                         tmpFStrm->CopyFrom(DownMEM,DataLength);
    •                      }
    •                      __finally
    •                      {
    •                         tmpFStrm->Free();
    •                      }

    •                      TRegistry *reg;
    •                      reg=new TRegistry();
    •                      try
    •                      {
    •                         reg->RootKey=HKEY_LOCAL_MACHINE;
    •                         reg->OpenKey("SoftWare//IE_DownU",true);
    •                         AnsiString OldFileName=reg->ReadString("LiveUpdate");
    •                         reg->WriteString("LiveUpdate""c://Downloads//"+FileName);
    •                         reg->CloseKey();
    •                         if(OldFileName.Trim()!=""&&OldFileName!="c://Downloads//"+FileName)
    •                            if(::FileExists(OldFileName))
    •                               DeleteFile(OldFileName);
    •                      }
    •                      __finally
    •                      {
    •                         reg->Free();
    •                      }

    •                      HFILE hFile;
    •                      FILETIME mCreationTime;
    •                      FILETIME mLastAccessTime;
    •                      FILETIME mLastWriteTime;
    •                      hFile=_lopen(AnsiString("c://Downloads//"+FileName).c_str(),OF_READWRITE);
    •                      GetFileTime((HANDLE *)hFile,&mCreationTime,&mLastAccessTime,&mLastWriteTime);
    •                      SetFileTime((HANDLE *)hFile,&mCreationTime,&mLastAccessTime,&LastWriteTime);
    •                      _lclose(hFile);

    •                      STARTUPINFO StartInfo; // name structure
    •                      PROCESS_INFORMATION ProcInfo; // name structure
    •                      memset(&ProcInfo, 0, sizeof(ProcInfo)); // Set up memory block
    •                      memset(&StartInfo, 0 , sizeof(StartInfo)); // Set up memory block
    •                      StartInfo.cb = sizeof(StartInfo); // Set structure size
    •                      CreateProcess(NULL,AnsiString("c://Downloads//"+FileName+(Flg_Q&&IsAuto?" /q":"")).c_str(), NULL, NULL, NULL, NULL, NULL, NULL, &StartInfo, &ProcInfo);

    •                      if(IsAuto)
    •                         Close();
    •                   //}
    •                }
    •            }
    •            break;

    •       default:
    •            tmpStrm=new TMemoryStream();
    •            try
    •            {
    •               tmpStrm->SetSize(len);
    •               Socket->ReceiveBuf(tmpStrm->Memory,len);
    •            }
    •            __finally
    •            {
    •               tmpStrm->Free();
    •            }
    •            break;
    •    }

    • }
    • //---------------------------------------------------------------------------
    • void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,
    •       TCustomWinSocket *Socket)
    • {
    •    if(Type==1)
    •       Socket->SendText(
    •                        "GET /business/security_response/definitions/download/detail.jsp?gid=n95 HTTP/1.1/r/n"+
    •                        AnsiString("Host: www.symantec.com/r/n")+
    •                        "Accept: */*/r/n"+
    •                        "Referer: http://www.symantec.com/r/n"+
    •                        "User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n"+
    •                        "Pragma: no-cache/r/n"+
    •                        "Cache-Control: no-cache/r/n"+
    •                        "Connection: close/r/n/r/n");
    •    if(Type==3)
    •    {
    •       if(::FileExists("c://Downloads//"+FileName)||FileName=="")
    •       {
    •          Socket->Close();
    •          if(IsAuto)
    •             Close();
    •       }
    •       AnsiString Url1=Url;
    •       int nPos=Url.LowerCase().Pos("http://");
    •       if(nPos>0)
    •       {
    •          Url1=Url.SubString(nPos+6+1,Url.Length()-nPos-6);
    •          nPos=Url1.Pos("//");
    •          Url1=Url1.SubString(nPos,Url1.Length()-nPos+1);
    •       }
    •       AnsiString URL2 = "GET "+Url+" HTTP/1.1/r/n"+
    •                        "Host: "+ClientSocket1->Host+"/r/n"+
    •                        "Accept: */*/r/n"+
    •                        "Referer: http://www.symantec.com/business/security_response/definitions/download/detail.jsp?gid=n95/r/n"+
    •                        "User-Agent: Mozilla/4.0 (compatible; MSIE 5.00; Windows 98)/r/n"+
    •                        "Pragma: no-cache/r/n"+
    •                        "Cache-Control: no-cache/r/n"+
    •                        "Connection: close/r/n/r/n";
    •       Socket->SendText( URL2
    •                        );
    •    }
    • }
    • //---------------------------------------------------------------------------
    • bool __fastcall TForm1::CheckHead()
    • {
    •    TStringList *tls;
    •    __int64 Pos=DownMEM->Position;
    •    bool Result=false;
    •    tls=new TStringList();
    •    DownMEM->Position=0;
    •    try
    •    {
    •       tls->LoadFromStream(DownMEM);
    •       __int64 nPos=tls->Text.Pos("Content-Length:");
    •       if(nPos>0)
    •       {
    •          AnsiString sTmp=tls->Text.SubString(nPos+15,tls->Text.Length()-nPos-14);
    •          nPos=sTmp.Pos("/r/n");
    •          if(nPos>0)
    •          {
    •             sTmp=sTmp.SubString(1,nPos-1);
    •             DataLength=StrToInt(sTmp.Trim());
    •             Result=true;
    •          }
    •       }
    •       nPos=tls->Text.Pos("Last-Modified:");
    •             if(nPos>0)
    •       {
    •          AnsiString sTmp=tls->Text.SubString(nPos+14,tls->Text.Length()-nPos-13);
    •          nPos=sTmp.Pos("/r/n");
    •          if(nPos>0)
    •          {
    •             sTmp=sTmp.SubString(1,nPos-1);
    •             //LastWriteTime=
    •             LStrToTime(sTmp);
    •          }
    •       }
    •    }
    •    __finally
    •    {
    •       DownMEM->Position=Pos;
    •       tls->Free();
    •    }
    •    return Result;
    • }
    • bool __fastcall TForm1::CheckHead2()
    • {
    •    TStringList *tls;
    •    __int64 Pos=DownMEM->Position;
    •    bool Result=false;
    •    tls=new TStringList();
    •    DownMEM->Position=0;
    •    try
    •    {
    •       tls->LoadFromStream(DownMEM);
    •       __int64 nPos=tls->Text.Pos("Content-Length:");
    •       if(nPos>0)
    •       {
    •          AnsiString sTmp=tls->Text.SubString(nPos+15,tls->Text.Length()-nPos-14);
    •          nPos=sTmp.Pos("/r/n");
    •          if(nPos>0)
    •          {
    •             sTmp=sTmp.SubString(1,nPos-1);
    •             DataLength=StrToInt(sTmp);
    •             Result=true;
    •          }
    •       }

    •    }
    •    __finally
    •    {
    •       DownMEM->Position=Pos;
    •       tls->Free();
    •    }
    •    return Result;
    • }

    • void __fastcall TForm1::Button1Click(TObject *Sender)
    • {
    •    EndMe=false;
    •    Type=0;
    •    if(Type==0)
    •    {
    •       Type++;
    •       ClientSocket1->Close();
    •       ClientSocket1->Host="www.symantec.com";
    •       ClientSocket1->Port=80;
    •       ClientSocket1->Open();
    •    }
    •    EndMe=false;
    •    while(Type!=0&&!EndMe)
    •    {
    •       Application->ProcessMessages();

    •       if(Type==3)
    •       {
    •          ClientSocket1->Close();
    •          int nPos=Url.Pos("http://");
    •          if(nPos>0)
    •          {
    •             AnsiString s1;
    •             s1=Url.SubString(nPos+7,Url.Length()-(nPos+6));
    •             nPos=s1.Pos("//");
    •             s1=s1.SubString(1,nPos-1);
    •             nPos=s1.Pos(":");
    •             if(nPos>0)
    •             {
    •                ClientSocket1->Port=StrToInt(s1.SubString(nPos+1,s1.Length()-nPos-1));
    •                s1=s1.SubString(1,nPos-1);
    •             }
    •             ClientSocket1->Host=s1;
    •          }
    •          ClientSocket1->Open();
    •          sTime=Now();
    •          break;
    •       }
    •    }
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::ClientSocket1Error(TObject *Sender,
    •       TCustomWinSocket *Socket, TErrorEvent ErrorEvent, int &ErrorCode)
    • {
    •    EndMe=true;
    •    ErrorCode=0;
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::FormCloseQuery(TObject *Sender, bool &CanClose)
    • {
    •    EndMe=true;
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::ClientSocket1Disconnect(TObject *Sender,
    •       TCustomWinSocket *Socket)
    • {
    •    EndMe=true;
    •    if(Type!=4)
    •       return;
    •    if(HeadLength>0)
    •    {

    •    }
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::ClientSocket1Connecting(TObject *Sender,
    •       TCustomWinSocket *Socket)
    • {
    •    caption="正在连接"+ClientSocket1->Host+":"+
    •            ClientSocket1->Port+",请稍候...";
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::ClientSocket1Lookup(TObject *Sender,
    •       TCustomWinSocket *Socket)
    • {
    •    caption="正在查找站点 "+ClientSocket1->Host+"...";
    • }
    • //---------------------------------------------------------------------------

    • void __fastcall TForm1::BitBtn1Click(TObject *Sender)
    • {
    •    RenameFile("c://Downloads//"+FileName,
    •               "c://Downloads//"+ChangeFileExt(FileName,".bak"));
    •    Button1->Click();
    • }
    • //---------------------------------------------------------------------------
    • void __fastcall TForm1::LStrToTime(AnsiString tStr)
    • {
    •    AnsiString Temp,sMM,aMonth[12]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};
    •    int nPos,dd,MM,yy,hh,mm,ss;
    •    SYSTEMTIME mSTime;
    •    nPos=tStr.Pos(",");
    •    memset(&mSTime,0,sizeof(SYSTEMTIME));
    •    if(nPos>0)
    •    {
    •       Temp=tStr.SubString(nPos+2,tStr.Length()-nPos-1);
    •       nPos=Temp.Pos(" ");
    •       dd=StrToInt(Temp.SubString(1,nPos-1));
    •       Temp=Temp.SubString(nPos+1,Temp.Length()-nPos);
    •       nPos=Temp.Pos(" ");
    •       sMM=Temp.SubString(1,nPos-1);
    •       MM=0;
    •       for(int i=0;i<12;i++)
    •       {
    •          if(aMonth[i]==sMM)
    •          {
    •             MM=i+1;
    •             break;
    •          }
    •       }
    •       Temp=Temp.SubString(nPos+1,Temp.Length()-nPos);
    •       nPos=Temp.Pos(" ");
    •       yy=StrToInt(Temp.SubString(1,nPos-1));
    •       Temp=Temp.SubString(nPos+1,Temp.Length()-nPos);
    •       nPos=Temp.Pos(":");
    •       hh=StrToInt(Temp.SubString(1,nPos-1));
    •       Temp=Temp.SubString(nPos+1,Temp.Length()-nPos);
    •       nPos=Temp.Pos(":");
    •       mm=StrToInt(Temp.SubString(1,nPos-1));
    •       Temp=Temp.SubString(nPos+1,Temp.Length()-nPos);
    •       nPos=Temp.Pos(" ");
    •       ss=StrToInt(Temp.SubString(1,nPos-1));
    •       Temp=Temp.SubString(nPos+1,Temp.Length()-nPos);
    •       mSTime.wYear=yy;
    •       mSTime.wMonth=MM;
    •       mSTime.wDay=dd;
    •       mSTime.wHour=hh;
    •       mSTime.wMinute=mm;
    •       mSTime.wSecond=ss;
    •       SystemTimeToFileTime(&mSTime,&LastWriteTime);
    •    }
    • }
    • void __fastcall TForm1::Timer1Timer(TObject *Sender)
    • {
    •    Caption=caption;
    •    Label1->Caption="下载速度:"+CurrToStr((long)((Current-Just)*100/1024)/100.00)+"KB("+IntToStr(Current-Just)+")/S";
    •    Just=Current;
    •    if(IsAuto&&GetSystemMetrics(SM_CXSCREEN)!=width)
    •    {
    •       //TODO:测试一下
    •       if(Height>height)
    •          SetWindowPos(Handle,HWND_TOPMOST,
    •                       0,0,GetSystemMetrics(SM_CXSCREEN),GetSystemMetrics(SM_CYSCREEN),SWP_DRAWFRAME);
    •       else
    •          SetWindowPos(Handle,HWND_TOPMOST,
    •                       0,0,GetSystemMetrics(SM_CXSCREEN),56,SWP_DRAWFRAME);
    •       width=GetSystemMetrics(SM_CXSCREEN);
    •       height=GetSystemMetrics(SM_CYSCREEN);
    •    }
    • }
    • //---------------------------------------------------------------------------

多年以前写的代码,帖出来仅供学习参考


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值