APP下载

一种基于指令驱动模型的移动编程通讯接口的设计与实现

2020-09-02王乐琪

小型微型计算机系统 2020年9期
关键词:服务器端接收器指令

侯 杰,王 静,2,王乐琪

1(上海海洋大学 信息学院,上海 201306)2(农村农业部渔业信息重点实验室,上海 201306)

E-mail:wangjing@shou.edu.cn

1 引 言

随着智能手机、平板电脑等移动设备的普及,各类移动应用的发展愈加繁荣[1].与此同时,移动应用的体积越来越庞大,通过网络接口获取数据是移动应用展示内容的主要来源[2,3],因此移动应用中的网络通讯模块也变得越来越重要.由于移动应用与网络接口的交互在移动应用开发中具有重要地位,并且在移动应用程序中,网络接口请求的代码块非常多而且所在位置比较离散,所以移动应用和网络接口的交互模块在编写和调试上具有较高的复杂度,影响着移动应用的开发效率.

目前存在很多成熟的框架可用于实现移动应用与网络接口交互的开发,例如在安卓开发中可以采用Retrofit请求框架,后台则常采用Struts2,SpringMVC等框架[4,5].这些框架对于移动开发中的网络通讯模块已经给出了可行的解决方案.在后台服务器编写接口,移动应用程序中编写请求.对于每一个移动端的网络接口调用,在服务器端对应已编写好的url,实现对应请求参数的处理代码模块.这种明确的分工使得移动应用开发中的接口调试与协同开发变得困难和复杂.在没有开发网络接口之前,移动端开发时的UI显示只能使用假数据填充,之后再改为网络接口请求来的数据,并调试接口数据的适配问题,其中开发出错率,以及开发复杂度都显著提高.问题在于服务器端和移动端在开发模块实现隔离的同时对交互数据也进行了隔离,因而难以实现协同开发.因而,涉及网络请求的移动应用开发不能仅考虑移动端,如何让移动端和服务器端相辅相成,配合恰当,在简化移动端的同时可以让服务器端也方便的开发接口,实现协同开发,从而降低开发复杂度,让移动端和服务器端具有数据一致性的同时也具有模块隔离性,是目前移动应用中网络通讯模块设计与开发面临的主要问题.

本文采用面向对象思想对移动编程通讯接口设计,在服务器端引入指令驱动模型,通过JSON格式的请求指令实现请求数据和返回数据端到端的处理,使移动端与服务器端具备数据一致性,从而提高移动端与服务器端的协同开发效率,并且提高了应用程序的可扩展性和测试效率.进一步基于此架构,以Android 应用为例介绍具体开发过程和效果.

2 基于指令驱动模型的移动应用通讯接口设计

2.1 通用开发模式

移动应用网络接口开发是典型的前后端分离式的开发,移动应用想要访问服务器资源,需要通过HTTP协议向指定的服务器传递信息,服务器根据应用请求调用相应的接口,并返回相应的结果.整个流程所涉及的操作为移动端定义请求接口方法,创造请求数据,填充URL和数据,向服务器发送请求,服务器调用指定URL的网络接口处理请求,返回请求结果,移动端根据请求结果刷新UI.移动应用网络接口的通用设计模式如图1所示.

图1 通用设计示意图Fig.1 General design diagram

在这种设计模式下,移动端与服务器端交互时传递的数据较为复杂,并且特定URL的请求数据和返回数据必须事先约定.然而,请求数据和URL之间却没有明确的联系,如果在发送请求时携带了错误的请求数据,移动端将不能接受到正常的返回结果.本模式中存在的另一个问题是接口对请求数据的逻辑处理耦合在接口之中,即使是使用MVC架构将逻辑处理模块独立起来,逻辑处理模块所处理的数据仍然需要通过上层数据接收层传递给它,并没有把网络请求的发送与接收完全屏蔽,无法真正实现移动端像调用函数一样调用接口,无法达到移动端与服务器端数据的一致性.因而,本文提出基于指令驱动模型对移动应用通讯接口的整个架构进行优化,确保请求数据与URL之间具有明确的联系,而且能够实现数据传输与数据处理的隔离.

2.2 框架整体设计

本文提出的编程架构共分为四个模块,构造请求指令,指令发送器,指令接收器,指令驱动模型,其中构造请求指令和指令驱动模型是一组,指令发送器和指令接收器是一组.整个流程如图2所示.

