mina 使用手记 4

原创   Apache Mina使用手记(四) 收藏

<script type="text/javascript"> document.body.oncopy = function() { if (window.clipboardData) { setTimeout(function() { var text = clipboardData.getData("text"); if (text && text.length>300) { text = text + "/r/n/n本文来自CSDN博客,转载请标明出处:" + location.href; clipboardData.setData("text", text); } }, 100); } } </script> <script class="blogstory">function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();}</script>

上一篇中,我们介绍了如何在mina中编写自己的日志过滤器,这一篇我们自己实现一个编解器。

实际应用当,很多应用系统应用的都不是标准的web service或XML等,比如象中国移动/联通/电信的短信网关程序,都有自己不同的协议实现,并且都是基于TCP/IP的字节流。Mina自带的编解 码器实现了TextLineEncoder和TextLineDecoder,可以进行按行的字符串处理,对于象短信网关程序,就要自己实现编解码过滤器 了。

我们定义一个简单的基于TCP/IP字节流的协议,实现在客户端和服务端之间的数据包传输。数据包MyProtocalPack有消息头和消息体组 成,消息头包括:length(消息包的总长度,数据类型int),flag(消息包标志位,数据类型byte),消息体content是一个字符串,实 际实现的时候按byte流处理。源代码如下:

  1. package  com.gftech.mytool.mina;  
  2. import  com.gftech.util.GFCommon;  
  3. public   class  MyProtocalPack {  
  4.     private   int  length;  
  5.     private   byte  flag;  
  6.     private  String content;  
  7.       
  8.     public  MyProtocalPack(){  
  9.           
  10.     }  
  11.       
  12.     public  MyProtocalPack( byte  flag,String content){  
  13.         this .flag=flag;  
  14.         this .content=content;  
  15.         int  len1=content== null ? 0 :content.getBytes().length;  
  16.         this .length= 5 +len1;  
  17.     }  
  18.       
  19.     public  MyProtocalPack( byte [] bs){  
  20.         if (bs!= null  && bs.length>= 5 ){  
  21.             length=GFCommon.bytes2int(GFCommon.bytesCopy(bs, 04 ));  
  22.             flag=bs[4 ];  
  23.             content=new  String(GFCommon.bytesCopy(bs,  5 , length- 5 ));  
  24.         }  
  25.     }  
  26.       
  27.     public   int  getLength() {  
  28.         return  length;  
  29.     }  
  30.     public   void  setLength( int  length) {  
  31.         this .length = length;  
  32.     }  
  33.     public   byte  getFlag() {  
  34.         return  flag;  
  35.     }  
  36.     public   void  setFlag( byte  flag) {  
  37.         this .flag = flag;  
  38.     }  
  39.     public  String getContent() {  
  40.         return  content;  
  41.     }  
  42.     public   void  setContent(String content) {  
  43.         this .content = content;  
  44.     }  
  45.       
  46.     public  String toString(){  
  47.         StringBuffer sb=new  StringBuffer();  
  48.         sb.append(" Len:" ).append(length);  
  49.         sb.append(" flag:" ).append(flag);  
  50.         sb.append(" content:" ).append(content);  
  51.         return  sb.toString();  
  52.     }  
  53. }  

