SMPP分析

本文详细介绍了使用JAVA实现SMPP协议解析的过程,包括客户端创建线程池连接服务端、发送bind_transmitter命令、服务端接收并响应bind_transmitter_resp包的步骤。通过抓包工具辅助理解数据传输细节,展示了SMPP协议在短消息服务中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

因业务需要,需要开发一个引擎(采用JAVA),来解析SMPP包然后经过一系列处理入REDIS队列。看到解包,想到之前用C/C++进行SOCKET编程时,事先约定好,头4个字节作为一个整形字段,代表整个字节组的长度,后面依次其他。解析时候先解析出头部得到字节总长度,和获取到的字节数组比对看是否相等,然后依次解析后面字段。我想解析SMPP包也类似,所以就网上搜索一下SMPP的资料,摘抄如下:

消息头语法

字段
长度(字节)
类型
Command Length
4
Integer
Command ID
4
Integer
Command_status
4
Integer
Sequence No.
4
Integer
Optional Message Body
可变
混合

具体字段描述说明:
 Command Length:整个包的长度(包括该字段本身)。。。看到这基本上明白,也是依次解析字段的。现在模拟一下,发包和解包的过程。

1 首先创建一个线程池来监听GlobalProperties.mClientPort端口。

private final ExecutorService execService = Executors.newSingleThreadExecutor();

SMPPServerSimulator smppServerSim = new SMPPServerSimulator(GlobalProperties.mClientPort);
execService.execute(smppServerSim);

1.1 SMPPServerSimulator 运行时会创建ServerSocket来监听GlobalProperties.mClientPort端口

public void run() {
        try {
            SMPPServerSessionListener sessionListener = new SMPPServerSessionListener(port);
            logger.info("Listening on port {}", port);
            while (true) {
                SMPPServerSession serverSession = sessionListener.accept(); //阻塞至到有客户端连接进来
                logger.info("Accepting connection for session {}", serverSession.getSessionId());
                serverSession.setMessageReceiverListener(this);
                serverSession.setResponseDeliveryListener(this);
                execService.execute(new WaitBindTask(serverSession));
            }
        } catch (IOException e) {
            logger.error("IO error occurred", e);
        }
    }

3 Client创建一个线程池来准备连接服务端

private final ExecutorService execService = Executors.newSingleThreadExecutor();

execService.execute(StressClient.getClient());

3.1 StressClient运行时会创建Socket来连接服务端

public void run() {
        try {
            smppSession.connectAndBind(host, port, BindType.BIND_TRX, systemId,
                    password, "cln", TypeOfNumber.UNKNOWN,
                    NumberingPlanIndicator.UNKNOWN, null);
            logger.info("Bound to {}:{}", host, port);
        } catch (IOException e) {
            logger.error("Failed initialize connection or bind", e);
            return;
        }
        new TrafficWatcherThread().start();

        logger.info("Starting to send {} bulk messages", bulkSize);
        for (int i = 0; i < bulkSize && !exit.get(); i++) {
            execService.execute(newSendTask("Hello " + id + " idx=" + i));
        }
        
        while (!exit.get()) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
        logger.info("Done");
        smppSession.unbindAndClose();
    }

通过抓包可以看到在客户机和服务器之间建立正常的TCP网络连接时,客户机首先发出一个SYN消息,服务器使用SYN+ACK应答表示接收到了这个消息,最后客户机再以ACK消息响应。


4 双方建立起了通信连接,可以正常发送信息了。

4.1 查看SMPP协议,bind_transmitter命令代表,短消息实体(ESME/SME)作为客户端与短消息中心(SMSC)建立连接,本连接建立起来后,允许短消息实体向短消息中心提交短消息。也就是说在扩展短消息实体和短消息中心之间建立虚连接,接收SMSC转发的短消息。那么首先按照bind_transmitter命令提供参数,发送SMPP包。

字段
长度(字节)
类型
System_id
最大 16
C_String
Password
最大 9
C_String
System_type
最大 13
C_String
Interface_version
1
Integer
Addr_ton
1
Integer
Addr_npi
1
Integer
Address_range
最大 41
C_DecString

