SIM300模块GPRS应用

  1. GPRS概述
    1. GPRS特点

      GPRS(General Packet Radio Service,通用无线分组业务)作为第二代移动通信技术GSM向第三代移动通信(3G)的过渡技术,是由英国BT Cellnet公司早在1993年提出的,是GSM Phase2+ (1997年)规范实现的内容之一,是一种基于GSM的移动分组数据业务,面向用户提供移动分组的IP或者X.25连接。

  GPRS是在现有的GSM网络基础上叠加的一个新的网络,同时在网络设备上增加一些硬件设备,并对原软件升级,形成了一个新的网络逻辑实体。GPRS能给用户提供端到端的、广域的无线IP连接。通俗地讲,GPRS是一项无线高速数据传输技术,它以分组交换技术为基础,用户通过GPRS可以在移动状态下使用各种高速数据业 务,包括收发E-mail、进行Internet浏览、即时聊天等。

    1. GPRS与CSD的不同点

      GTR60支持接入互联网的方式有两种:GPRS和CSD方式。

CSD的全称为Circuit Switched Data ,是电路交换数据业务的简称,是一种移动上网方式。CSD链接就是语音通信正在进行的意思,CSD方式相当于拨号上网,接入号是17266,模拟式数据传输,在GPRS之前时的WAP上网就是采取这种连接方式。

CSD现只有全球通的卡才具备这种功能,这是用数据交换的方式利用手机上网的一种。

CSD是按时间计费的,GPRS是按流量计费的。

CSD就像电脑的拨号上网,而GPRS就像宽带上网。

一种直观的描述:使用CSD传数据时就像打电话一样,不管你是否在讲话或传送数据,系统都会在上下行的频段中保留一个信道给你,其费率是以使用时间的长短来计算的(一般0.15元/分钟),很不划算,而且它的传输速率远比不上GPRS。GPRS理论速度能达到170kbps,实际大概在14.4k-43.2kbps,CSD为9.6kbps

  1. GTR60内置的网络协议
  2. AT命令实现TCP/IP通讯

      见Application Note for SIM100 TCP/IP AT Commands.pdf

  1. ppp拨号连接实现TCP/IP通讯
    1. ppp拨号概述

      ppp协议是TCP/IP网络协议集合中的一个子协议,主要用来创建电话线路以及ISDN拨号接入ISP的连接,具有多种身份验证方法、数据压缩和加密以及通知IP地址等功能。PPP协议是SLIP协议的替代协议,在功能上没有太大的区别。

      1. 介绍

      PPP(Point-to-Point Protocol点到点协议)是为在同等单元之间传输数据包这样的简单链路设计的链路层协议。这种链路提供全双工操作,并按照顺序传递数据包。设计目的主要是用来通过拨号或专线方式建立点对点连接发送数据,使其成为各种主机、网桥和路由器之间简单连接的一种共通的解决方案。

      1. PPP链路建立过程

      PPP协议中提供了一整套方案路建立、维护、拆除、上层协议协商、认证等问题。PPP协议包含这样几个部分:链路控制协议LCP(Link Control Protocol);网络控制协议NCP(Network Control Protocol);认证协议,最常用的包括口令验证协议PAP(Password Authentication Protocol)和挑战握手验证协议CHAP(Challenge-Handshake Authentication Protocol)。

  LCP负责创建,维护或终止一次物理连接。NCP是一族协议,负责解决物理连接上运行什么网络协议,以及解决上层网络协议发生的问题。

  下面介绍PPP链路建立的过程:

  PPP链路状态机如图1所示。一个典型的链路建立过程分为三个阶段:创建阶段、认证阶段和网络协商阶段。

  阶段1:创建PPP链路

  LCP负责创建链路。在这个阶段,将对基本的通讯方式进行选择。链路两端设备通过LCP向对方发送配置信息报文(Configure Packets)。一旦一个配置成功信息包(Configure-Ack packet)被发送且被接收,就完成了交换,进入了LCP开启状态。

  应当注意,在链路创建阶段,只是对验证协议进行选择,用户验证将在第2阶段实现。
  阶段2:用户验证

  在这个阶段,客户端会将自己的身份发送给远端的接入服务器。该阶段使用一种安全验证方式避免第三方窃取数据或冒充远程客户接管与客户端的连接。在认证完成之前,禁止从认证阶段前进到网络层协议阶段。如果认证失败,认证者应该跃迁到链路终止阶段。

  在这一阶段里,只有链路控制协议、认证协议,和链路质量监视协议的packets是被允许的。在该阶段里接收到的其他的packets必须被静静的丢弃。

  最常用的认证协议有口令验证协议(PAP)和挑战握手验证协议(CHAP)。 认证方式介绍在第三部分中介绍。

  阶段3:调用网络层协议
  认证阶段完成之后,PPP将调用在链路创建阶段(阶段1)选定的各种网络控制协议(NCP)。选定的NCP解决PPP链路之上的高层协议问题,例如,在该阶段IP控制协议(IPCP)可以向拨入用户分配动态地址。

