关于如何实现wince6.0 下网络抓包的实现总结

自己耗费了2个月的时间在,师傅的帮助下总算将这个功能实现出来了。下面对这个功能的实现进行一个简单的总结。希望这篇文章能给大家帮助。

   首先,如果采用一般的网络通信过程中的原始套接字的功能来实现方法在wince6.0下是行不通的。如何通过套接字实现,网络上已经都讲得很清楚了,这里我就不多说了,大家可以自己去搜索。

实现过程:

1.       从wince自带的目录底下:C:\WINCE600\PUBLIC\COMMON\OAK\UTILS\NETLOG中获取netlog文件夹中自带的所有文件。使用它们来创建一个DLL工程,删掉和工程中自带的头文件,源文件,资源文件中的内容。将netlog文件夹中的文件都增加到工程中,包括netlog.def文件,或者保留 netlog.def然后用C:\WINCE600\PUBLIC\COMMON\OAK\UTILS\NETLOG中的netlog.def替代。另外在头文件中增加一个文件netlogioctl.h。因为这个是属于驱动文件,所以不需要资源文件。然后编译,

2.       出现了无法找到stadx.h的错误。解决办法:属性—C/C++—在预编译头中/创建/使用预编译头中选择“不使用预编译头”

3.       F7出现查找不到npptypes.h的等4个错误。解决办法:属性/C/C++/常规/附加包含目录/中增加C:\WINCE600\PUBLIC\COMMON\OAK\INC

F7出现了124个错误。增加依赖库 修改属性/链接器/输入附加依赖项ndis.lib ;coredll.lib ; corelibc.lib; ole32.lib; oleaut32.lib; uuid.lib; commctrl.lib ;模块定义文件.\netlog.def修改成netlog.def;延迟加载的DLL中增加$(NOINHERIT);  f7编译通过。

 

开始对程序进行修改:

1.       首先设置混杂模式和设置普通模式。即在netlogioctl.h中增加两个define

IOCTL_NETLOG_SETMODE     9   IOCTL_NETLOG_RESETMODE    10 这两个值分别对应着cestream.cppextern  “c ” BOOL NLG_IOControl函数中的 case值里面的代码为