5 回到  上面 3.1 StressClient运行时会创建Socket来连接服务端在run里面有个方法connectAndBind,看名字就明白了,连接和绑定(这里Bind指是发送bind_transmitter命令)

5.1 public String connectAndBind(String host, int port,
            BindParameter bindParam, long timeout)
            throws IOException {
        logger.debug("Connect and bind to {} port {}", host, port);
        if (sequence().currentValue() != 1) {
            throw new IOException("Failed connecting");
        }
        
        conn = connFactory.createConnection(host, port);
        logger.info("Connected to {}", conn.getInetAddress());
        
        conn.setSoTimeout(getEnquireLinkTimer());
        
        sessionContext.open();
        try {
            in = new DataInputStream(conn.getInputStream());
            out = conn.getOutputStream();
            
            pduReaderWorker = new PDUReaderWorker();
            pduReaderWorker.start();
            String smscSystemId = sendBind(bindParam.getBindType(), bindParam.getSystemId(), bindParam.getPassword(), bindParam.getSystemType(),
                    bindParam.getInterfaceVersion(), bindParam.getAddrTon(), bindParam.getAddrNpi(), bindParam.getAddressRange(), timeout);
            sessionContext.bound(bindParam.getBindType()); //参数和上面发送bind_transmitter命令提供参数类似。

            
            enquireLinkSender = new EnquireLinkSender();
            enquireLinkSender.start();
            return smscSystemId;
        } catch (PDUException e) {
            logger.error("Failed sending bind command", e);
            throw new IOException("Failed sending bind since some string parameter area invalid: " + e.getMessage(), e);
        } catch (NegativeResponseException e) {
            String message = "Receive negative bind response";
            logger.error(message, e);
            close();
            throw new IOException(message + ": " + e.getMessage(), e);
        } catch (InvalidResponseException e) {
            String message = "Receive invalid response of bind";
            logger.error(message, e);
            close();
            throw new IOException(message + ": " + e.getMessage(), e);
        } catch (ResponseTimeoutException e) {
            String message = "Waiting bind response take time too long";
            logger.error(message, e);
            close();
            throw new IOException(message + ": " + e.getMessage(), e);
        } catch (IOException e) {
            logger.error("IO error occurred", e);
            close();
            throw e;
        }
    }

5.2 通过抓包软件也可以看到发送了一个bind_transmitter命令的SMPP包,里面内容是3.1connectAndBind时传递过来的,如System type = "cln"

6 回到上面的1.1 里面的run方法,用另一个线程池来执行WaitBindTask(实现了Runnable的类),查看WaitBindTask的RUN方法

6.1 public void run() {
            try {
                BindRequest bindRequest = serverSession.waitForBind(1000); //阻塞,至到超时时间到
                logger.info("Accepting bind for session {}, interface version {}", serverSession.getSessionId(), bindRequest.getInterfaceVersion());
                try {
                    bindRequest.accept("sys", InterfaceVersion.IF_34);
                } catch (PDUStringException e) {
                    logger.error("Invalid system id", e);
                    bindRequest.reject(SMPPConstant.STAT_ESME_RSYSERR);
                }
            
            } catch (IllegalStateException e) {
                logger.error("System error", e);
            } catch (TimeoutException e) {
                logger.warn("Wait for bind has reach timeout", e);
            } catch (IOException e) {
                logger.error("Failed accepting bind request for session {}", serverSession.getSessionId());
            }
        }

6.2 跳转到waitForBind方法

public BindRequest waitForBind(long timeout) throws IllegalStateException,
            TimeoutException {
        SessionState currentSessionState = getSessionState();
        if (currentSessionState.equals(SessionState.OPEN)) {
            new PDUReaderWorker().start();
            try {
                return bindRequestReceiver.waitForRequest(timeout);
            } catch (IllegalStateException e) {
                throw new IllegalStateException("Invocation of waitForBind() has been made", e);
            } catch (TimeoutException e) {
                close();
                throw e;
            }
        } else {
            throw new IllegalStateException(
                    "waitForBind() should be invoked on OPEN state, actual state is "
                            + currentSessionState);
        }
    }