这样,经过三个阶段以后,一条完整的PPP链路就建立起来了。

      1. 认证方式

1)口令验证协议(PAP)

  PAP是一种简单的明文验证方式。NAS(网络接入服务器,Network Access Server)要求用户提供用户名和口令,PAP以明文方式返回用户信息。很明显,这种验证方式的安全性较差,第三方可以很容易的获取被传送的用户名和口令,并利用这些信息与NAS建立连接获取NAS提供的所有资源。所以,一旦用户密码被第三方窃取,PAP无法提供避免受到第三方攻击的保障措施。

  2)挑战-握手验证协议(CHAP)

  CHAP是一种加密的验证方式,能够避免建立连接时传送用户的真实密码。NAS向远程用户发送一个挑战口令(challenge),其中包括会话ID和一个任意生成的挑战字串(arbitrary challengestring)。远程客户必须使用MD5单向哈希算法(one-way hashing algorithm)返回用户名和加密的挑战口令,会话ID以及用户口令,其中用户名以非哈希方式发送。

CHAP对PAP进行了改进,不再直接通过链路发送明文口令,而是使用挑战口令以哈希算法对口令进行加密。因为服务器端存有客户的明文口令,所以服务器可以重复客户端进行的操作,并将结果与用户返回的口令进行对照。CHAP为每一次验证任意生成一个挑战字串来防止受到再现攻击(replay attack)。在整个连接过程中,CHAP将不定时的向客户端重复发送挑战口令,从而避免第3方冒充远程客户(remote client impersonation)进行攻击。

      1. ppp帧格式

PPP帧格式和HDLC帧格式相似,如图1所示。二者主要区别:PPP是面向字符的,而HDLC是面向位的。

图1 PPP帧格式

    可以看出,PPP帧的前3个字段和最后两个字段与HDLC的格式是一样的。标志字段F为0x7E(0x表示7E),但地址字段A和控制字段C都是固定不变的,分别为0xFF、0x03。PPP协议不是面向比特的,因而所有的PPP帧长度都是整数个字节。

    与HDLC不同的是多了2个字节的协议字段。协议字段不同,后面的信息字段类型就不同。如:

0x0021——信息字段是IP数据报

0xC021——信息字段是链路控制数据LCP

0x8021——信息字段是网络控制数据NCP

0xC023——信息字段是安全性认证PAP

0xC025——信息字段是LQR

0xC223——信息字段是安全性认证CHAP

    当信息字段中出现和标志字段一样的比特0x7E时,就必须采取一些措施。因PPP协议是面向字符型的,所以它不能采用HDLC所使用的零比特插入法,而是使用一种特殊的字符填充。具体的做法是将信息字段中出现的每一个0x7E字节转变成2字节序列(0x7D,0x5E)。若信息字段中出现一个0x7D的字节,则将其转变成2字节序列(0x7D,0x5D)。若信息字段中出现ASCII码的控制字符,则在该字符前面要加入一个0x7D字节。这样做的目的是防止这些表面上的ASCII码控制字符被错误地解释为控制字符。

    1. ppp拨号下实现TCP/IP通讯

      在PC(windows、linux)或嵌入式设备上实现ppp拨号连接,当通过windows的ppp拨号连接与远程服务器建立ppp连接后,PC上的TCP/IP通讯可以通过socket编程实现。Ppp拨号及TCP/IP通讯框图如下:

TCP

IP

PPP

GPRS

TCP

IP

GPRS网关

协议

GPRS网络

PPP

GPRS

GPRS网关

协议

数据链路层

物理层

    传输层

    网络层

Internet终端

      本文档以windows下的拨号为例,来实现ppp拨号。

    1. windows(windows 2003)下建立ppp拨号
      1. 安装调制解调器

      此时将SIM300模块作为调制解调器安装。在控制面板中添加调制解调器。

添加:

下一步:

下一步:

指定串口后,下一步,直到完成。

最后设置调制解调起的属性:

设定最大端口速度。

设定GPRS网络初始化AT指令。

“更改默认首选项”:

数据流控制:无。

最后诊断调制解调器,测试其是否能正常工作:

到此为止,调制解调器安装完毕,增加网络拨号:

在“网络连接”à“新建网路连接”:

下一步,直到完成。

通过其在桌面建立的拨号连接,即可接入internet网络。在windows上的网络应用全部转换为socket编程。

    1. Pc上Ppp拨号AT流程

      以上为在pc下通过ppp拨号上网的例子,可以看出pc上ppp拨号流程被图形化。

      利用GPRS无线上网通常有以下几个步骤1、GPRS无线模块上电2、通过发送AT指令与中国移动网的节点服务器建立连接3、在连接建立后进行相应的PPP配置PPP配置成功后即可获得节点服务器配置IP地址进行网络通讯了。

      Ppp拨号实际流程如下:

  1. ATE0V1    //返回:OK
  2. AT+CGDCONT=1,”IP”,”CMNET”    //返回:OK 写PDP上下文信息,连接到中国移动gprs网络
  3. ATS0=0  //返回:OK
  4. ATDT*99#   //返回:CONNECT(拨号连接)
  5. 接下来就时ppp协议握手过程,由操作系统的ppp协议栈完成,若握手不成功则自动断开。
  6. ATH          //挂断
    1. GTR60 ppp拨号实现TCP通讯

      在GTR60内模块也内置了TCP/IP协议栈。因此可以通过ppp方式实现拨号上网,完成tcp通讯。因此当ppp拨号完成以后,网络数据传输则通过socket编程(待完善)来实现。

  1. SIMCOM平台代码实现关闭ppp拨号

      若pc死机,则可通过话机菜单项来关闭ppp拨号,而无需重新启动话机。

      发送三个信号,函数如下:

void custSendDsPppConfigReq()

{

       SignalBuffer sendSignal = kiNullBuffer;

       DsPppConfigReq *sigBody_p=PNULL;

       ENTRY(custSendDsPppConfigReq);

     KiCreateZeroSignal(SIG_DS_PPP_CONFIG_REQ,sizeof(DsPppConfigReq),&sendSignal);

       sigBody_p = (DsPppConfigReq*)sendSignal.sig;

       sigBody_p->bufferSize = 0x065a;

       sigBody_p->dataOffset = 0;

       sigBody_p->escapeCharacterMapTx = 0xffffffff;

       KiSendSignal(VG_CI_TASK_ID, &sendSignal);

       EXIT(custSendDsPppConfigReq);

}

void custSendPppNoCarrierReq()

{

       SignalBuffer sendSignal = kiNullBuffer;

       CiPppNoCarrierReq *sigBody_p;

       ENTRY(custSendPppNoCarrierReq);

       KiCreateZeroSignal(SIG_CI_PPP_NO_CARRIER_REQ,sizeof (CiPppNoCarrierReq), &sendSignal);

       sigBody_p = (CiPppNoCarrierReq*)sendSignal.sig;

       sigBody_p->cause = PPP_CAUSE_NOT_SET;

       sigBody_p->connectionType = PPP_NEGOTIATION;

       KiSendSignal(VG_CI_TASK_ID, &sendSignal);

       EXIT(custSendPppNoCarrierReq);

}

void custSendApexAbgpReleaseReq()