{

case  IOCTL_NETLOG_SETMODE : //设置混杂模式

case  IOCTL_NETLOG_RESETMODE: //恢复普通模式

{

HANDLE h;

DWORD dwFilter = NDIS_PACKET_TYPE_PROMISCUOUS;

if(dwCode == IOCTL_NETLOG_RESETMODE)

dwFilter = NDIS_PACKET_TYPE_DIRECTED | NDIS_PACKET_TYPE_MULTICAST | NDIS_PACKET_TYPE_ALL_MULTICAST | NDIS_PACKET_TYPE_BROADCAST;

 

RETAILMSG(1, (_T("IOCTL_NETLOG_SETMODE/IOCTL_NETLOG_SETMODE %s\r\n"), (WCHAR *)pBufIn));

h = CreateFile(

NDISUIO_DEVICE_NAME,                                //    Object name.

                             0x00,                                    //    Desired access.

                             0x00,                                    //    Share Mode.

                             NULL,                                   //    Security Attr

OPEN_EXISTING,                                  //    Creation Disposition.

FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,    //    Flag and Attributes..

           (HANDLE)INVALID_HANDLE_VALUE);

                                       if (INVALID_HANDLE_VALUE != h)

                                       {

                                                DWORD dwWritten;

 

                                                struct {

                                                         NDISUIO_SET_OID SetOid;

                                                         unsigned char   uca[64];

                                                } NdisUioSetOid;

 

                                                // --- Set Authentication for the connection

 

                                                NdisUioSetOid.SetOid.Oid = OID_GEN_CURRENT_PACKET_FILTER;

                                                NdisUioSetOid.SetOid.ptcDeviceName = (WCHAR *)pBufIn;

                                                *(PDWORD)NdisUioSetOid.SetOid.Data = dwFilter;

          

                                                dwWritten = 0;

                                                if (! DeviceIoControl(

                                                         h,

                                                         IOCTL_NDISUIO_SET_OID_VALUE,

                                                         &NdisUioSetOid,

                                                         sizeof(NdisUioSetOid),

                                                         NULL,

                                                         0,

                                                         &dwWritten,

                                                         NULL)) {

                                                                   CloseHandle(h);               

                                                                   goto error;                

                                                }}

F7

这时候出现了18个错误。增加两个头文件

#include <ndis.h>

#include <nuiouser.h>

F7出现一个错误

wcsncpy(Globals.state.wszCapFileBaseName,(USHORT *)

pBufIn,dwLenIn);

这时候将USHORT 改成 wchar_t就可以了

这里主要是设置混杂模式,即全部抓包。

另外还需要设置

Case  IOCTL_NETLOG_SET_FILTER:      //设置过滤条件

case  IOCTL_NETLOG_CAPTURE_FILTER_PACKET:       //上传过滤的数据

两个值

 

2.PacketFilterAndDump(

   IN  PWSTR wszProtocolName,

   IN  PWSTR wszStreamName,

   IN  PSTR  szDirection,

   IN  PBYTE pPacket,

   IN  DWORD cbPacket,

   IN  PWSTR wszPacketType)函数中的增加这段代码,表示抓包为IP包。

         //判断是否存在VLAN标记,

                   int bVlan = 0;

                   int bQinQ = 0;

                   //2次嵌套

                   if(ntohs(*(short *)(pPacket + 12)) == 0x8100)

                  {

                            bVlan = 1;

                            if(ntohs(*(short *)(pPacket + 16)) == 0x8100)

                            {

                                     bQinQ = 1;

                            }

                   }

                   int bOffset = 12;

                   bOffset += (bVlan + bQinQ) * 4;      

                   short nType = ntohs(*(short *)(pPacket + bOffset));

                   //RETAILMSG(1,(TEXT("%x\r\n"), nType));

                   if(nType == 0x0800) //IP

                   {

                            bOffset += 2;

                            //RETAILMSG(1,(TEXT("netlogPalen = %d  bOffset = %d \r\n"), cbPacket, bOffset));

                            BOOL ret = Packet_analyze((char *)pPacket + bOffset, cbPacket - bOffset);

//                       if (ret == FALSE)

//                       {

//                                 RETAILMSG(1,(TEXT("Failed\r\n")));

//                       }

                   }

        // No adjustment Necassary.

//         pFrame=pPacket;

//         cbFrame=cbPacket;

                   return true;

3.开始编写上层的代码了。

主要有两个函数,大家参照我这几个函数来写吧,这些函数可以放置在一个cpp中

/*--------------------------------------------------------------------

【函数介绍】: 关闭监听,设置驱动为正常模式,卸载CXport驱动

【入口参数】: pWnd用于指定父窗口句柄

【出口参数】: ()

【返回值】: TRUE:打开成功;FALSE:打开失败

---------------------------------------------------------------------*/

bool CTCPClient_CE::Close_Monit()

{

   //设置线程退出事件

   SetEvent(m_exitThreadEvent);

  

   //设置网卡为正常模式

   //获取网卡的名称

   if (!DeviceIoControl(m_hDriver,

      IOCTL_NETLOG_RESETMODE,

      (LPBYTE)ADAPTER_NAME,//网卡名称

      (_tcslen(ADAPTER_NAME) + 1) * sizeof(TCHAR),

      NULL,

      NULL,

      NULL,

      NULL

      ))

   {

      return FALSE;

   }

 

   //设置开始

   if (!DeviceIoControl(m_hDriver,

      IOCTL_NETLOG_STOP,

      NULL,

      NULL,

      NULL,

      NULL,

      NULL,

      NULL

      ))

   {

      return FALSE;

   }

 

   CloseHandle(m_hDriver);  //关闭驱动

  

 

   //调用CExport驱动,进行卸载

   SendCommandToCxport(IOCTL_NETLOG_UNLOAD, NULL, NULL, NULL, NULL);  

 

   //卸载驱动

   if(m_hDevice)

      DeactivateDevice(m_hDevice);

}

 

/*--------------------------------------------------------------------

【函数介绍】: 用于打开客户端监听socket

【入口参数】: pWnd用于指定父窗口句柄

【出口参数】: ()

【返回值】: TRUE:打开成功;FALSE:打开失败

---------------------------------------------------------------------*/

bool CTCPClient_CE::Open_Monit(CWnd * pWnd)

{

   //初始化socket环境

 

   //复位线程退出事件

   ResetEvent(m_exitThreadEvent);

   //存储父窗口句柄

   m_pOwnerWnd = pWnd;

   HOSTENT* pHost = NULL;

   CHAR*    pszIp = NULL;

   unsigned long uLIpsource = 0;

   SOCKADDR_IN Addr;

   //创建TCP套接字

   //m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_IP);//IPPROTO_IP == 0

   CHAR szHostName[128] = {0};//名称

   //根据名称找到对应的网卡

   if(gethostname(szHostName, sizeof(szHostName))==0)

   {

      pHost = gethostbyname(szHostName);

      if(pHost != NULL)

      {

         //获取IP

         pszIp = inet_ntoa(*(in_addr*)pHost->h_addr_list[0]);

         uLIpsource = inet_addr(pszIp);

         //设置参数填充SOCKADDR_IN结构

         Addr.sin_addr = *(in_addr*)pHost->h_addr_list[0];

         TRACE(_T("%d.%d.%d.%d"),Addr.sin_addr.S_un.S_un_b.s_b1,

            Addr.sin_addr.S_un.S_un_b.s_b2,

            Addr.sin_addr.S_un.S_un_b.s_b3,

            Addr.sin_addr.S_un.S_un_b.s_b4);

         Addr.sin_family = AF_INET;

         Addr.sin_port =(USHORT)m_port;//htons(m_port);

         //将原始的套接字SOCK绑定到本地网卡地址上

         //if(bind(m_socket,(PSOCKADDR)&Addr, sizeof(Addr)) == SOCKET_ERROR)

         //{

         // AfxMessageBox(_T("Bind = false!"));

         // closesocket(m_socket);

         // return FALSE;

         //}

         CreateNetLog();//填写注册表

         //调用CExport驱动

         SendCommandToCxport(IOCTL_NETLOG_LOAD, NULL, NULL, NULL, NULL);

 

         //激活驱动

         BOOL Ret = SendCommandToDriver(NETLOG_DEVICE_KEY, NETLOG_LEGACY_NAME, ADAPTER_NAME);

         if (Ret == FALSE)

         {

            //驱动打开失败

            return FALSE;

         }

      }

      else

      {

         AfxMessageBox(_T("pHost = NULL!"));

         closesocket(m_socket);

         return FALSE;

      }

 

   }

   else

   {

      AfxMessageBox(_T("can't find host name!"));

      closesocket(m_socket);

      return FALSE;

   }

 

   if (m_socket == SOCKET_ERROR)

   {

      closesocket(m_socket);

      return FALSE;

   } 

 

   //创建通讯线程

   m_tcpThreadHandle = CreateThread(NULL,0,Monitor_ThreadFunc,this,0,NULL);

   if (m_tcpThreadHandle == NULL)

   {

      closesocket(m_socket);

      return FALSE;

   }

   return TRUE;

}

// 创建Netlog驱动相关的注册表信息

BOOL CTCPClient_CE::CreateNetLog()

{

   //手工添加注册表 

   HKEY  hKey;

   DWORD dwDisp = 0;

   HRESULT hr = ERROR_SUCCESS;

   CString strPath, strNlgDrvPath;

   TCHAR szBuffer[_MAX_PATH];

   static HANDLE hNlgLoadHandle = INVALID_HANDLE_VALUE;

   HANDLE hNlgHandle = INVALID_HANDLE_VALUE;

 

 

   DWORD dwRet = RegCreateKeyEx(HKEY_LOCAL_MACHINE,

      TEXT("Drivers\\Netlog"),

      0,

      NULL,

      REG_OPTION_NON_VOLATILE,

      KEY_ALL_ACCESS,

      NULL,

      &hKey,

      &dwDisp);

   if( dwRet != ERROR_SUCCESS )

   {

      //打开注册表失败

      return FALSE;     

   } 

 

   ::GetModuleFileName(AfxGetInstanceHandle(), szBuffer, _MAX_PATH);

   strPath = szBuffer;

   strPath = strPath.Left(strPath.ReverseFind(_T('\\')));

   strPath += _T("\\netlog.dll");

   strNlgDrvPath = _T("\\Windows\\netlog.dll");

   DeleteFile(strNlgDrvPath);

   CopyFile(strPath, strNlgDrvPath, FALSE);

 

   //如果不是新建键值,则进行子键添加

   //if (dwDisp != REG_OPENED_EXISTING_KEY)

   {

      //RETAILMSG(1, (_T("CreateReg DMM!")));

      if (ERROR_SUCCESS != (hr = RegSetValueEx (hKey, L"Dll", 0, REG_SZ, (BYTE *)strNlgDrvPath.GetBuffer(0), strNlgDrvPath.GetLength() * 2))) {

         RegCloseKey (hKey);        

         return FALSE;

      }

      DWORD dwValue = 0x41;

      if (ERROR_SUCCESS != (hr = RegSetValueEx (hKey, L"Order", 0, REG_DWORD, (BYTE *)&dwValue, sizeof(DWORD)))) {

         RegCloseKey (hKey);        

         return FALSE;

      }

      dwValue = 0;

      if (ERROR_SUCCESS != (hr = RegSetValueEx (hKey, L"Index", 0, REG_DWORD, (BYTE *)&dwValue, sizeof(DWORD)))) {

         RegCloseKey (hKey);        

         return FALSE;

      } 

     if (ERROR_SUCCESS != (hr = RegSetValueEx (hKey, L"Prefix", 0, REG_SZ, (BYTE *)L"NLG", sizeof(L"NLG")))) {

         RegCloseKey (hKey);        

         return FALSE;

      }       

   }

   RegCloseKey(hKey);

 

   return TRUE;

}/*--------------------------------------------------------------------

【函数介绍】: 获取设置的IP

【入口参数】: IP1主站IP2从站

【出口参数】: ()

【返回值】:

---------------------------------------------------------------------*/

void CTCPClient_CE::GetSet_IP(DWORD IP1,DWORD IP2, int port)

{

   m_MainIP = IP1;

   m_SecoIP = IP2;

 

   m_filterIP.MainIP = ntohl(IP1);

   m_filterIP.SeconIP = ntohl(IP2);

   m_filterIP.nport = ntohs(port);

}

 

 

BOOL CTCPClient_CE::SendCommandToCxport(

                    IN  DWORD cmd,

                    IN  PVOID pIn,

                    IN  DWORD cbIn,

                   OUT PVOID pOut,

                    IN  DWORD cbOut)

{

   DWORD cbReturned = 0;

   // Failing to Activate is not fatal.

   ce::auto_hdevice hDeviceActivateDevice(CXPORT_DEVICE_KEY, NULL);

 

   ce::auto_hfile hDriver = CreateFile(CXPORT_LEGACY_NAME, GENERIC_READ | GENERIC_WRITE,

      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

 

   if(!hDriver.valid())

   {    

      RETAILMSG(1, (L"Unable to load %s, CreateFile failed 0x%x\n",CXPORT_LEGACY_NAME, GetLastError()));

      return FALSE;

   }

 

   if (!DeviceIoControl(hDriver,

      cmd,

      pIn,

      cbIn,

      pOut,

      cbOut,

      &cbReturned,

      NULL

      ))

   {

      return FALSE;

   }

 

   return TRUE;

}

//---------------------------------------------------------------------

//发送命令到驱动--- 打开驱动

//szDeviceKey 驱动的名称,szLegacyName 驱动的路径,cmd命令

//pIn 输入的缓存区,cbIn缓存区的大小pcbReturned 返回值

//---------------------------------------------------------------------

BOOL CTCPClient_CE::SendCommandToDriver(LPCWSTR szDeviceKey, LPCWSTR szLegacyName,LPCWSTR szadaptername)

{

   // Failing to Activate is not fatal.

   DWORD nDw = 0;

   m_hDeviceActivateDevice(szDeviceKey, nDw);

   if(m_hDevice == 0 || m_hDevice == INVALID_HANDLE_VALUE)

   {

      nDw = GetLastError();

   }

 

   m_hDriver = CreateFile(szLegacyName, GENERIC_READ | GENERIC_WRITE,

      FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

 

   if(m_hDriver == INVALID_HANDLE_VALUE)

   {    

      RETAILMSG(1, (L"Unable to load %s, CreateFile failed 0x%x\n",szLegacyName,GetLastError()));

      MessageBox(NULL, _T("Netlog驱动打开失败!"), _T("警告"), MB_OK);

      return FALSE;

   }

 

   //设置混杂模式

   //获取网卡的名称

 

   if (!DeviceIoControl(m_hDriver,

      IOCTL_NETLOG_SETMODE,

      (LPBYTE)szadaptername,//网卡名称

      (_tcslen(szadaptername) + 1) * sizeof(TCHAR),

      NULL,

      NULL,

      NULL,

      NULL

      ))

   {

      return FALSE;

   }

 

   //设置过滤条件

   if (!DeviceIoControl(m_hDriver,

      IOCTL_NETLOG_SET_FILTER,

      (PVOID)&m_filterIP,

      sizeof(STFilter_IP),

      NULL,

      NULL,

      NULL,

      NULL

      ))

   {

      return FALSE;

   }

 

   //设置开始

   if (!DeviceIoControl(m_hDriver,

      IOCTL_NETLOG_START,

      NULL,

      NULL,

      NULL,

      NULL,

      NULL,

      NULL

      ))

   {

      return FALSE;

   }

 

   return TRUE;

 

}

 

/*--------------------------------------------------------------------

【函数介绍】此线程用于监听TCP客户端通讯的事件,例如当接收到数据、

连接断开和通讯过程发生错误等事件

【入口参数】lparam:无类型指针,可以通过此参数,向线程中传入需要用到的资源。

在这里我们将CTCPClient_CE类实例指针传进来

【出口参数】()

【返回值】返回值没有特别的意义,在此我们将返回值设为。

---------------------------------------------------------------------*/

DWORD CTCPClient_CE::Monitor_ThreadFunc(LPVOID lparam)

{

   CTCPClient_CE *pSocket;

   //得到CTCPClient_CE实例指针

   pSocket = (CTCPClient_CE*)lparam;

   BYTE szBufer[30 * 1024];

   while (TRUE)

   {

      //收到退出事件,结束线程

      if (WaitForSingleObject(pSocket->m_exitThreadEvent,100) == WAIT_OBJECT_0)

      {

         break;

      }

 

      int recvLen;

 

      ZeroMemory(szBufer, sizeof(szBufer));

      //设置读取事件

      if (!DeviceIoControl(pSocket->m_hDriver,

         IOCTL_NETLOG_CAPTURE_FILTER_PACKET,

         NULL,//

         NULL,

         szBufer,

         sizeof(szBufer),

         (LPDWORD)&recvLen,

         NULL

         ))

      {

         return FALSE;

 

      }

      //打印缓冲区

     

      if (recvLen != 0)

      {

         //过滤分析

         BOOL ret pSocket->Packet_analyze((const char *)szBufer, recvLen);

         if (ret == TRUE)

         {

            //RETAILMSG(1, (_T("deal with Data complete.\r\n")));

         }

         else

         {

            //RETAILMSG(1, (_T("deal with Data complete flase.\r\n")));

         }

      }

      Sleep(20);

   }

   RETAILMSG(1, (_T("Monitor_ThreadFunc Exit.\r\n")));

   return 0;

}

4. /*--------------------------------------------------------------------

【函数介绍】: 获取IP包的数据

【入口参数】: buf抓取到的包,len包的长度

【出口参数】: ()

【返回值】: TRUE:打开成功;FALSE:打开失败

---------------------------------------------------------------------*/

BOOL CTCPClient_CE::Get_IP_packet_Data(char * buf, int len)

{

   ip_hdr * p_IPHdr = (ip_hdr *)buf;

   int pack_len = 0;

   tcp_hdr *pTCPHead;

   udp_hdr  *pUDPHead;

   icmp_hdr *pICMPHead;

   BYTE  *pdata = NULL;

   int HdrLen = 0;

   BOOL Ret = FALSE;

   //方向判断

   BOOL DIR = FALSE;

   if(m_filterIP.MainIP == p_IPHdr->srcaddr)

   {

      DIR = FALSE;

   }

   else

   {

      DIR = TRUE;

   }

   RETAILMSG(1, (_T(" MainIP = %x  ,srcaddr = %x  ,DIR = %x"), m_filterIP.MainIP, p_IPHdr->srcaddr, DIR));

   HdrLen = p_IPHdr->ihl * 4;    

   pack_len = ntohs(p_IPHdr->tot_len);

   pack_len -= HdrLen;

 

   //RETAILMSG(1,(_T(" len = %d, Packlen = %d \r\n"), len, pack_len));

  

   switch(p_IPHdr->protocol)//协议判断

   {

      case IPPROTO_ICMP:

         {

            pICMPHead=(icmp_hdr *)(buf+HdrLen);

            //strL4.Format(" type:%d code:%d\n",pICMPHead->Type,pICMPHead->Code);

            pdata=((BYTE *)pICMPHead) + sizeof(icmp_hdr);

            pack_len -= sizeof(icmp_hdr);

           

         }

         break;

   case IPPROTO_TCP:

      {

         pTCPHead=(tcp_hdr *)(buf+HdrLen);

         HdrLen = pTCPHead->thl//in fact only 4 bits

         HdrLen *= 4;//TCP头的实际长度

 

         pdata=((BYTE *)pTCPHead)+HdrLen;

         pack_len -= HdrLen;

         //TRACE(_T("HdrLen = %d, packinlen = %d"), HdrLen, pack_len);

         //buf = (char *)pdata;

         //memcpy(buf, pdata, len);

         //len = pack_len;

         RETAILMSG(1, (_T(" %d \r\n"), pack_len));

         RetTRUE;

      }

      break;

   case IPPROTO_UDP:

      {

         pUDPHead=(udp_hdr *)(buf+HdrLen);

         pdata=((BYTE *)pUDPHead) + sizeof(udp_hdr);

         pack_len -= sizeof(udp_hdr);

         //buf = (char *) pdata;

         //len = pack_len;

         RetTRUE;

        

      }

      break;

   }

//    for (int i = 0; i<pack_len; i++)

//    {

//        RETAILMSG(1, (_T(" %x "), *(pdata + i)));

//    }

   if (pack_len != 0)

   {

      int inlen = 0;

      if (DIR == FALSE)//

      {

         memcpy(m_MainMonitBuf + m_MainBufLen, pdata, pack_len);

         m_MainBufLen += pack_len;

         inlen = m_MainBufLen;

         //主站

         if(pdata)

         {

            OnRead(m_pOwnerWnd,(char *)m_MainMonitBuf, m_MainBufLen, DIR);

            if(m_MainBufLen != 0)

            {

                memcpy(m_MainMonitBuf, m_MainMonitBuf + (inlen - m_MainBufLen), m_MainBufLen);//将剩余的拷贝到前面     

            }

            memset(m_MainMonitBuf + m_MainBufLen, 0, (inlen - m_MainBufLen));//清空后面的

         }

      }

      else

      {

         //从站

         memcpy(m_SecoMonitBuf + m_SecoBufLen, pdata, pack_len);

         m_SecoBufLen += pack_len;

         inlen = m_SecoBufLen;

         //从站

         if(pdata)

         {

            OnRead(m_pOwnerWnd,(char *)m_SecoMonitBuf, m_SecoBufLen, DIR);

            if(m_SecoBufLen != 0)

            {

                memcpy(m_SecoMonitBuf, m_SecoMonitBuf + (inlen - m_SecoBufLen), m_SecoBufLen);//将剩余的拷贝到前面     

            }

            memset(m_SecoMonitBuf + m_SecoBufLen, 0, (inlen - m_SecoBufLen));//清空后面的

         }

      }

   }

  

  

   return Ret;

}

 

可能后面的函数有点乱,因为时间及也没好好整理,等以后有时间的时候设法弄成一个模块。有错误的地方,希望大家能多多包涵,谢谢大家

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值