回过头来,我们先看一下在MinaTimeServer中,如何使用一个文本的编解码过滤器,它是在过滤器链中添加了一个叫ProtocalCodecFilter的类,其中它调用 了一个工厂方法TextLineCodecFactory的工厂类,创建具休的TextLineEncoder和TextLineDecoder编码和解 码器。我们看一下具体的源代码:

  1. acceptor.getFilterChain().addLast( "codec"new  ProtocolCodecFilter( new  TextLineCodecFactory(Charset.forName( "GBK" ))));  

  1. package  org.apache.mina.filter.codec.textline;  
  2. import  java.nio.charset.Charset;  
  3. import  org.apache.mina.core.buffer.BufferDataException;  
  4. import  org.apache.mina.core.session.IoSession;  
  5. import  org.apache.mina.filter.codec.ProtocolCodecFactory;  
  6. import  org.apache.mina.filter.codec.ProtocolDecoder;  
  7. import  org.apache.mina.filter.codec.ProtocolEncoder;  
  8. /**  
  9.  * A {@link ProtocolCodecFactory} that performs encoding and decoding between  
  10.  * a text line data and a Java string object.  This codec is useful especially  
  11.  * when you work with a text-based protocols such as SMTP and IMAP.  
  12.  *  
  13.  * @author The Apache MINA Project (dev@mina.apache.org)  
  14.  * @version $Rev$, $Date$  
  15.  */   
  16. public   class  TextLineCodecFactory  implements  ProtocolCodecFactory {  
  17.     private   final  TextLineEncoder encoder;  
  18.     private   final  TextLineDecoder decoder;  
  19.     /**  
  20.      * Creates a new instance with the current default {@link Charset}.  
  21.      */   
  22.     public  TextLineCodecFactory() {  
  23.         this (Charset.defaultCharset());  
  24.     }  
  25.     /**  
  26.      * Creates a new instance with the specified {@link Charset}.  The  
  27.      * encoder uses a UNIX {@link LineDelimiter} and the decoder uses  
  28.      * the AUTO {@link LineDelimiter}.  
  29.      *  
  30.      * @param charset  
  31.      *  The charset to use in the encoding and decoding  
  32.      */   
  33.     public  TextLineCodecFactory(Charset charset) {  
  34.         encoder = new  TextLineEncoder(charset, LineDelimiter.UNIX);  
  35.         decoder = new  TextLineDecoder(charset, LineDelimiter.AUTO);  
  36.     }  
  37.     /**  
  38.      * Creates a new instance of TextLineCodecFactory.  This constructor  
  39.      * provides more flexibility for the developer.  
  40.      *  
  41.      * @param charset  
  42.      *  The charset to use in the encoding and decoding  
  43.      * @param encodingDelimiter  
  44.      *  The line delimeter for the encoder  
  45.      * @param decodingDelimiter  
  46.      *  The line delimeter for the decoder  
  47.      */   
  48.     public  TextLineCodecFactory(Charset charset,  
  49.             String encodingDelimiter, String decodingDelimiter) {  
  50.         encoder = new  TextLineEncoder(charset, encodingDelimiter);  
  51.         decoder = new  TextLineDecoder(charset, decodingDelimiter);  
  52.     }  
  53.     /**  
  54.      * Creates a new instance of TextLineCodecFactory.  This constructor  
  55.      * provides more flexibility for the developer.  
  56.      *  
  57.      * @param charset  
  58.      *  The charset to use in the encoding and decoding  
  59.      * @param encodingDelimiter  
  60.      *  The line delimeter for the encoder  
  61.      * @param decodingDelimiter  
  62.      *  The line delimeter for the decoder  
  63.      */   
  64.     public  TextLineCodecFactory(Charset charset,  
  65.             LineDelimiter encodingDelimiter, LineDelimiter decodingDelimiter) {  
  66.         encoder = new  TextLineEncoder(charset, encodingDelimiter);  
  67.         decoder = new  TextLineDecoder(charset, decodingDelimiter);  
  68.     }  
  69.     public  ProtocolEncoder getEncoder(IoSession session) {  
  70.         return  encoder;  
  71.     }  
  72.     public  ProtocolDecoder getDecoder(IoSession session) {  
  73.         return  decoder;  
  74.     }  
  75.        /**  
  76.      * Returns the allowed maximum size of the encoded line.  
  77.      * If the size of the encoded line exceeds this value, the encoder  
  78.      * will throw a {@link IllegalArgumentException}.  The default value  
  79.      * is {@link Integer#MAX_VALUE}.  
  80.      * <p>  
  81.      * This method does the same job with {@link TextLineEncoder#getMaxLineLength()}.  
  82.      */   
  83.     public   int  getEncoderMaxLineLength() {  
  84.         return  encoder.getMaxLineLength();  
  85.     }  
  86.     /**  
  87.      * Sets the allowed maximum size of the encoded line.  
  88.      * If the size of the encoded line exceeds this value, the encoder  
  89.      * will throw a {@link IllegalArgumentException}.  The default value  
  90.      * is {@link Integer#MAX_VALUE}.  
  91.      * <p>  
  92.      * This method does the same job with {@link TextLineEncoder#setMaxLineLength(int)}.  
  93.      */   
  94.     public   void  setEncoderMaxLineLength( int  maxLineLength) {  
  95.         encoder.setMaxLineLength(maxLineLength);  
  96.     }  
  97.     /**  
  98.      * Returns the allowed maximum size of the line to be decoded.  
  99.      * If the size of the line to be decoded exceeds this value, the  
  100.      * decoder will throw a {@link BufferDataException}.  The default  
  101.      * value is <tt>1024</tt> (1KB).  
  102.      * <p>  
  103.      * This method does the same job with {@link TextLineDecoder#getMaxLineLength()}.  
  104.      */   
  105.     public   int  getDecoderMaxLineLength() {  
  106.         return  decoder.getMaxLineLength();  
  107.     }  
  108.     /**  
  109.      * Sets the allowed maximum size of the line to be decoded.  
  110.      * If the size of the line to be decoded exceeds this value, the  
  111.      * decoder will throw a {@link BufferDataException}.  The default  
  112.      * value is <tt>1024</tt> (1KB).  
  113.      * <p>  
  114.      * This method does the same job with {@link TextLineDecoder#setMaxLineLength(int)}.  
  115.      */   
  116.     public   void  setDecoderMaxLineLength( int  maxLineLength) {  
  117.         decoder.setMaxLineLength(maxLineLength);  
  118.     }  
  119. }  

