总的思路
1.使用socket连接服务器
2.将XmlPullParser的数据源关联到socket的InputStream
3.启动线程不断循环处理消息
4.将接收到的消息解析xml处理封装好成一个Packet包
5.将包广播给所有注册事件监听的类
逐步击破
(声明在看下面的文章时,最好先理解一下smack的使用,这样才能达到深入的理解)
(谨记:上图只显示本文章解释所要用到的类和方法,减缩了一些跟本文主题无关的代码,只留一条贯穿着从建立连接到接收消息的线。)
解析这块东西打算从最初的调用开始作为入口,抽丝剥茧,逐步揭开。
1.
PacketListener packetListener = <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>PacketListener() { <span style="margin: 0px; padding: 0px; color: rgb(100, 100, 100);">@Override </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public void </span>processPacket(Packet packet) { System.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">out </span>.println(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"Activity----processPacket" </span>+ packet.toXML()); } }; PacketFilter packetFilter = <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>PacketFilter() { <span style="margin: 0px; padding: 0px; color: rgb(100, 100, 100);">@Override </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public boolean </span>accept(Packet packet) { System.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">out</span>.println(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"Activity----accept" </span>+ packet.toXML()); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">return true</span>; } };
解释:创建包的监听以及包的过滤,当有消息到时就会广播到所有注册的监听,当然前提是要通过packetFilter的过滤。
2.
connection = new XMPPConnection();
XMPPConnection在这构造函数里面主要配置ip地址和端口(super(new ConnectionConfiguration("169.254.141.109", 9991));)
3.
connection.addPacketListener(packetListener, packetFilter);
connection.connect();
注册监听,开始初始化连接。
4.
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public void </span>connect() { <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// Stablishes the connection, readers and writers </span>connectUsingConfiguration(<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">config</span>); }
5.
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private void </span>connectUsingConfiguration(ConnectionConfiguration config) { String host = config.getHost(); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">int </span>port = config.getPort(); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">try </span>{ <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">socket </span>= <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>Socket(host, port); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(UnknownHostException e) { e.printStackTrace(); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(IOException e) { e.printStackTrace(); } initConnection(); }
通过之前设置的ip和端口,建立socket对象
6.
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">protected void </span>initDebugger() { Class<?> debuggerClass = <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">null</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">try </span>{ debuggerClass = Class.forName(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"com.simualteSmack.ConsoleDebugger"</span>); Constructor<?> constructor = debuggerClass.getConstructor( Connection.<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">class</span>, Writer.<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">class</span>, Reader.<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">class</span>); <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">debugger </span>= (SmackDebugger) constructor.newInstance(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>, <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">writer</span>, <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">reader</span>); <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">reader </span>= <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">debugger</span>.getReader(); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(ClassNotFoundException e1) { <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// </span><span style="margin: 0px; padding: 0px; color: rgb(127, 159, 191);">TODO </span><span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">Auto-generated catch block </span>e1.printStackTrace(); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(Exception e) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">throw new </span>IllegalArgumentException( <span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"Can't initialize the configured debugger!"</span>, e); } }
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private void </span>initReaderAndWriter() { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">try </span>{ reader = <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>BufferedReader(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>InputStreamReader(socket .getInputStream(), <span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"UTF-8"</span>)); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(UnsupportedEncodingException e) { <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// </span><span style="margin: 0px; padding: 0px; color: rgb(127, 159, 191);">TODO </span><span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">Auto-generated catch block </span>e.printStackTrace(); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(IOException e) { <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// </span><span style="margin: 0px; padding: 0px; color: rgb(127, 159, 191);">TODO </span><span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">Auto-generated catch block </span>e.printStackTrace(); } initDebugger(); }
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private void </span>initConnection() { <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// Set the reader and writer instance variables </span>initReaderAndWriter(); packetReader = <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>PacketReader(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>); addPacketListener(debugger.getReaderListener(), <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">null</span>); <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// Start the packet reader. The startup() method will block until we // get an opening stream packet back from server. </span>packetReader.startup(); }
从三个方法可以看出,建立reader和writer的对象关联到socket的InputStream,实例化ConsoleDebugger,该类主要是打印出接收到的消息,给reader设置了一个消息的监听。接着建立PacketReader对象,并启动。PacketReader主要负责消息的处理和通知
7.
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public class </span>PacketReader { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private </span>ExecutorService <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">listenerExecutor</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private boolean </span><span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">done</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private </span>XMPPConnection <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">connection</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private </span>XmlPullParser <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private </span>Thread <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">readerThread</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">protected </span>PacketReader(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">final </span>XMPPConnection connection) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">connection </span>= connection; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>.init(); } <span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">/** * Initializes the reader in order to be used. The reader is initialized * during the first connection and when reconnecting due to an abruptly * disconnection. */ </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">protected void </span>init() { <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">done </span>= <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">false</span>; <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">readerThread </span>= <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>Thread() { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public void </span>run() { parsePackets(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>); } }; <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">readerThread</span>.setName(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"Smack Packet Reader "</span>); <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">readerThread</span>.setDaemon(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">true</span>); <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// create an executor to deliver incoming packets to listeners. // we will use a single thread with an unbounded queue. </span><span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">listenerExecutor </span>= Executors .newSingleThreadExecutor(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>ThreadFactory() { <span style="margin: 0px; padding: 0px; color: rgb(100, 100, 100);">@Override </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public </span>Thread newThread(Runnable r) { Thread thread = <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>Thread(r, <span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"smack listener processor"</span>); thread.setDaemon(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">true</span>); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">return </span>thread; } }); resetParser(); } <span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">/** * Starts the packet reader thread and returns once a connection to the * server has been established. A connection will be attempted for a maximum * of five seconds. An XMPPException will be thrown if the connection fails. * */ </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public void </span>startup() { <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">readerThread</span>.start(); } <span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">/** * Shuts the packet reader down. */ </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public void </span>shutdown() { <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">done </span>= <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">true</span>; <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// Shut down the listener executor. </span><span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">listenerExecutor</span>.shutdown(); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private void </span>resetParser() { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">try </span>{ <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser </span>= XmlPullParserFactory.newInstance().newPullParser(); <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>.setFeature(XmlPullParser.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">FEATURE_PROCESS_NAMESPACES</span>, <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">true</span>); <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>.setInput(<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">connection</span>.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">reader</span>); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(XmlPullParserException xppe) { xppe.printStackTrace(); } } <span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">/** * Parse top</span><span style="margin: 0px; padding: 0px; color: rgb(127, 127, 159);">-</span><span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">level packets in order to process them further. * * </span><span style="margin: 0px; padding: 0px; color: rgb(127, 159, 191);">@param </span><span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">thread * the thread that is being used by the reader to parse incoming * packets. */ </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private void </span>parsePackets(Thread thread) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">try </span>{ <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">int </span>eventType = <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>.getEventType(); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">do </span>{ <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">if </span>(eventType == XmlPullParser.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">START_TAG</span>) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">if </span>(<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>.getName().equals(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"message"</span>)) { processPacket(PacketParserUtils.parseMessage(<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>)); } System.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">out</span>.println(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"START_TAG"</span>); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">else if </span>(eventType == XmlPullParser.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">END_TAG</span>) { System.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">out</span>.println(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"END_TAG"</span>); } eventType = <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">parser</span>.next(); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">while </span>(!<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">done </span>&& eventType != XmlPullParser.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">END_DOCUMENT </span>&& thread == <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">readerThread</span>); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(Exception e) { e.printStackTrace(); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">if </span>(!<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">done</span>) { } } } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private void </span>processPacket(Packet packet) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">if </span>(packet == <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">null</span>) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">return</span>; } <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// Loop through all collectors and notify the appropriate ones. </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">for </span>(PacketCollector collector : <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">connection</span>.getPacketCollectors()) { collector.processPacket(packet); } <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">// Deliver the incoming packet to listeners. </span><span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">listenerExecutor</span>.submit(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">new </span>ListenerNotification(packet)); } <span style="margin: 0px; padding: 0px; color: rgb(63, 95, 191);">/** * A runnable to notify all listeners of a packet. */ </span><span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private class </span>ListenerNotification <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">implements </span>Runnable { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">private </span>Packet <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">packet</span>; <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public </span>ListenerNotification(Packet packet) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">this</span>.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">packet </span>= packet; } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public void </span>run() { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">for </span>(ListenerWrapper listenerWrapper : <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">connection</span>.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">recvListeners </span>.values()) { listenerWrapper.notifyListener(<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">packet</span>); } } } }
创建该类时就初始化线程和ExecutorService ,接着调用resetParser() 方法为parser设置输入源(这里是重点,parser的数据都是通过这里获取),调用startup启动线程,循环监听parser,如果接收到消息根据消息协议的不同将调用PacketParserUtils类里的不同方法,这里调用parseMessage()该方法主要处理message的消息,在该方法里分析message消息并返回packet包。返回的包将调用processPacket方法,先通知所有注册了PacketCollector的监听,接着消息(listenerExecutor.submit(new ListenerNotification(packet)); )传递给所有注册了PacketListener的监听。这样在activity开始之前注册的那个监听事件就会触发,从而完成了整个流程。
7以上.
剩下的就是一些辅助包,很简单。比如PacketCollector 这个类,它的用处主要用来处理一些需要在发送后需要等待一个答复这样的请求。
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">protected synchronized void </span>processPacket(Packet packet) { System.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">out</span>.println(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"PacketCollector---processPacket"</span>); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">if </span>(packet == <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">null</span>) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">return</span>; } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">if </span>(<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">packetFilter </span>== <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">null </span>|| <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">packetFilter</span>.accept(packet)) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">while </span>(!<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">resultQueue</span>.offer(packet)) { <span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">resultQueue</span>.poll(); } } }
<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">public </span>Packet nextResult(<span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">long </span>timeout) { <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">long </span>endTime = System.currentTimeMillis() + timeout; System.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">out</span>.println(<span style="margin: 0px; padding: 0px; color: rgb(42, 0, 255);">"nextResult"</span>); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">do </span>{ <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">try </span>{ <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">return </span><span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">resultQueue</span>.poll(timeout, TimeUnit.<span style="margin: 0px; padding: 0px; color: rgb(0, 0, 192);">MILLISECONDS</span>); } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">catch </span>(InterruptedException e) { <span style="margin: 0px; padding: 0px; color: rgb(63, 127, 95);">/* ignore */ </span>} } <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">while </span>(System.currentTimeMillis() < endTime); <span style="margin: 0px; padding: 0px; color: rgb(127, 0, 85);">return null</span>; }
该方法就是将获取到的包,先过滤然后放到队列里,最后通过nextResult来获取包,这样就完成一个请求收一个答复。
这样整个流程就完成了,最后总结一下,如图(就这么简单^0^):
项目下载(只有客户端的,服务端的就是一个简单的socket接受,为了锻炼一下大家的编写代码的能力,服务器那个只能自己写咯^0^,其实是懒得上传了,代码很简单的)