开启了一个线程来读取5.1connectAndBind方法里,sendBind的SMPP包。
查看PDUReaderWorker线程
@Override
        public void run() {
            logger.info("Starting PDUReaderWorker with processor degree:{} ...", getPduProcessorDegree());
            while (isReadPdu()) {
                readPDU();//读取SMPP包
            }
            close();
            executorService.shutdown();
            logger.info("PDUReaderWorker stop");

        }

读到完信息之后,服务端要返回一个bind_transmitter_resp SMPP包给客户端,回到6.1run方法里面调用了bindRequest.accept,查看这个方法

public void accept(String systemId, InterfaceVersion interfaceVersion) throws PDUStringException, IllegalStateException, IOException {
        StringValidator.validateString(systemId, StringParameter.SYSTEM_ID);
        lock.lock();
        try {
            if (!done) {
                done = true;
                try {
                    responseHandler.sendBindResp(systemId, interfaceVersion, bindType, originalSequenceNumber);//返回一个bind_transmitter_resp SMPP包给客户端
                } finally {
                    condition.signal();
                }
            } else {
                throw new IllegalStateException("Response already initiated");
            }
        } finally {
            lock.unlock();
        }
        done = true;
    }


通过抓包软件可以看到服务端返回给客户端的bind_transmitter_resp SMPP包,如System type = "sys"

打印的LOG如下 服务端