图2 整体架构设计Fig.2 Overall architecture design

图2中:

标注1,2,3:将需要发送给服务器的数据构造为指令

标注4,5,6:将服务器返回的数据用于刷新UI界面

标注7,8:客户端与服务器之间传递指令和返回的数据

标注9,10:将指令送给指令驱动模型处理,得到返回值

标注11,12:服务器端与数据库交互部分

移动应用要访问服务器,需要通过HTTP协议向服务器发送信息,由于JSON格式[6,7]在传输效率上优于其它数据传输格式,因此本文在设计接口架构时,使用JSON数据格式来传递信息.网络接口通过URL来访问,URL可以视为一个导向,移动端在请求URL时需要携带相应的请求数据,通常一个URL对应着固定的请求数据和返回数据.由于URL与传输数据具有这种强关联的关系,本文把URL视为操作码,将URL与传输数据抽象为一个整体,即指令,把指令视为网络请求传输的基本数据单位,指令包含了URL和传输数据所表达的信息.

在移动端构造请求指令后,通过指令发送器发送到服务器,服务器通过指令解析器解析出具体的指令,并把指令交给指令驱动模型处理,得到指令驱动模型的返回结果并返回给移动端.其中指令发送和指令解析是网络数据传输过程,它们的任务就是传递指令,而与具体的URL和请求数据无关,这样可以屏蔽掉网络传输过程,让程序的编写和调试更加方便,也方便将请求接口函数化.指令驱动模型是服务器端与网络传输无关的运行单元,在移动端构造的请求指令是可以直接用于对应指令驱动模型的,这就满足了数据一致性,让移动应用调用接口如同调用本地函数,这给程序的调试带来了极大的便利.在这种模式下,当需要修改功能或者是添加新功能时,只需要修改指令驱动模型就可以完成,极大的提高了程序的可扩展性.

2.3 指令驱动模型和构造请求指令的设计

在整个网络接口架构中,指令驱动模型和构造请求指令是一套完整的体系,独立在网络传输之外,如果部署到同一台设备仍然可以执行,构造的请求指令就是要投入指令驱动模型中来得到返回结果,本文采用面向对象的思想对其进行设计.

在大多数需要与数据库交互的程序中,基本上都是把数据库的每张表看作一个类,表里的每一行看为一个对象,正是由于这种面向对象的思想,在程序与数据库交互方面诞生了不少优秀的框架[8,9].而移动应用向服务器发送请求,最终也是对服务器数据库进行一定的操作,这些操作是面向数据库表的抽象数据类型的,因此移动应用的网络请求可以根据具体操作的抽象数据类型来划分,所以指令的操作码部分应该包含类和对类的操作,指令的数据部分可能有很多参数,为了更好的组织代码,提高代码质量,对于操作码中的每个操作,指令中都需要有对应所有参数的抽象数据类型.指令结构如图3所示.事实上,对一个类的操作并不会很多,而且可能只是增删改查,所以很多指令的操作数部分只需要类本身对象就足够了.如果严格按照每个操作对应的参数创建抽象参数对象,在移动端编程时填充指令对象就会方便很多,且不易出错,这在一定程度上可以提高开发效率,提高系统可扩展性.

图3 本文提出的指令格式Fig.3 Instruction format proposed in this paper

指令驱动模型是从面向对象的思想出发,它需要实现的功能是能够执行相应的指令,返回执行结果.所谓指令驱动,就是模型内部的执行过程是靠指令来驱动的.指令驱动模型是处理请求指令的核心,所以模型在使用时应该具有良好的可扩展性,在设计指令驱动模型时需要充分考虑到程序是否具有低耦合高内聚的特性.

本文提出的指令驱动模型具有处理请求指令的功能,而对请求指令的处理是通过指令的操作码实现对指令操作数的处理.由于设计指令时,指令能够通过指令要操作的类进行划分,指令驱动模型就可以根据这一特点,分析指令的操作码然后把指令交到具体类的操作对象,由具体的操作对象对指令的操作数处理并返回处理结果.所以用于处理操作数的对象也会由类来划分,这样就会保证程序的可扩展性.当然,具体类的操作对象需要注册到指令驱动模型中,以保证指令驱动模型在收到指令后可以正常运作.