{

       SignalBuffer sendSignal = kiNullBuffer;

       ApexAbgpReleaseReq *sigBody_p;

       ENTRY(custSendApexAbgpReleaseReq);

       KiCreateZeroSignal(SIG_APEX_ABGP_RELEASE_REQ,sizeof (ApexAbgpReleaseReq), &sendSignal);

       sigBody_p = (ApexAbgpReleaseReq*)sendSignal.sig;

       sigBody_p->taskId =FOREGROUND_TASK_ID;

       sigBody_p->isMEInitiated = TRUE;

       sigBody_p->cause = PPP_CAUSE_NOT_SET;

       sigBody_p->nsapi = GPRS_NSAPI_5;

       KiSendSignal(TASK_BL_ID, &sendSignal);

       EXIT(custSendApexAbgpReleaseReq);

}

Void shutDownPPP()

{

      custSendDsPppConfigReq();

     custSendPppNoCarrierReq();

     custSendApexAbgpReleaseReq();

}

  1. Socket编程实现

什么是Socket,为什么要用Socket?

应用层通过传输层进行数据通信时,TCP和UDP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了称为套接字(Socket )的接口,区分不同应用程序进程间的网络通信和连接。
生成套接字,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。

    1. 域名解析
      1. DNS任务启动

      当需要DNS解析是时,启动DNS解析任务即可。

afemh08ConfigureDns();即发送信号:SIG_DNS_CONFIG_REQ。启动DNS procedure.

      1. DNS解析

函数:extern Int16 gethostbyname(TaskId taskId,

                                               const Char *name_p,  // 域名

                           DnsCallbackProc callbackProc_p, //

                                                     Int32 *commandRef_p);

      说明:发送dns解析请求

      例子:

 gethostbyname(TASK_FL_ID,

    context_p->szDestAddress,

     afshDnsCallbackProc, //回调函数,发送SIG_AFSH_DNS_EVENT_SIG

        &(context_p->cmdRefDnsInd));

      当域名解析成功后,DNS procedure会发送一个SIG_AFSH_DNS_EVENT_SIG信号,告知解析的ip地址。

通过信号SIG_AFSH_DNS_EVENT_SIG传送响应的域名解析数据。

typedef struct AfshDnsEventSigTag

{

  Int32 commandRef;

  Int16 error;

  Char h_name[MAX_HOST_LEN + 1];  //主机名              

  Char h_aliases[MAX_DNS_NAMES][MAX_HOST_LEN + 1]; //域名数组

  Int16     numAliases;

  IpAddress h_addr_list[MAX_DNS_ADDRS]; //ip地址数组

  Int16     numAddresses;  //ip 地址个数

} AfshDnsEventSig;

    1. 启动TCPIP任务

      进行socket编程之前先要启动tcpip协议栈任务afemh08StartTcpipTask(context_p);

有两个方式可以选择:CSD和GPRS方式。

      这里只介绍GPRS(与拨号GPRS有区别)上网方式:GPRS设置内容紧涉及三个参数:APN:CMNET,username:空,password:空。

      参数设置好后,启动协议栈任务:afemh08SendIpStartupReq(&ipStartupReq);往任务ID为TCPIP_TASK_ID的任务发送信号:SIG_TCPIP_IP_STARTUP_REQ。

收到SIG_TCPIP_IP_STARTUP_CNF后,在发送SIG_TCPIP_IF_CONFIG_REQ,收到SIG_TCPIP_IF_CONFIG_CNF的值为:IF_UP后,说明网络连接成功。

通过信号SIG_TCPIP_IF_STATUS_REQ,SIG_TCPIP_IF_STATUS_CNF可以得知GPRS登陆的情况。

    1. Socket编程
      1. socket通讯涉及的信号

      SIG_AFSH_DNS_EVENT_SIG:DNS解析信号,表明DNS解析成功。

      SIG_AFSH_SOCKET_EVENT_SIG:socket操作时产生此信号。

信号携带的数据结构为:

typedef struct AfshSocketEventSigTag

{

  Int32 socket;  //socket id

  Int16 eventTypeMask;  //事件类型

  Int16 eventError;  //错误事件值

} AfshSocketEventSig;

其中:eventTypeMask的值分别代表不同的情况:

SOCK_EVENT_READ     socket有数据可以读取

SOCK_EVENT_WRITE    socket写事件

SOCK_EVENT_CONNECT   socket接事件