TextLineFactory实现了ProtocalCodecFactory接口,该接口主要有一个编码的方法getEncoder()和一个解码的方法getDecoder():

  1. package  org.apache.mina.filter.codec;  
  2. import  org.apache.mina.core.session.IoSession;  
  3. /**  
  4.  * Provides {@link ProtocolEncoder} and {@link ProtocolDecoder} which translates  
  5.  * binary or protocol specific data into message object and vice versa.  
  6.  * <p>  
  7.  * Please refer to  
  8.  * <a href="../../../../../xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html" mce_href="xref-examples/org/apache/mina/examples/reverser/ReverseProtocolProvider.html"><code>ReverserProtocolProvider</code></a>  
  9.  * example.  
  10.  *  
  11.  * @author The Apache MINA Project (dev@mina.apache.org)  
  12.  * @version $Rev$, $Date$  
  13.  */   
  14. public   interface  ProtocolCodecFactory {  
  15.     /**  
  16.      * Returns a new (or reusable) instance of {@link ProtocolEncoder} which  
  17.      * encodes message objects into binary or protocol-specific data.  
  18.      */   
  19.     ProtocolEncoder getEncoder(IoSession session) throws  Exception;  
  20.     /**  
  21.      * Returns a new (or reusable) instance of {@link ProtocolDecoder} which  
  22.      * decodes binary or protocol-specific data into message objects.  
  23.      */   
  24.     ProtocolDecoder getDecoder(IoSession session) throws  Exception;  
  25. }  