2016-08-16 11:25:46,805 [pool-1-thread-1] INFO  com.rongwu.SMPPServerSimulator - Listening on port 8056
2016-08-16 11:25:52,200 [pool-1-thread-1] INFO  com.rongwu.SMPPServerSimulator - Accepting connection for session 3d92afa2
2016-08-16 11:25:52,202 [Thread-2] INFO  com.rongwu.session.SMPPServerSession - Starting PDUReaderWorker with processor degree:3 ...
2016-08-16 11:25:52,203 [pool-4-thread-1] DEBUG com.rongwu.session.PDUProcessServerTask - Received SMPP message PDUHeader(31, 00000009, 00000000, 1) 00 00 00 1f 00 00 00 09 00 00 00 00 00 00 00 01 6a 00 6a 70 77 64 00 63 6c 6e 00 34 00 00 00
2016-08-16 11:25:52,208 [pool-2-thread-1] INFO  com.rongwu.SMPPServerSimulator - Accepting bind for session 3d92afa2, interface version IF_34
2016-08-16 11:25:52,208 [EnquireLinkSender: com.rongwu.session.SMPPServerSession@4e5a309d] INFO  com.rongwu.session.AbstractSession - Starting EnquireLinkSender for session 3d92afa2
2016-08-16 11:25:52,227 [pool-2-thread-1] DEBUG com.rongwu.DefaultPDUSender - Sending SMPP message 00 00 00 19 80 00 00 09 00 00 00 00 00 00 00 01 73 79 73 00 02 10 00 01 34
2016-08-16 11:25:52,252 [pool-4-thread-2] DEBUG com.rongwu.session.PDUProcessServerTask - Received SMPP message PDUHeader(58, 00000004, 00000000, 2) 00 00 00 3a 00 00 00 04 00 00 00 00 00 00 00 02 00 00 00 31 36 31 36 00 00 00 36 32 31 36 31 36 31 36 00 00 00 00 00 00 00 00 00 00 0d 48 65 6c 6c 6f 20 30 20 69 64 78 3d 30
2016-08-16 11:25:52,374 [pool-4-thread-2] DEBUG com.rongwu.SMPPServerSimulator - Receiving submit_sm 'Hello 0 idx=0', and return message id 7bdf3eb8
2016-08-16 11:25:52,375 [pool-4-thread-2] DEBUG com.rongwu.session.state.SMPPServerSessionBoundTX - Sending response with message_id 7bdf3eb8 for request with sequence_number 2
2016-08-16 11:25:52,375 [pool-4-thread-2] DEBUG com.rongwu.DefaultPDUSender - Sending SMPP message 00 00 00 19 80 00 00 04 00 00 00 00 00 00 00 02 37 62 64 66 33 65 62 38 00
2016-08-16 11:25:52,375 [pool-4-thread-2] DEBUG com.rongwu.SMPPServerSimulator - submit_sm_resp with message_id 7bdf3eb8 has been sent
2016-08-16 11:25:54,251 [pool-4-thread-3] DEBUG com.rongwu.session.PDUProcessServerTask - Received SMPP message PDUHeader(16, 00000006, 00000000, 3) 00 00 00 10 00 00 00 06 00 00 00 00 00 00 00 03
2016-08-16 11:25:54,252 [pool-4-thread-3] INFO  com.rongwu.session.state.AbstractGenericSMPPSessionBound - Receiving unbind request
2016-08-16 11:25:54,252 [pool-4-thread-3] DEBUG com.rongwu.DefaultPDUSender - Sending SMPP message 00 00 00 10 80 00 00 06 00 00 00 00 00 00 00 03
2016-08-16 11:25:54,257 [Thread-2] WARN  com.rongwu.session.SMPPServerSession - IOException while reading: null
2016-08-16 11:25:54,257 [Thread-2] DEBUG com.rongwu.session.AbstractSession - Close session 3d92afa2
2016-08-16 11:25:54,259 [Thread-2] INFO  com.rongwu.session.AbstractSession - Closing enquireLinkSender for session Thread[EnquireLinkSender: com.rongwu.session.SMPPServerSession@4e5a309d,5,main]
2016-08-16 11:25:54,710 [EnquireLinkSender: com.rongwu.session.SMPPServerSession@4e5a309d] DEBUG com.rongwu.session.AbstractSession - EnquireLinkSender stopped for session 3d92afa2
2016-08-16 11:25:54,710 [Thread-2] DEBUG com.rongwu.session.AbstractSession - Session 3d92afa2 is closed and enquireLinkSender stopped
2016-08-16 11:25:54,711 [Thread-2] DEBUG com.rongwu.session.AbstractSession - Close session 3d92afa2
2016-08-16 11:25:54,711 [Thread-2] INFO  com.rongwu.session.AbstractSession - Closing enquireLinkSender for session Thread[EnquireLinkSender: com.rongwu.session.SMPPServerSession@4e5a309d,5,]
2016-08-16 11:25:54,711 [Thread-2] DEBUG com.rongwu.session.AbstractSession - Session 3d92afa2 is closed and enquireLinkSender stopped
2016-08-16 11:25:54,712 [Thread-2] INFO  com.rongwu.session.SMPPServerSession - PDUReaderWorker stop


打印的LOG如下客户端