因此,在驱动模型中需要一个具体操作对象的管理器来管理这些对象,在收到指令后可以分发给具体的操作对象去处理.由于指令是有格式的,具体的操作对象也具备一定的格式才能正常处理对应的指令.因此不是任何一个对象都可以通过指令驱动模型处理,也不是任何一个对象都可以注册到指令驱动模型中作为具体操作对象.因此,本驱动模型中还具备一个分析器,不仅可以实现注册操作对象时对操作对象的分析,而且能够完成模型执行指令时对指令的分析,从而保证模型的健壮性.另外,本模型具备异常处理模块,因开发时因为错误的使用模型而使程序不正常运行,能够报出异常,并可以提示异常原因,从而提高开发效率.

在使用指令驱动模型编程时,只需要编写不同类别的具体操作对象然后注册到模型中.当然,操作对象必须可以处理相应指令,而指令和操作对象的关系就如函数的形参与函数的关系.因而,在本文设计的框架下,指令处理后的返回结果是任意的.如果把操作对象类比于函数,这里的返回结果任意性不是说函数的返回值不需要定义,而是可以将返回值定义为任何类型,也即是对于同一个操作码,操作对象可以把返回值定义任何类型,在将结果返回时都可以正常接收返回数据.这是由于,如果调用者需要调用模型处理一条指令,在调用模型之前它是知道需要的返回结果类型的.操作对象返回结果的任意性可以提高工作效率,简化代码,减少不必要的前后台数据协商.

2.4 网络数据传输设计

在网络数据传输中,本文所设计的指令发送器和指令接收器只需要完成指令的前后台传递,具有传输JSON数据的功能.

图4 网络数据传输流程Fig.4 Transmission process of network data

在移动应用工程中,出于网络通讯安全性的考虑,往往需要对指令进行加密,而且操作数具有不同的特性,如图片资源和普通参数,因此需要对指令进行分类,此分类不同于将指令按操作码的类别划分,前者相当于指令的标签,后者是指令中操作码具有的性质.因此本研究在设计网络数据传输时,需要指令发送器可以区分指令的标签,根据指令的标签信息对指令进行相应的操作,在向服务器发送指令时,携带着指令的标签信息,构造扩展指令,方便服务器对指令的正确解析.依据扩展指令可以完成指令的发送和接收,然而在返回数据的传输方面,就会略显复杂,由于一条指令的返回结果具有任意性,这就给返回数据的接收带来了挑战,为了方便返回数据的接收和解析,可以对指令好返回数据统一化处理,都采用扩展指令的形式封装.事实上,指令和返回数据都是一种抽象数据类型,因此可以统一化处理,使用统一的扩展指令来传递,使网络数据传输过程仍然具备前后端分离的特性.扩展指令由附加信息和指令/返回数据构成.

本文设计网络数据传输的流程为指令发送器分析指令标签,生成扩展指令发送到指令接收器,指令接收器解析出指令并传递到指令驱动模型,指令接收器拿到对应指令返回数据后生成扩展指令返回给指令发送器,指令发送器解析出返回数据.统一化处理指令和返回数据,屏蔽了网络传输过程,提高了开发效率和程序的可扩展性,并且给程序的调试带来了便利.网络数据传输流程如图4所示.

3 基于指令驱动模型的移动应用通讯接口框架实现

Java是一个广泛使用的面向对象的网络编程语言,其具有良好的可移植性,跨平台性,安全性,被广泛应用在各种场景的程序开发[10],本文使用Java编程语言面向Android应用实现基于指令驱动模型的移动应用通讯接口架构.

3.1 指令驱动模型的实现

指令驱动模型是本文设计架构的基础功能模块,因此在实现架构前需要先实现指令驱动模型.指令驱动模型模块需要实现的是指令的实现和驱动模型的实现.指令包含操作码和操作数,操作码可以根据要操作的实体类进行划分,故把操作码设计为“实体类.具体操作”,对同一个实体类操作的指令使用同一个指令类.在用Java实现时,对于每一个实体类都编写一个对应的指令类并统一命名规范,指令类命名为“要操作实体类加后缀Come”,操作码字段使用request.操作对象要对指令对象操作,需要有对应指令对象的属性,把操作对象统一命名“实体类+Actor”.