SOCK_EVENT_CLOSE       socket关闭连接

SOCK_EVENT_ACCEPT      socket接收连接

SOCK_EVENT_DNS            

SOCK_EVENT_DESTROYED  销毁socket

SOCK_EVENT_OOB

      1. 创建socket

      函数:extern Int32 createsocket (Int16 af, //AF_INET

                                               SocketType type,  //TCP or UDP

                                               Int16 protocol,

                                  TaskId taskId,

      SocketEventCallbackProc callbackProc_p,

                                 Int16 *error_p);

      说明:当*error_p返回零时表示成功,函数返回值就是稍后用于通讯的socket

      例子:

      test_p->skt

    = createsocket(AF_INET,

                   SOCK_STREAM,

                   PF_UNSPEC,

                   TASK_FL_ID,

                   afshSocketEventCallbackProc,

                   &sktError);

      例子说明:函数指针afshSocketEventCallbackProc,当产生信号SIG_AFSH_SOCKET_EVENT_SIG时,回调此函数,对socket事件进行处理。

      1. 绑定socket

      函数:extern Int32 bind (Int32 socket,

                  const struct sockaddr *name_p,

                                         Int16 nameLen);

     说明:将socket绑定到本地端口。

      例子:

struct sockaddr_in sin;

memset(&sin, 0, sizeof(sin));

sin.sin_family      = AF_INET;

sin.sin_port        = 0; /* 本地任意端口 */

sin.sin_addr.s_addr = INADDR_ANY; 

sktStatus = bind( test_p->skt, (struct sockaddr *)&sin, sizeof(sin) );

      例子说明:如果函数返回SOCKET_ERROR表示失败。失败之后做如下处理:

    asyncSelect(test_p->skt, SOCK_EVENT_DESTROYED);

    closesocket(test_p->skt);

      1. 连接服务器

      函数:extern Int32 connect (Int32 socket, const struct sockaddr *name_p, Int16 nameLen);

      说明:连接TCP服务器

      例子:

struct sockaddr_in sin;

memset(&sin, 0, sizeof(sin));

sin.sin_family = AF_INET;

sin.sin_port = htons(6666); //连接端口"6666"

sin.sin_addr.s_addr=htonl(0xDE4C95B3);//服务器"222.76.149.179"

sktStatus = asyncSelect(test_p->skt, SOCK_EVENT_CONNECT);

sktStatus = connect( test_p->skt, (struct sockaddr *)&sin, sizeof(sin) );

      例子说明:本文中的socket编程采用的是异步通讯方式,因此函数调用,会立即返回。connect函数立即返回值为:SOCKET_ERROR。但并不表示连接失败。还需要获取socket错误号,来判断连接结果。

      函数:extern Int16 getsockerror (Int32 socket);

      说明:获取socket错误号

      例子:Boolean berror = getsockerror(test_p->skt)

      例子说明:如果berror ==SOCK_EOK表明连接成功。如果berror ==SOCK_EWOULDBLOCK,说明要等待SIG_AFSH_SOCKET_EVENT_SIG信号返回结构中的变量值来判断具体情况。其他错误代码见socketerr.h中说明。

      Int16 htons(Int16 x)函数将主机端口x转化为TCP/IP网络字节顺序,与之对应的为ntohs(Int16 x)函数

      Int32 htonl(Int32 x)<->Int32 ntohs(Int32 x)

      1. 收发数据包

      函数:extern Int32 send (Int32 socket, const void *buf_p, Int16 len, Int16 flags);

              extern Int32 recv (Int32 socket, void *buf_p, Int16 len, Int16 flags);

   说明:数据包发送与接收。

   例子:

char *buf_p = "this is r60 test";

asyncSelect(context_p->test.tcpTest.skt, SOCK_EVENT_READ | SOCK_EVENT_WRITE);

send (context_p->test.tcpTest.skt, buf_p, strlen(buf_p), 0);

      例子说明:SOCK_EVENT_READ表示发送完毕数据之后,通知读数据包,此时调用recv函数接收到来的数据包。SOCK_EVENT_WRITE表明已经发送数据包完毕。

      1. 断开连接

      函数:extern Int32 closesocket (Int32 socket);

      说明:关闭socket连接。

      例子:

    asyncSelect(test_p->skt, SOCK_EVENT_DESTROYED);