我们主要是仿照TextLineEncoder实现其中的encode()方法,仿照TextLineDecoder实现其中的decode()即 可,它们分别实现了ProtocalEncoder和ProtocalDecoder接口。我们要编写三个类分别 是:MyProtocalCodecFactory,MyProtocalEncoder,MyProtocalDecoder对应 TextLineCodecFactory,TextLineEncoder,TextLineDecoder。

  1. package  com.gftech.mytool.mina;  
  2. import  java.nio.charset.Charset;  
  3. import  org.apache.mina.core.session.IoSession;  
  4. import  org.apache.mina.filter.codec.ProtocolCodecFactory;  
  5. import  org.apache.mina.filter.codec.ProtocolDecoder;  
  6. import  org.apache.mina.filter.codec.ProtocolEncoder;  
  7. public   class  MyProtocalCodecFactory    implements  ProtocolCodecFactory {  
  8.         private   final  MyProtocalEncoder encoder;  
  9.         private   final  MyProtocalDecoder decoder;  
  10.           
  11.         public  MyProtocalCodecFactory(Charset charset) {  
  12.             encoder=new  MyProtocalEncoder(charset);  
  13.             decoder=new  MyProtocalDecoder(charset);  
  14.         }  
  15.            
  16.         public  ProtocolEncoder getEncoder(IoSession session) {  
  17.             return  encoder;  
  18.         }  
  19.         public  ProtocolDecoder getDecoder(IoSession session) {  
  20.             return  decoder;  
  21.         }  
  22.           
  23. }  

  1. package  com.gftech.mytool.mina;  
  2. import  java.nio.charset.Charset;  
  3. import  org.apache.mina.core.buffer.IoBuffer;  
  4. import  org.apache.mina.core.session.IoSession;  
  5. import  org.apache.mina.filter.codec.ProtocolEncoderAdapter;  
  6. import  org.apache.mina.filter.codec.ProtocolEncoderOutput;  
  7. public   class  MyProtocalEncoder  extends  ProtocolEncoderAdapter {  
  8.     private   final  Charset charset;  
  9.     public  MyProtocalEncoder(Charset charset) {  
  10.         this .charset = charset;  
  11.     }  
  12.     //在此处实现对MyProtocalPack包的编码工作,并把它写入输出流中   
  13.     public   void  encode(IoSession session, Object message, ProtocolEncoderOutput out)  throws  Exception {  
  14.         MyProtocalPack value = (MyProtocalPack) message;  
  15.         IoBuffer buf = IoBuffer.allocate(value.getLength());  
  16.         buf.setAutoExpand(true );  
  17.         buf.putInt(value.getLength());  
  18.         buf.put(value.getFlag());  
  19.         if  (value.getContent() !=  null )  
  20.             buf.put(value.getContent().getBytes());  
  21.         buf.flip();  
  22.         out.write(buf);  
  23.     }  
  24.     public   void  dispose()  throws  Exception {  
  25.     }  
  26. }  

  1. package  com.gftech.mytool.mina;  
  2. import  java.nio.charset.Charset;  
  3. import  java.nio.charset.CharsetDecoder;  
  4. import  org.apache.mina.core.buffer.IoBuffer;  
  5. import  org.apache.mina.core.session.AttributeKey;  
  6. import  org.apache.mina.core.session.IoSession;  
  7. import  org.apache.mina.filter.codec.ProtocolDecoder;  
  8. import  org.apache.mina.filter.codec.ProtocolDecoderOutput;  
  9. public   class  MyProtocalDecoder  implements  ProtocolDecoder {  
  10.     private   final  AttributeKey CONTEXT =  new  AttributeKey(getClass(),  "context" );  
  11.     private   final  Charset charset;  
  12.     private   int  maxPackLength =  100 ;  
  13.     public  MyProtocalDecoder() {  
  14.         this (Charset.defaultCharset());  
  15.     }  
  16.     public  MyProtocalDecoder(Charset charset) {  
  17.         this .charset = charset;  
  18.     }  
  19.     public   int  getMaxLineLength() {  
  20.         return  maxPackLength;  
  21.     }  
  22.     public   void  setMaxLineLength( int  maxLineLength) {  
  23.         if  (maxLineLength <=  0 ) {  
  24.             throw   new  IllegalArgumentException( "maxLineLength: "  + maxLineLength);  
  25.         }  
  26.         this .maxPackLength = maxLineLength;  
  27.     }  
  28.     private  Context getContext(IoSession session) {  
  29.         Context ctx;  
  30.         ctx = (Context) session.getAttribute(CONTEXT);  
  31.         if  (ctx ==  null ) {  
  32.             ctx = new  Context();  
  33.             session.setAttribute(CONTEXT, ctx);   
  34.         }   
  35.         return  ctx;  
  36.     }  
  37.     public   void  decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)  throws  Exception {  
  38.         final   int  packHeadLength =  5 ;  
  39.         //先获取上次的处理上下文,其中可能有未处理完的数据   
  40.         Context ctx = getContext(session);  
  41.         // 先把当前buffer中的数据追加到Context的buffer当中    
  42.         ctx.append(in);   
  43.         //把position指向0位置,把limit指向原来的position位置   
  44.         IoBuffer buf = ctx.getBuffer();  
  45.         buf.flip();   
  46.         // 然后按数据包的协议进行读取   
  47.         while  (buf.remaining() >= packHeadLength) {  
  48.             buf.mark();  
  49.             // 读取消息头部分   
  50.             int  length = buf.getInt();  
  51.             byte  flag = buf.get();  
  52.             //检查读取的包头是否正常,不正常的话清空buffer   
  53.             if  (length< 0  ||length > maxPackLength) {  
  54.                 buf.clear();   
  55.                 break ;  
  56.             }   
  57.             //读取正常的消息包,并写入输出流中,以便IoHandler进行处理   
  58.             else   if  (length >= packHeadLength && length - packHeadLength <= buf.remaining()) {  
  59.                 int  oldLimit2 = buf.limit();  
  60.                 buf.limit(buf.position() + length - packHeadLength);  
  61.                 String content = buf.getString(ctx.getDecoder());  
  62.                 buf.limit(oldLimit2);  
  63.                 MyProtocalPack pack = new  MyProtocalPack(flag, content);  
  64.                 out.write(pack);  
  65.             } else  {  
  66.                 // 如果消息包不完整   
  67.                 // 将指针重新移动消息头的起始位置    
  68.                 buf.reset();   
  69.                 break ;  
  70.             }  
  71.         }  
  72.         if  (buf.hasRemaining()) {  
  73.             // 将数据移到buffer的最前面    
  74.                 IoBuffer temp = IoBuffer.allocate(maxPackLength).setAutoExpand(true );  
  75.                 temp.put(buf);  
  76.                 temp.flip();  
  77.                 buf.clear();  
  78.                 buf.put(temp);  
  79.                    
  80.         } else  { // 如果数据已经处理完毕,进行清空   
  81.             buf.clear();   
  82.         }  
  83.           
  84.           
  85.     }  
  86.     public   void  finishDecode(IoSession session, ProtocolDecoderOutput out)  throws  Exception {  
  87.     }  
  88.     public   void  dispose(IoSession session)  throws  Exception {   
  89.         Context ctx = (Context) session.getAttribute(CONTEXT);  
  90.         if  (ctx !=  null ) {  
  91.             session.removeAttribute(CONTEXT);  
  92.         }  
  93.     }  
  94.     //记录上下文,因为数据触发没有规模,很可能只收到数据包的一半   
  95.     //所以,需要上下文拼起来才能完整的处理   
  96.     private   class  Context {  
  97.         private   final  CharsetDecoder decoder;  
  98.         private  IoBuffer buf;  
  99.         private   int  matchCount =  0 ;  
  100.         private   int  overflowPosition =  0 ;  
  101.         private  Context() {  
  102.             decoder = charset.newDecoder();  
  103.             buf = IoBuffer.allocate(80 ).setAutoExpand( true );  
  104.         }  
  105.         public  CharsetDecoder getDecoder() {  
  106.             return  decoder;  
  107.         }  
  108.         public  IoBuffer getBuffer() {  
  109.             return  buf;  
  110.         }  
  111.         public   int  getOverflowPosition() {  
  112.             return  overflowPosition;  
  113.         }  
  114.         public   int  getMatchCount() {  
  115.             return  matchCount;  
  116.         }  
  117.         public   void  setMatchCount( int  matchCount) {  
  118.             this .matchCount = matchCount;  
  119.         }  
  120.         public   void  reset() {  
  121.             overflowPosition = 0 ;  
  122.             matchCount = 0 ;  
  123.             decoder.reset();  
  124.         }  
  125.         public   void  append(IoBuffer in) {   
  126.             getBuffer().put(in);  
  127.             
  128.         }  
  129.    
  130.     }  
  131. }  