指令驱动模型可以根据指令对象的request的实体类标识找到具体的操作对象,根据request的具体操作标识找到具体的操作方法,进而处理指令和返回结果.request字段与操作对象其中的方法存在映射关系,如图5所示.驱动模型根据映射关系正确的处理指令,可以使用Java编程语言的高级特性-注解[11,12]和反射编程.通过在Actor类中添加相应的注解说明Come和Actor之间的映射关系.

图5 Come指令和Actor的映射关系Fig.5 Mapping relationship between come instruction and actor

通过定义注解(@Come,@Actor,@Action)然后使用在Actor类中,就可以通过类似与user.register来定位到UserActor中的register方法.当然UserActor需要提前注册到驱动模型中.

定义指令驱动模型的控制器为Context类,实现为单例模式,Context类的方法应该有addActor():向Context注册Actor类,getComeClass():通过Come对象的类名获取类的字节码对象,这个方法主要是和利用JSON解析指令相关,showWorks():此方法用于显示出注册到Context中的Actor类的所有指令到处理方法的映射路线,back(Come),此方法用于对接收Come对象并产生返回结果.

分析器作用是分析Come对象中request字段是否符合规范,以及注册到Context的Actor类是否符合规范.如果不符合规范,交由异常处理模块进行相应处理.操作对象管理器负责实例化Actor对象,并将收到的Come指令填充其中的Come属性,根据Come指令的request字段调用@Action对应的方法,如果Actor对象中含有该方法返回值类型的引用,调用方法前会把返回值的引用实例化.

每个操作码对应这一种返回数据类型,但是开发人员在使用驱动模型时是不希望总是写强制类型转化的,因此在实现Context的back()方法时,通过利用Java的泛型编程消除强制类型转化步骤.

测试指令驱动模型:

public class TestCome {

private String request;

}

@Actor(name = “user”)

public class TestActor {

@Come

TestCome come;

TestBack testBack;

@Action(name = “test”)

TestBack getBack(){

testBack.setData(“Hello World!”);

return testBack;

}

@Action(name = “testList”)

List getList(){

List list=new ArrayList();

return list;

}

}

//注册TestActor

Context context=Context.getContext();

context.addActor(TestActor.class);

context.showWorks();

TestCome comeData=new TestCome();

//测试返回TestBack类型

comeData.setRequest(“user.test”);

TestBack backData=context.back(comeData);

//测试返回list类型

comeData.setRequest(“user.testList”);

List string1=context.back(comeData);

3.2 网络数据传输的实现

在实现指令和返回数据的传递方面,扩展指令的定义非常重要,扩展指令包含附加信息和指令/返回数据,附加信息很少,只需要定义几个字段即可,但是不同的指令和数据具有不同的数据类型,如果在扩展指令类中一一定义,会使扩展指令对象变的极为复杂,而且影响程序的可扩展性.所以本文实现的扩展指令对象中的指令或数据属于字符串类型,字段定义为data,用来存放JSON格式的指令或数据,这样就可以对指令和数据进行统一化实现.指令接收器接收到扩展指令后需要对指令反序列化,解析成Java对象,因此在扩展指令的附加信息中需要有指令类的类名,指令接收器就可以调用Context对象的getComeClass拿到指令的字节码,进而将其解析为Java对象.本文把扩展指令定义为Body类,字段包括name,data和key,其中name是指令或返回数据的类名,data是指令或返回数据的JSON格式字符串,key用于请求验证,扩展指令的传递同样需要JSON数据传输格式.

指令接收器用servlet实现,定义一个servlet接收JSON字符串形式的Body,定义一个数据处理类,用于JSON解析和数据加密.指令接收器只负责将扩展指令Body中的Come通过数据处理对象解析出来,交给指令驱动模型处理,拿到返回数据再通过数据处理对象包装成Body作为返回体.数据处理对象用来将数据对象化,进行token认证,数据加密和解密处理[13].这种模式下只需要创建一个servlet即可,具体的指令处理与指令接收隔离开,指令驱动模型与指令接收器是隔离的,可以极大的提高程序的易读性和可扩展性.

对于指令发送器,本文采用Retrofit封装网络请求模块,让指令发送器负责将指令封装为扩展指令Body,对其发送与接收,采用异步的请求处理方式,使用谷歌的Gson进行JSON数据解析.在具体请求代码块中构造请求指令,向指令发送器传递Come对象并接收返回对象,并且收到返回数据后可以通过doSucces和doFailure方法刷新UI界面.同样的,指令发送器也利用数据处理对象将指令转化为Body发送或者将接收到的Body中的返回数据转化为Java对象,同时需要根据Come指令的类别(比如需要加密)对指令进行相应的处理.