2016-08-15 11:38:20,280 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Target server 192.168.16.118:8056
2016-08-15 11:38:20,280 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - System ID: j
2016-08-15 11:38:20,281 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Password: jpwd
2016-08-15 11:38:20,281 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Source address: 1616
2016-08-15 11:38:20,281 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Destination address: 62161616
2016-08-15 11:38:20,281 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Transaction timer: 2000
2016-08-15 11:38:20,281 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Bulk size: 1
2016-08-15 11:38:20,282 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Max outstanding: 10
2016-08-15 11:38:20,282 [AWT-EventQueue-0] INFO  com.rongwu.StressClient - Processor degree: 3
2016-08-15 11:38:20,332 [pool-1-thread-1] DEBUG com.rongwu.session.SMPPSession - Connect and bind to 192.168.16.118 port 8056
2016-08-15 11:38:22,328 [pool-1-thread-1] INFO  com.rongwu.session.SMPPSession - Connected to /192.168.16.118
2016-08-15 11:38:22,343 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] INFO  com.rongwu.session.SMPPSession - Starting PDUReaderWorker
2016-08-15 11:38:22,357 [pool-1-thread-1] DEBUG com.rongwu.DefaultPDUSender - Sending SMPP message 00 00 00 1f 00 00 00 09 00 00 00 00 00 00 00 01 6a 00 6a 70 77 64 00 63 6c 6e 00 34 00 00 00
2016-08-15 11:38:22,929 [pool-3-thread-1] DEBUG com.rongwu.session.PDUProcessTask - Received SMPP message PDUHeader(25, 80000009, 00000000, 1) 73 79 73 00 02 10 00 01 34
2016-08-15 11:38:22,930 [pool-3-thread-1] DEBUG com.rongwu.session.state.SMPPSessionOpen - Bind Response header (25, 80000009, 00000000, 1)
2016-08-15 11:38:22,984 [pool-1-thread-1] DEBUG com.rongwu.session.AbstractSession - bind response received for session 34762440
2016-08-15 11:38:22,984 [pool-1-thread-1] INFO  com.rongwu.session.SMPPSession - Other side reports SMPP interface version 34
2016-08-15 11:38:22,984 [pool-1-thread-1] INFO  com.rongwu.session.SMPPSession - Changing processor degree to 3
2016-08-15 11:38:22,985 [pool-1-thread-1] INFO  com.rongwu.StressClient - Bound to 192.168.16.118:8056
2016-08-15 11:38:22,985 [EnquireLinkSender: com.rongwu.session.SMPPSession@f231023] INFO  com.rongwu.session.AbstractSession - Starting EnquireLinkSender for session 34762440
2016-08-15 11:38:22,986 [pool-1-thread-1] INFO  com.rongwu.StressClient - Starting to send 1 bulk messages
2016-08-15 11:38:22,986 [Thread-0] INFO  com.rongwu.StressClient - Starting traffic watcher...
2016-08-15 11:38:22,992 [pool-2-thread-1] DEBUG com.rongwu.DefaultPDUSender - Sending SMPP message 00 00 00 3a 00 00 00 04 00 00 00 00 00 00 00 02 00 00 00 31 36 31 36 00 00 00 36 32 31 36 31 36 31 36 00 00 00 00 00 00 00 00 00 00 0d 48 65 6c 6c 6f 20 30 20 69 64 78 3d 30
2016-08-15 11:38:23,974 [pool-3-thread-2] DEBUG com.rongwu.session.PDUProcessTask - Received SMPP message PDUHeader(25, 80000004, 00000000, 2) 33 33 38 64 34 39 37 38 00
2016-08-15 11:38:23,976 [pool-2-thread-1] DEBUG com.rongwu.session.AbstractSession - submit_sm response received for session 34762440
2016-08-15 11:38:23,987 [Thread-0] INFO  com.rongwu.StressClient - Request/Response per second: 1/1 of 1 maxDelay=990
2016-08-15 11:38:24,989 [pool-1-thread-1] INFO  com.rongwu.StressClient - Done
2016-08-15 11:38:24,990 [pool-1-thread-1] DEBUG com.rongwu.session.AbstractSession - Unbind and close sesssion 34762440
2016-08-15 11:38:24,992 [pool-1-thread-1] DEBUG com.rongwu.DefaultPDUSender - Sending SMPP message 00 00 00 10 00 00 00 06 00 00 00 00 00 00 00 03
2016-08-15 11:38:26,057 [pool-3-thread-3] DEBUG com.rongwu.session.PDUProcessTask - Received SMPP message PDUHeader(16, 80000006, 00000000, 3)
2016-08-15 11:38:26,058 [pool-1-thread-1] DEBUG com.rongwu.session.AbstractSession - unbind response received for session 34762440
2016-08-15 11:38:26,058 [pool-1-thread-1] DEBUG com.rongwu.session.AbstractSession - Close session 34762440
2016-08-15 11:38:26,059 [pool-1-thread-1] INFO  com.rongwu.session.AbstractSession - Closing enquireLinkSender for session Thread[EnquireLinkSender: com.rongwu.session.SMPPSession@f231023,5,main]
2016-08-15 11:38:26,060 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] WARN  com.rongwu.session.SMPPSession - IOException while reading: Socket closed
2016-08-15 11:38:26,061 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] DEBUG com.rongwu.session.AbstractSession - Close session 34762440
2016-08-15 11:38:26,062 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] INFO  com.rongwu.session.AbstractSession - Closing enquireLinkSender for session Thread[EnquireLinkSender: com.rongwu.session.SMPPSession@f231023,5,main]
2016-08-15 11:38:26,489 [EnquireLinkSender: com.rongwu.session.SMPPSession@f231023] DEBUG com.rongwu.session.AbstractSession - EnquireLinkSender stopped for session 34762440
2016-08-15 11:38:26,491 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] DEBUG com.rongwu.session.AbstractSession - Session 34762440 is closed and enquireLinkSender stopped
2016-08-15 11:38:26,491 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] DEBUG com.rongwu.session.AbstractSession - Close session 34762440
2016-08-15 11:38:26,491 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] INFO  com.rongwu.session.AbstractSession - Closing enquireLinkSender for session Thread[EnquireLinkSender: com.rongwu.session.SMPPSession@f231023,5,]
2016-08-15 11:38:26,492 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] DEBUG com.rongwu.session.AbstractSession - Session 34762440 is closed and enquireLinkSender stopped
2016-08-15 11:38:26,492 [PDUReaderWorker: com.rongwu.session.SMPPSession@f231023] INFO  com.rongwu.session.SMPPSession - PDUReaderWorker stop
2016-08-15 11:38:26,491 [pool-1-thread-1] DEBUG com.rongwu.session.AbstractSession - Session 34762440 is closed and enquireLinkSender stopped
2016-08-15 11:38:37,006 [Finalizer] DEBUG com.rongwu.session.AbstractSession - Close session 34762440
2016-08-15 11:38:37,010 [Finalizer] INFO  com.rongwu.session.AbstractSession - Closing enquireLinkSender for session Thread[EnquireLinkSender: com.rongwu.session.SMPPSession@f231023,5,]
2016-08-15 11:38:37,010 [Finalizer] DEBUG com.rongwu.session.AbstractSession - Session 34762440 is closed and enquireLinkSender stopped