在MyProtocalServer中,添加自己实现的Log4jFilter和编解码过滤器:

  1. package  com.gftech.mytool.mina;  
  2. import  java.io.IOException;  
  3. import  java.net.InetSocketAddress;  
  4. import  java.nio.charset.Charset;  
  5. import  org.apache.log4j.Logger;  
  6. import  org.apache.log4j.PropertyConfigurator;  
  7. import  org.apache.mina.core.service.IoAcceptor;  
  8. import  org.apache.mina.core.service.IoHandlerAdapter;  
  9. import  org.apache.mina.core.session.IdleStatus;  
  10. import  org.apache.mina.core.session.IoSession;  
  11. import  org.apache.mina.filter.codec.ProtocolCodecFilter;  
  12. import  org.apache.mina.transport.socket.nio.NioSocketAcceptor;  
  13. public   class  MyProtocalServer {  
  14.     private   static   final   int  PORT =  2500 ;  
  15.     static  Logger logger = Logger.getLogger(MyProtocalServer. class );  
  16.     public   static   void  main(String[] args)  throws  IOException {  
  17.         PropertyConfigurator.configure("conf//log4j.properties" );  
  18.         IoAcceptor acceptor = new  NioSocketAcceptor();  
  19.         Log4jFilter lf = new  Log4jFilter(logger);  
  20.         acceptor.getFilterChain().addLast("logger" , lf);  
  21.       
  22.         acceptor.getFilterChain().addLast("codec"new  ProtocolCodecFilter( new  MyProtocalCodecFactory(Charset.forName( "GBK" ))));  
  23.         acceptor.getSessionConfig().setReadBufferSize(1024 );  
  24.         acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10 );  
  25.         acceptor.setHandler(new  MyHandler());  
  26.         acceptor.bind(new  InetSocketAddress(PORT));  
  27.         System.out.println("start server ..." );  
  28.     }  
  29. }  
  30. class  MyHandler  extends  IoHandlerAdapter {  
  31.     static  Logger logger = Logger.getLogger(MyHandler. class );  
  32.     @Override   
  33.     public   void  exceptionCaught(IoSession session, Throwable cause)  throws  Exception {  
  34.         cause.printStackTrace();  
  35.     }  
  36.     @Override   
  37.     public   void  messageReceived(IoSession session, Object message)  throws  Exception {  
  38.         MyProtocalPack pack=(MyProtocalPack)message;  
  39.         logger.debug("Rec:"  + pack);  
  40.     }  
  41.     @Override   
  42.     public   void  sessionIdle(IoSession session, IdleStatus status)  throws  Exception {  
  43.         logger.debug("IDLE "  + session.getIdleCount(status));  
  44.     }  
  45. }  

编写一个客户端程序进行测试:

  1. package  com.gftech.mytool.mina;  
  2. import  java.io.DataOutputStream;  
  3. import  java.net.Socket;  
  4. public   class  MyProtocalClient {  
  5.       
  6.     public   static   void  main(String[] args) {  
  7.         try  {  
  8.             Socket socket = new  Socket( "127.0.0.1"2500 );  
  9.             DataOutputStream out =  new  DataOutputStream( socket.getOutputStream() ) ;  
  10.             for  ( int  i =  0 ; i <  1000 ; i++) {  
  11.                 MyProtocalPack pack=new  MyProtocalPack(( byte )i,i+ "测试MyProtocalaaaaaaaaaaaaaa" );  
  12.                 out.writeInt(pack.getLength());  
  13.                 out.write(pack.getFlag());  
  14.                 out.write(pack.getContent().getBytes());  
  15.                 out.flush();  
  16.                 System.out.println(i + " sended" );  
  17.             }  
  18.             Thread.sleep(1000  );  
  19.         } catch  (Exception e) {  
  20.             e.printStackTrace();  
  21.         }  
  22.     }  
  23. }  

也可以用IoConnector实现自己的客户端:

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯智能台灯

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值