图6 整体架构的实现Fig.6 Implementation of overall architecture

指令发送器和指令接收器交互只用Body进行,因此指令发送器只需编写一个请求方法,这简化了网络请求编写的复杂度.数据处理对象与指令接收器中的数据处理对象类似,实现数据对象之间的解析转化,其中将返回Body中的放回数据解析成具体对象是一个难点,因为由JSON类型字符串解析成Java对象,需要有Java对象的字节码对象或者对象类型,如果接收一个带有泛型的Map或者List,运行时泛型就会被擦除,导致无法正确解析到想要的对象,针对此问题,本文通过Gson的TypeToken获取具体对象的Type,Type type = new TypeToken(){}.getType();再进行相应的解析,但是每个请求得到的返回类型有差异,为了减少编写复杂度,本文采用面向对象的继承特性,创建DoBack抽象类继承Gson的TypeToken,完美的解决了这个问题.

调用请求只需要构造Come指令,交给指令发送器,就可以获得返回对象,进而进行相应的处理,处理的方法也是在UI线程中的,可直接刷新界面.这样一来,就完成了Come指令到返回数据端到端的处理过程,提高程序的可读性和开发效率.

3.3 整体框架的实现

在定义好扩展指令Body类,指令发送器,指令接收器之后,就在应用与服务器之间生成了一条数据传输线,在编写请求时,只需要面向指令驱动模型进行编写.测试时在数据传输线没有错误的情况下,只需要对具体请求块和指令驱动模型进行测试,具体请求块的测试属于客户端独立的测试,指令驱动模型属于服务器端独立的测试,从而可以提高测试效率.整体架构的实现如图6所示.

3.4 框架实现具体代码

具体请求块:使用一个抽象类继承GSON的TypeToken,使之具有getType方法,用于获取真正的类型.

public abstract class DoBack extends TypeToken{

protected DoBack(Object object){

Api.getApi().go(object,this);

}

public abstract void doSuccess(T t);

public abstract void doFailure();}

指令发送器:使用Retrofit框架定义网络请求接口传输Body的JSON字符串:

@FormUrlEncoded

@POST(“ActionService”)

Call request(@Field(“data”)String body);

创建Api类定义Retrofit的HttpClient,JSON参数解析等基本配置,getApi()得到单例对象Api,go(object,this)是请求方法,object是Come对象,用DoBack本身作为参数,用于准确接收想要的返回类型,配备返回数据的处理方法.go方法定义如下:

public void go(final T t,final DoBack doBack){

String data= BCB.createBodyString(t);

Call call = getOwnApi().request(data);

call.enqueue(new Callback(){

@Override

public void onResponse(Call call,Response response){

if(response.body()!= null){

Object obj=BCB.ComeOrBack(response.body(),doBack.getType());

if(obj == null)

doBack.doFailure();

else

doBack.doSuccess(obj);

} else {

doBack.doFailure();

}

}

@Override

public void onFailure(Call call,Throwable t){

doBack.doFailure();

}

});}

BCB是定义的数据处理对象,可以看到传入doBack的doSuccess()方法的obj是解析好的返回数据对象,因此在具体请求块就可以直接利用返回数据对象进行相应的刷新页面.

指令接收器:

data=req.getParameter(“data”);

body=BCB.Body(data);

if(BCB.checkTime(body)==false){

onDefeat();

return;

}

String result=

BCB.createBodyString(

context.back(

BCB.ComeOrBack(body,context.getComeClass(body.getName()))));

if(result==null){

onDefeat();

return;

}

writer.write(result);

其中context是指令驱动模型的控制器,先调用getComeClass()得到传到指令接收器的Come指令对象的字节码文件,调用BCB解析出Come对象,调用context.back()得到返回数据对象,进而包装为Body的JSON字符串返回.

4 基于Android 应用的网络接口实现

样例APP实现user的注册登录,登录成功显示新闻列表,数据库连接使用Hibernate框架,user表字段uid,username,password.news表字段 nid,title,content.IDE使用AS和eclipse.