测试程序下载


1. SMPP网关概述(联系dotphoenix@foxmail.com获取更多信息) 1.1 包含一个标准的SMPP Server(SMPP模拟器),一个SMPP Client 和 SDK 1.2 支持标准的SMPP V3.4,支持如下功能:多种bind模式,unbind,submit_sm,delivery_sm,enquire_link,generic_ack 1.3 SMPP Server可运行于Linux\Windows\OSX\Embeded Linux 1.4 SMPP Client可运行于Windows\Linux\OSX\Android\iOS\Embeded Linux 1.5 SDK支持Windows\Linux\OSX\Android\iOS\Embeded Linux,可被Java\C#\C++\MFC\PHP等调用,并提供基于json的HTTP API 1.6 支持MYSQL和SQLITE数据库 1.7 支持自定义计费,支持自定义业务系统 1.8 支持大吞吐量(使用数据库可支持500/秒,不使用数据库可支持8000条/秒) 1.9 兼容性良好,可以和几乎所有主流和非主流的网关及客户端正常工作 SMPP Gateway System(Contact dotphoenix@qq.com to get more information) 1 Including a standard SMPP Server(SMPP Simulator),a SMPP Client and SDK 2 Implement SMPP V3.4,Support PDUs including:3 bind modes,unbind,submit_sm,delivery_sm,enquire_link,generic_ack 3 SMPP Server supported platforms: Linux\Windows\OSX\Embeded Linux 4 SMPP Client supported platforms: Windows\Linux\OSX\Android\iOS\Embeded Linux 5 SDK supported platforms: Windows\Linux\OSX\Android\iOS\Embeded Linux,supported languages: Java\C#\C++\MFC\PHP,and also including HTTP API based on JSON. 6 Suppored s: MYSQL\SQLITE 7 Support customizing billing system and business system 8 Small delay, good reliability and large throughput (500 PDUs/s if need ,8000 PDUs/s if no need ) 9 Good compatibility, can co-work with nearly all the mainstream and non-mainstream gateways/servers/clients
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a3676212

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值