closesocket(test_p->skt);

例子说明:当SIG_AFSH_SOCKET_EVENT_SIG信号结构中变量对应的socket值为test_p->skt,且eventTypeMask为SOCK_EVENT_DESTROYED时表明关闭连接成功。

    1. 关闭tcp/ip协议栈

      在afshSocketEventCallbackProc 函数的SOCK_EVENT_DESTROYED情况下:

      发送信号:SIG_TCPIP_SHUTDOWN_REQ往TCPIP_TASK_ID,即可关闭tcp/ip协议栈。

    1. 平台tcpip相关文件说明

请参考:

socket.h,

afem_h08.c,

socketerr.h,

apptcpip.c,

apptcpip.h,

startuptcpip.c,

apptcpip_sig.h,

apptcp_fnc.c,

apptcp_fnc.h,

appudp_fnc.c,

appudp_fnc.h。

  1. GTR60 TCP/IP通讯存在的问题及注意事项
    1. 上位机串口使用AT指令完成TCP/IP数据传输问题
      1. 请求状态回应与应答包的顺序颠倒

      详细AT指令见6相关AT指令一览

      TCP/IP通讯过程中有两种数据:1、tcp/ip通讯的数据。2、模块对通讯状态的指示(包括TCP/IP通讯过成中的连接、发送、接收、断开等指示信息)

      通过监视串口观察:当客户端发送请求包完毕,服务器马上返回应答包,然后模块返回通讯状态指令(如“SEND OK”),或者是在服务器返回的应答包中,夹杂模块返回的状态指示(如“SEND OK”)。因此造成用户接收服务器应答包后,认为应答数据有误。

      正常的流程因为:客户端发送请求包后,模块应该先返回客户端请求包发送成功或者失败的状态指示(如“SEND OK”),然后再收到服务器的应答包。

      1. 收发数据大于1k时的问题

      1、tcp/ip通讯发送或接收大数据量包(1k),从服务器返回所有应答数据后,pc通过串口立即发送断开连接的AT指令( AT+CIPCLOSE),偶尔出现GTR60死机或重启的现象。

      2、服务器返回所有应答数据后,pc未做任何操作(未关闭通讯连接AT+CIPCLOSE和断开gprs网络AT+CIPSHUT),偶尔造成GTR60自动重启。

      因此使用此方式完成数据传输,收发数据量最好小于1kbyte。

    1. 注意事项
  1. 相关AT指令一览

AT指令表(SIM100-300

序号

AT指令

功能作用(“①”等序号表示执行的步骤

1

AT+CIPSTART=TCPSERVERIP地址SERVER的端口号

端给模块发送AT命令与SERVER建立TCP连接,连接成功后会返回CONNECT OK

2

 AT+CIPSEND

发送数据到SERVERAT+CIPSEND返回>后输入需要发送的数据,数据以0x1A(CTRL+Z)结束 .

3

AT+CIPCLOSE

③关闭当前TCP连接

4

AT+CIPSHUT

④关闭GPRS网络连接,本地IP地址会改变,下次建立连接的时间比AT+CIPCLOSE 方式会稍长一点

5

AT+CIPHEAD=1

接收数据为自动接收,若有远端数据则自动接收,在接收的数据前面自动加上标识。

6

 AT+CSTT=”APN”, ”USER        NAME”, ”USER PASSWORD”

①专用的APN方式来实现通讯

7

 AT+CIICR

8

 AT+CIFSR

③得到IP

9

 AT+CIPSTART=”TCP”,”REMOTE SERVER”,”PORT” (建立TCP连接)

④与服务器建立TCP连接。

10

AT+CDNSCFG=”211.136.18.171”

①先配置DNS

11

AT+CDNSORIP=1

②选择域名(1)还是IP地址

12

AT+CIPSTART=”TCP”,”www.263.net”, ”80”

③建立连接

13

AT+CIPSEND

④返回“>”后开始发送数据,ctrl+z启动发送

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

  1.    总结

      建议使用ppp协议实现TCP/IP通讯,通讯稳定可靠。而避免直接使用AT指令来进行TCP/IP

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值