定义UserActor和NewsActor,注册到指令驱动模型.两个Actor面对的Come都是UserCome,UserCome中只有一个request字段和一个User对象.UserActor对应的指令是user.login和user.register,NewsActor对应的指令是news.getList.编写APP页面并加入具体请求块,用于注册的具体请求块如下:

User user=new User();

user.setUsername(username.getText().toString());

user.setPassword(password.getText().toString());

UserCome userCome=new UserCome();

userCome.setRequest(“user.register”);

userCome.setUser(user);

DoBackdoBack=new DoBack(userCome){

@Override

public void do Success(String s){

T;

}

@Override

public void doFailure(){

F;

}

};

获取News的具体请求块如下:

userCome.setRequest(“news.getList”);

DoBack> doBack1=new DoBack>(userCome){

@Override

public void doSuccess(List news){

T;

}

@Override

public void doFailure(){

F;

}

};

登陆模块与此类似.指令驱动模型编写在Actor中使用Hibernate进行数据库交互.例如user.register和news.getList如下所示:

@Actor(name = “user”)

public class UserActor {

@Come

UserCome userCome;

@Action(name = “register”)

String register(){

User user=

HibernateUtil.getobj(User.class,“username”,

userCome.getUser().getUsername());

if(user==null){

HibernateUtil.save(userCome.getUser());

return “注册成功”;

}else {

return “用户名已存在”;

}

}

}

@Actor(name = “news”)

public class NewsActor {

@Come

UserCome userCome;

@Action(name = “getList”)

List get(){

List list=

HibernateUtil.listByHql(“from News”,null);

return list;

}

}

捏造UserCome对登录、注册和获取新闻列表接口测试,测试结果如图7所示.

图7 部分测试结果示意图Fig.7 Illustration of some testing results

采用本文提出的框架进行开发,开发测试和扩展过程归约到对指令驱动模型开发测试和扩展,屏蔽了网络传输过程,提高开发效率.如果采用Retrofit(请求框架)+Struts2或SpringMVC(后台框架)搭建应用框架,则需要对每个请求接口编写请求方法,在请求时指向URL并携带参数,后台编写对应的Action类或Controller类来接受参数并进行处理.使用Struts2框架或者SpringMVC框架,前端后台是通过网络传输这一过程进行分割,通过URL传递参数进行沟通,这无疑会使开发过程脱离不了网络传输,在编写调试方面都会提高复杂度.表1给出了各框架在开发模式、编码、测试、维护与扩展进行理论上的比较,并总结了应用各框架的开发复杂度和各框架的作用.

表1 各个框架详细对比Table 1 Comparison with some common frameworks

在开发模式上,本文将URL和参数抽象为指令,结合指令发送器和接收器屏蔽了网络传输过程,使接口的请求仅依赖指令驱动模型,这给编码测试和扩展提供了便利.

5 结 论

本文提出了基于指令驱动模型对移动编程通信接口设计,把整个通信过程分解为构造请求指令,指令发送器,令接收器,指令驱动模型,进而产生了指令到网络传输到指令驱动模型的开发框架,通过请求指令化,在保证模块隔离性的同时也保证了前后台数据的一致性,指令驱动模型成为实际数据交互的载体,保证程序的扩展性,提高测试效率.这种架构可以屏蔽具体网络传输,降低数据耦合度,简化移动端和服务器端的代码编写,根据该架构开发基于Android系统的 APP应用并对比了各个框架的使用情况,验证了此架构的有效性和实用性.

在请求指令化后,指令就成为移动应用请求的基本数据单元,移动端的请求被抽象为一条条指令的发送.通常在移动应用中,一个界面可能具有很多可能要发送的指令,而且指令具有一定的逻辑关系.因此,可以在本文提出的编程架构基础上进一步研究指令的对应概念,如指令的顺序执行,分支结构和循环结构,以适应具体开发场景.

猜你喜欢

服务器端接收器指令
基于 Verilog HDL 的多周期 CPU 设计与实现
Linux环境下基于Socket的数据传输软件设计
浅谈一种新型的25Hz相敏轨道电路微电子接收器
《单一形状固定循环指令G90车外圆仿真》教案设计
关于ARM+FPGA组建PLC高速指令控制器的研究
奇奇小笨探秘海洋世界(六)
基于Qt的安全即时通讯软件服务器端设计
基于Qt的网络聊天软件服务器端设计
无线充电器
基于C/S架构的嵌入式监控组态外设扩展机制研究与应用