下面是小编为大家整理的Android SQLite的ORM接口实现(一)findAll和find的实现(共含6篇),仅供大家参考借鉴,希望大家喜欢!同时,但愿您也能像本文投稿人“lottypiglululu”一样,积极向本站投稿分享好文章。
最近在看Android的ORM数据库框架LitePal,就想到可以利用原生的SQLite来实现和LitePal类似的ORM接口实现,
LitePal有一个接口是这样的:
List
指定什么类型,就能获取到该类型的数据集合。
这样是很方便,于是想着自己不看它们的实现,自己搞一个出来。
首先想到的就是利用反射和泛型。
利用反射有一个比较好的方式就是注解,读取注解就知道哪些属性是要被赋值的,但现在我还不想使用注解,那该怎么办呢?
我想到了利用反射来调用set方法完成赋值。
首先我们要知道什么字段需要赋值,反射是可以获取到字段,但可惜的是,它无法确定属性的名称和类型,原生的SQLite操作是要知道列名的。
反射是可以知道属性的名字的:
Field[] fields = clazz.getDeclaredFields;
for (Field field : fields) {
Log.e(“DatabaseStore”, field.getName());
}
Java的Class API有getFields和getDeclaredFields两个方法,前者是用来获取public字段的,后者是用来获取所有声明的字段的,显然必须使用后者,而且注意的是,因为获取到的字段是所有声明的字段,所以绝对有可能获取到不需要的字段。
但光知道属性的名字还是不够的,Android的SQLite需要知道自己要获取到的是什么类型:
cursor.getString(cursor.getColumnIndex(“name”));
幸运的是,是可以获取到的:
for (Field field : fields) {
Type type = field.getGenericType();
Log.e(“DatabaseStore”, type.toString());
}
但如何知道哪些属性是要被赋值的呢?
在代码约束上,我们是可以要求model的所有属性都是要被赋值的,没有道理一个model出现的属性竟然是不需要被赋值的,但实现上,我们还是假设有这样的可能。
这就需要获取到setter,只要有setter,就说明它是需要被赋值的:
List
for (Method method : allMethods) {
String name = method.getName();
if (name.contains(“set”) && !name.equals(“offset”)) {
setMethods.add(method);
continue;
}
}
这就要求我们所有的属性的setter前面都必须带有set关键字,这同样也是种代码约束。
既然同样都是代码约束,为什么不能直接就是要求属性必须都是要被赋值的呢?
很可惜的是,有可能这个model是需要被序列化的,而序列化有可能会有一个序列ID,序列ID是不需要被赋值的,但又是有可能存在于model中的。
比起这个,只要我们利用编辑器自动生成的setter,是一定会有set关键字的,所以,这种约束更加简单。
接着我们的操作就很简单了:判断Field的名称数组中的元素是否有对应的setter,如果有,就从Field的类型数组中取出该属性的类型,然后判断该类型属于哪种类型,就去表中取出对应的值。
Cursor cursor = Connector.getDatabase().query(clazz.getSimpleName(), null, null, null, null, null, null);//查询并获得游标
List
Constructor
while (cursor.moveToNext()) {
T data = null;
try {
data = (T) constructor
.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
for (Method method : setMethods) {
String name = method.getName();
String valueName = name.substring(3).substring(0, 1).toLowerCase() + name.substring(4);
String type = null;
int index = 0;
if (fieldNames.contains(valueName)) {
index = fieldNames.indexOf(valueName);
type = fields[index].getGenericType().toString();
}
Object value = new Object();
if (type != null) {
if (type.contains(“String”)) {
value = cursor.getString(cursor.getColumnIndex(valueName.toLowerCase()));
} else if (type.equals(“int”)) {
value = cursor.getInt(cursor.getColumnIndex(valueName.toLowerCase()));
} else if (type.equals(“double”)) {
value = cursor.getDouble(cursor.getColumnIndex(valueName.toLowerCase()));
} else if (type.equals(“float”)) {
value = cursor.getFloat(cursor.getColumnIndex(valueName.toLowerCase()));
} else if (type.equals(“boolean”)) {
value = cursor.getInt(cursor.getColumnIndex(valueName.toLowerCase())) == 1 ? true : false;
} else if (type.equals(“long”)) {
value = cursor.getLong(cursor.getColumnIndex(valueName.toLowerCase()));
} else if (type.equals(“short”)) {
value = cursor.getShort(cursor.getColumnIndex(valueName.toLowerCase()));
}
try {
fields[index].setAccessible(true);
fields[index].set(data, value);
} catch (IllegalAccessException e) {
Log.e(“data”, e.toString());
}
}
}
list.add(data);
}
cursor.close();
为了保证通用性,使用了泛型,但这里有个小小的问题需要解决,就是如何new一个T?
这不是开玩笑的,因为T是无法new的,所以还是需要通过反射来完成。
通过反射来获取构造器是必须的,但构造器有可能是有很多的,如何获取到最佳的构造器还是个问题。
什么是最佳构造器?
实际上,model的构造器基本上应该是无参构造器,但以防万一,我们还是需要通过一个比较:
protected Constructor
Constructor
Constructor
for (Constructor
if (finalConstructor == null) {
finalConstructor = constructor;
} else {
int finalParamLength = finalConstructor.getParameterTypes().length;
int newParamLength = constructor.getParameterTypes().length;
if (newParamLength < finalParamLength) {
finalConstructor = constructor;
}
}
}
finalConstructor.setAccessible(true);
return finalConstructor;
}
谁的参数最少,谁就是最佳构造器,0当然是最少的。
到了这里,我们基本上就实现了一个拥有和LitePal的API一样但内在实现却是原生方法的数据库接口方法了:
List
LitePal当然会提供条件查询的接口,也就是所谓的模糊查询。
模糊查询的基本结构如下:
SELECT 字段 FROM 表 WHERE 某字段 Like 条件
其中,条件有四种匹配模式。
1.%,表示任意0个或更多字符,可匹配任意类型和长度的字符,有些情况下若是中文,就得使用%%表示。
SELECT * FROM [user] WHERE u_name LIKE '%三%'
会把u_name中有“三”的记录找出来。
可以用and条件来增加更多的条件:
SELECT * FROM [user] WHERE u_name LIKE '%三%' AND u_name LIKE '%猫%'
这样能够找出u_name中的“三脚猫”的记录,但无法找到“张猫三”的记录,
2._,表示任意单个字符,匹配单个任意字符,用来限制表达式的字符长度语句:
SELECT * FROM [user] WHERE u_name LIKE '_三_'
这样只能找出“张三猫”这样中间是“三”的记录。
SELECT * FROM [user] WHERE u_name LIKE '三__';
这样是找到“三脚猫”这样“三”放在开头的三个单词的记录。
3.[],表示括号内所列字符中的一个,指定一个字符,字符串,或者范围,要求匹配对象为它们中的任一个。
SELECT * FROM [user] WHERE u_name LIKE '[张李王]三'
这样是找到“张三”,“李三”或者“王三”的记录。
如 [ ] 内有一系列字符(01234、abcde之类的),则可略写为“0-4“,“a-e”:
SELECT * FROM [user] WHERE u_name LIKE '老[1-9]'
这将找出”老1“,”老2“。。。等记录。
4.[^],表示不在括号所列之内的单个字符,其取值和[]相同,但它要求所匹配对象为指定字符以外的任一个字符。
SELECT * FROM [user] WHERE u_name LIKE '[^张李王]三'
这样找到的记录就是排除”张三“,”李三“或者”王三“的其他记录。
5.查询内容包含通配符。
如果我们查特殊字符,如”%“,“_“等,一般程序是需要用”/“括起来,但SQL中是用”[]“。
知道了这些基本的知识后,我们就可以开始看LitePal的接口是怎样的:
List
这样的接口比较简单,并且允许链式调用,形式上更加简洁。
要想实现这个,倒也不难,我们暂时就简单的用一个condition的字符串表示要查询的条件,然后提供一个where方法实现where查询的拼接,暂时就只是单个条件:
private String conditionStr;
public DatabaseStore where(String key, String value) {
conditionStr = ” where “ + key + ” like '%“ + value + ”%'“;
return store;
}
为了实现链式调用,返回DatabaseStore是必须的。
接下来就非常简单了,只要拼接完整的SQL语句,然后执行就可以了:
public
String sql = ”SELECT * FROM “ + clazz.getSimpleName().toLowerCase() + conditionStr;
Cursor cursor = Connector.getDatabase().rawQuery(sql, null);
Field[] fields = clazz.getDeclaredFields();
List
for (Field field : fields) {
fieldNames.add(field.getName());
}
List
List
cursor.close();
conditionStr = ”“;
return list;
}
复制代码
getSetMethods方法就是上面获取setter的代码的封装,而getList方法就是上面生成指定类型对象的List的代码的封装。
这样我们的接口方法的调用就是这样的:
List
无论是LitePal还是我们自己的实现,where都必须放在find前面。
这里倒有一个小贴士可以说说,就是获取数据库所有表名的操作。
由于底层我们还是使用LitePal来建表,而LitePal的建表非常简单,就是在assets文件夹下面放一个litepal.xml文件:
但表名具体到底是啥呢?
为了确认一下,我们可以查询数据库中所有的表的名字:
Cursor cursor = Connector.getDatabase().rawQuery(”select name from sqlite_master where type='table' order by name“, null);
while (cursor.moveToNext()) {
//遍历出表名
String name = cursor.getString(0);
Log.e(”DatabaseStore“, name);
}
每一个SQLite的数据库中都有一个sqlite_master的表,这个表的结构如下:
CREATE TABLE sqlite_master (
type TEXT,
name TEXT,
tbl_name TEXT,
rootpage INTEGER,
sql TEXT
);
对于表来说,type字段是”table“,name字段是表的名字,而索引,type就是”index“,name是索引的名字,tbl_name则是该索引所属的表的名字。
不管是表还是索引,sql字段是原先用CREATE TABLE或者CREATE INDEX语句创建它们时的命令文本,对于自动创建的索引,sql字段为NULL。
sqlite_master表示只读的,它的更新只能通过CREATE TABLE,CREATE INDEX,DROP TABLE或者DROP INDEX命令自动更新。
临时表不会出现在sqlite_master中,临时表及其索引和触发器是存放在另外一个叫sqlite_temp_master的表中,如果想要查询包括临时表在内的所有的表的列表,就需要这样写:
SELECT name FROM
(SELECT * FROM sqlite_master UNION ALL
SELECT * FROM sqlite_temp_master)
WHERE type=’table’
ORDER BY name
LitePal还可以对结果进行排序:
List
这个也是很简单就能实现的,类似where方法一样的处理:
public DatabaseStore order(String key) {
conditionStr += ” order by “ + key;
return store;
}
默认是升序。
API被人乱用的概率相当大,这时就需要有一些错误提示帮助用户定位问题了,最简单的例子就是在没有任何条件的情况下调用find方法,这时就应该提示没有任何条件:
if (conditionStr.equals(”“)) {
throw new Throwable(”There are not any conditions before find method invoked“);
}
还有一种情况并不算是被乱用,但按照上面的实现是会出错的:
statuses = DatabaseStore.getInstance().order(”updatetime“).where(”text“, ”我好“).find(Status.class);
绝对会报错,因为最后的SQL语句是这样的:select * from status order by updatetime where text like '%我好%'。
这是不对的,必须将where放在order by前面。
解决这个问题的方法就是提供两个字符串:
private String whereStr = ”“;
private String rderStr = ”“;
public DatabaseStore where(String key, String value) {
whereStr += ” where “ + key + ” like '%“ + value + ”%'“;
return store;
}
public DatabaseStore order(String key) {
orderStr += ” order by “ + key;
return store;
}
接着就是在find方法中进行判断:
if (whereStr.equals(”“) && orderStr.equals(”“)) {
throw new Throwable(”There are not any conditions before find method invoked“);
}
String sql = ”select * from “ + clazz.getSimpleName().toLowerCase() + (whereStr.equals(”“) ? ”“ : whereStr) + (orderStr.equals(”“) ? ”“ : orderStr);
暂时就简单实现了类似LitePal的ORM接口调用形式。
基于VRTX操作系统实现V5接口协议
摘要:V5接口是接入网(AN)和本地交换机(LE)之间标准化的开放接口。本文首先简要介绍V5接口协议结构,然后给出IDS综合数字通信系统中V5接口的实现方法;着重介绍第三层协议的软件实现方法,并对嵌入式系统编程进行一些探讨。关键词:接入网V5接口实时操作系统本地交换机
引言
接入网的概念是由传统的用户线发展起来的。近年来,由于电话业务的发展、新业务种类的增多、对线路带宽要求的提高以及各种新兴技术(特别是光纤通信技术)的发展,使得传统的用户线的概念逐渐发展成为接入网的概念。前几年,国际电信联盟(ITU)正式定义了用户接入网。引入接入网后,现有的电信网将减少端局数量,简化中继网的复杂程度,有利于中继网的管理。
接入网所覆盖的范围可以由三类接口来界定,如图1所示。接口的标准化、综合性直接影响到接入网的建设成本及接入网能承载的业务能力,为此,ITU-T综合考虑各种需要,终于通过了关于接入网和本地交换机之间标准化的V5.1和V5.2接口的建议[1,2]。
V5接口是一种在接入网中适用范围广、标准化程度高的新型开放的数字接口,对于设备的开发应用、多种业务的发展和网络的更新起着重要的作用。V5接口的标准化代表了重要的网络演进方向,影响深远。其意义在于交换机通过此接口可以支持多种类型的用户接入,而且V5接口的开放性意味着交换机和接入网的技术和业务演进完全独立开来。接入网的发展可以不受交换机的限制,使得接入网市场完全开放。
一、V5接口协议结构
窄带V5接口包括V5.1和V5.2接口。V5.1接口由1条2048Kb/s链路构成,通过时隙传递公共控制信号,支持模拟电话接入,基于64Kb/s的综合业务数字网基本接入(2B+D)和用于半永久连接的、不加带外信令信息的其他模拟接入和数字接入。这些接入类型都具有指配的承载通路分配,即用户端口与V5.1接口内承载通路有固定的对应关系。V5.2接口按需要可以由1~16个2048Kb/s链路构成,除支持V5.1接口的业务外,还支持ISDNPRI(30B+D)接入,其基于呼叫的时隙分配使得V5.2接口具有集中功能。V5.1接口是V5.2接口的子集,V5.1接口应当能够升级到V5.2接口。
V5接口协议由3层组成,接入网侧和本地交换机侧呈不对称布置,层与层之间的信息传递采用原语实现,而同层子层间的信息传递则采用映射。
V5接口物理层由1~16条2048Kb/s的链路构成,电气和物理特性符合G.703建议,帧结构符合G.704/G.706建议。每帧由32个时隙组成,其中:时隙TS0用作帧定位和CRC-4规程;时隙TS15、TS16和TS31可以用作通信通路(C通路),运载信令信息和控制信息,通过指配来分配;其余时隙可用作承载通路。
V5接口的数据链路层仅对于C通路而言。第二层协议(LAPV5)规范以建议Q.921中规定的LAPD协议和规程为基础,允许将不同的信息流灵活地复用到C通路上去。第二层协议分为两个子层:封装功能子层(LAPV5-EF)和数据链路子层(LAPV5-DL)。此外,第二层功能中还应包括帧中继功能(AN-FR)。
V5接口的第三层协议簇包括PSTN协议、控制协议、链路控制协议、BCC协议和保护协议(后三种协议为V5.2接口特有)。PSTN协议负责处理与PSTN业务有关的信令;控制协议负责用户端口状态指示与控制,协调两侧在网管控制下的数据指配;链路控制协议负责协调和控制AN、LE两侧的链路阻塞、链路解除阻塞和链路标识功能;BCC协议用来把一特定2048Kb/s链路上的承载通路基于呼叫分配给用户端口;保护协议提供V5.2接口在出现故障时通信路径切换的保护功能。
如图2所示,当第三层协议有信令信息需要发送时,通过数据链路子层(LAPV5-DL),请求封装功能子层(LAPV5-EF),用给定的封装功能地址传送数据链路子层端到端数据。
二、V5接口的实现
笔者曾参与IDS2000综合数字通信系统(接入网侧)V5.2接口的开放研制工作。IDS2000综合数字通信系统是电力自动化研究院为了满足电力通信网发展的需要而开发的一种综合接入设备。该设备具备V5接口,使得IDS2000系统可以很方便地通过开放接口和大型交换设备互连,取代了原先接入大型交换设备时所用的音频Z接口或专用接口。
V5接口协议分3层结构。物理层和数据链路层部分功能由硬件实现,这在超大规模集成电路飞速发展的今天,难度已不是太大。Mitel公司生产的大规模E1接口芯片MT9075就是一种很合适的芯片[3]。MT9075是一种单E1接口,综合了成帧器和LIU。重要特性包括数据链路接入、告警、中断、环回和诊断,并内嵌了两个HDLC控制器(MT8952),特别适用在V5接口(封装功能子层功能和HDLC协议类似)。
V5接口的第三层协议包含内容较多,由软件实现。本文主要结合嵌入式系统编程来介绍V5接口的第三层软件结构,对于协议细节的实现不多赘述。
1.实时多任务操作系统[4]
目前,嵌入式应用领域的一个发展倾向是采用实时多任务操作系统RTOS(RealTimeOperatingSystem)。RTOS的广泛使用与应用的复杂化有关。过去1个单片机应用程序所控制的外设和履行的任务不多,采取1个主循环和几个顺序调用的子程序模块即可满足要求;但现在1个嵌入式控制系统可能要同时
控制/监视很多外设,要求实时响应,有很多处理任务,各个任务之间有很多信息传递,如果仍采用原来的方法,存在两个问题:一是中断可能得不到及时响应,处理时间过长;二是系统任务多,要考虑的各种可能也多,各种资源若调度不当就会造成死锁,降低软件可靠性,程序编写任务量成指数增加。正是这种情况的出现,推动着RTOS的应用迅速发展。
对于V5接口,系统软件工作量相当大,包括5个核心协议功能的处理,须管理很多定时器,有很多用户端口需要监视,并且要求实时处理,若采用传统的软件编程方法,很难实现。在IDS2000系统中,笔者采用VRTX实时多任务操作系统[5]作为开发平台,大大减轻了软件的工作量,而采用面向对象的编程方法和事件驱动的消息机制,使得协议程序具有高可靠性、可控、可观测、易于维护和管理。
2.V5接口软件框图
如图3所示,V5接口软件主要包括:
(1)数据链路层模块(仅包括数据链路子层功能,封装功能子层由硬件板完成);
(2)PSTN协议处理模块;
(3)控制协议处理模块(包括用户端口状态机、指配控制状态机、公共控制状态机);
(4)链路控制协议处理模块(包括链路控制状态机、链路控制L3状态机);
(5)BCC协议处理模块;
(6)保护协议模块;
(7)V5接口AN侧系统管理模块;
(8)V5接口AN侧资源管理模块;
(9)定时器管理模块;
(10)消息处理模块;
(11)用户端口模块;
(12)10ms定时中断。
上述模块中,模块1~7完成V5接口协议的核心功能。其中,数据链路层模块对模块2~7所产生的消息进行处理,交由V5硬件接口板完成封装功能,再发送给LE侧实体;PSTN协议处理模块主要功能是建立用户端口状态和LE侧国内协议实体之间的联系;控制协议模块用于表示用户端口状态指示与控制,还和系统启动、重新启动、指配有关;V5接口AN侧系统管理模块是协议功能正常实现的重要部分,在AN和LE中,不同的FSM之间或第二层协议实体之间没有直接的通信,而是通过系统管理来协调V5接口各个协议实体之间的操作。另外,系统管理负责从AN或LE的各种功能模块中接收和处理有关状态和故障的信息。系统管理还是维护台或网管系统与V5接口之间的桥梁,它负责接收维护命令,并对V5接口执行相应的操作,随时向上层网管报告系统的运行状况。
模块8~12完成V5接口协议的辅助功能。其中用户端口模块负责用户端口状态扫描与控制,直接与硬件端口联系;AN侧资源管理模块配合BCC协议处理模块完成V5接口中BCC协议功能;消息处理模块是为了减轻操作系统的负担设立的,它负责协同操作系统管理各模块的消息队列;定时器管理模块负责产生、管理系统中所需要的定时器,当定时器溢出时,发送消息至相应模块;10ms定时中断程序负责调度系统中需周期性运行的任务,采用“信号量”(semaphores)的通信机制完成。
3.有限状态机
在上述程序模块中,存在大量的有限状态机(FSM),如控制协议中的用户端口FSM、公共控制FSM、BCC协议中的承载通路连接FSM等。有限状态机是描述通信协议过程的一般方式,是一种面向对象的`描述方法,与具体实现程序无关。有限状态机具有有限状态集,在任意给定的时刻,必有惟一确定的状态,在某状态下必须依赖于外部输入的特定消息触发,才能引起状态转移或执行某种任务。
有限状态机对于系统其他软件而言相当于一个“黑匣子”。它可以接收有限的消息组,也可以发送特定的消息组,但其内部结构不为系统其他部分所知。它的功能完全由它接收和发送消息顺序所决定。
有限状态机的这种描述方式很适合用面向对象的方法实现。在传统的实现方法中,状态变量和状态表可以在状态机模块外被访问,易于遭到破坏。面向对象的实现方法提供了一种更加结构化和更加直观的FSM实现方法,更利于“数据隐藏”,而且这些优点随着FSM规模的增大越发明显。我们只须要定义一个FSM基类,利用封装、继承和多态性的特点,就很容易从先前定义的基类中派生出所需要的FSM,大大减少了软件的工作量,软件也易于维护。
4.定时器的实现机制
定时器对于正确实现通信协议功能有着很重要的作用。V5接口协议中存在大量的定时器,当话务量较大时,可能有数百个定时器同时运行。定时器的设计是正确完成V5接口协议功能的一个重要部分。
本系统中由定时器管理进程负责管理V5接口中所有的定时器。由于各种定时器所要求的精度各不相同,所选择时间的长度对各定时器进行监视和计数累计是问题的关键:时间间隔太小,影响系统运行效率;时间间隔太长,影响定时器的精度。为了解决这个问题,系统中设定了三种不同分辨率的定时器:10ms、100ms和1s(分辨率指计数时间间隔)。
若某应用进程须要使用定时器,首先要向定时器管理进程发送一消息,消息中应包含申请定时器的分辨率、预置计数值、溢出后应发送的消息等信息。定时器管理进程收到该消息后,根据定时器的分辨率将其放到合适的定时队列中,启动定时。若定时器计数为零,则表示该定时器溢出,定时器管理进程应向相应的应用进程发送溢出消息,同时应释放该定时器。应用进程若主动要求放弃一定时器,则应向定时器管理进程发送一释放定时器消息。消息中应包含所申请定时器的标号、分辨率以及溢出目标进程、目标对象等。定时器管理进程接收到此消息后,在合适的定时器队列中寻找到该定时器后,将其释放。定时器管理进程管理其他各应用进程所申请的定时器。在定时器管理进程中,有三个链表分别用于存放这3种不同分辨率的定时器。
定时器管理进程完成以下一些基本功能:
(1)管理10ms、100ms和1s三种定时器链表。
(2)当10ms定时间隔到,应遍历10ms定时器链表,完成对链表中各定时器的计数处理。若有定时器溢出,则向相应的进程发送溢出消息,并从定时器链表中释放该定时器。同样,当100ms或1s的定时间隔到时,也应对100ms或1s定时器链表作相同处理。
(3)处理从其他进程接收到的消息。当收到从其他进程发送的申请定时器的消息时,应在相应分辨率的定时器链表中插入所申请的定时器;在接收到从其他进程来的释放定时器的消息后,应寻找到该定时器,并将其释放。
5.编写可重入函数
V5接口中有大量的函数需要编写。和传统的编程环境不同,本系统中,V5接口软件运行在多任务环境上,函数的可重入性(reentrancy)显得很重要。
所谓可重入函数是指一个可以被多个任务调用的过程,任务在调用时不必担心数据是否会出错。在编写函数时应尽量只使用局部变量,对于要使用的全局变量需要加以保护(如采用关中断、信号量等措施),这样构成的函数一定是可重入的;而编译器是否具有可重入的库,与它所服务的操作系统有关,如DOS下的BorlandC和MicrosoftC/C++等就没有可重入函数库,这是因为DOS是一个单用户、单任务的操作系统。
为了确保每一个调用函数的任务控制自己私有变量,在一个可重入的C函数中,将这样的变量申明为局部变量,C编译器将这样的变量存放在调用栈上或寄存器里。在VRTX操作系统下编写可重入的函数,须要遵循以下原则:
(1)将所有局部变量申明为auto或寄存器(register)类型;
(2)尽量不要使用static或extern变量,不可避免使用全局变量时,需加以保护;
(3)用VRTX库函数sc_gblock分配大的数据结构。
三、V5接口发展的趋势
从长远看,为了克服现有一系列SNI(业务节点接口)功能的限制,须要开发附加的SNI,特别是模块型和B-ISDN接入型SNI。目前,以ATM为基础的VB5接口的标准化工作正在进行。VB5参考点可分为VB5.1和VB5.2两种形式。
ITU-T已经通过了关于VB5参考点的建议,规定了VB5既支持AN与SN的直接连接,又支持两者的远程连接;既支持基于ATM的B-ISDN接入,又支持各种非ATM的接入。此外,VB5还应支持接入各种SN的接入功能,并能同时连接到多个不同的SN。有关VB5的技术框架还有待进一步研究和开发。
一个模块有两部分组成:接口和实现,接口指明模块要做什么,它声明了使用该模块的代码可用的标识符、类型和例程,实现指明模块是如何完成其接口声明的目标的,一个给定的模块通常只有一个接口,但是可能会有许多种实现能够提供接口所指定的功能。每个实现可能使用不同的算法和数据结构,但是它们都必须符合接口所给出的使用说明。客户调用程序是使用某个模块的一段代码,客户调用程序导入接口,而实现导出接口。由于多个客户调用程序是共享接口和实现的,因此使用实现的目标代码避免了不必要的代码重复,同时也有助于避免错误,因为接口和实现只需一次编写和调试就可多次使用。
本文地址:www.cnblogs.com/archimedes/p/c-interfaces-implementations.html,请注明源地址。
接口
接口只需要指明客户调用程序可能使用的标识符即可,应尽可能地隐藏一些无关的表示细节和算法,这样客户调用程序可以不必依赖于特定的实现细节。这种客户调用程序和实现之间的依赖--耦合----可能会在实现改变时引起错误,当这种依赖性埋藏在一些关于实现隐藏的或是不明确的假设中时,这些错误可能很难修复,因此一个设计良好且描述精确的接口应该尽量减少耦合。
C语言对接口和实现的分离只提供最基本的支持,但是简单的约定能给接口/实现方法论带来巨大的好处。在C中,接口在头文件声明,头文件声明了客户调用程序可以使用的宏、类型、数据结构、变量以及例程。用户使用C语言的预处理指令#include导入接口。
下面的例子说明了本篇文章的接口中所使用的一些约定、接口:
arith.h
该接口的名字为Arith,接口头文件也相应地命名为arith.h,接口的名字以前缀的形式出现在接口的每个标识符中。模块名不仅提供了合适的前缀,而且还有助于整理客户调用程序代码。
Arith接口还提供了一些标准C函数库中没有但是很有用的函数,并为出发和取模提供了良好的定义,而标准C中并没有给出这些操作的定义和只提供基于实现的定义。
实现
一个实现导出一个接口,它定义了必要的变量和函数以提供接口所规定的功能,在C语言中,一个实现是由一个或多个.c文件提供的,一个实现必须提供其导出的接口所指定的功能。实现应包含接口的.h文件,以保证它的定义和接口的声明时一致的。
Arith_min和Arith_max返回其整型参数中的最小值和最大值:
int Arith_max(int x, int y) {
return x > y ? x : y;
}
int Arith_min(int x, int y) {
return x > y ? y : x;
}
Arith_div返回y除以x得到的商,Arith_mod返回相应的余数。当x与y同号的时候,Arith_div(x,y)等价于x/y,Arith_mod(x,y)等价于x%y
当x与y的符号不同的时候,C的内嵌操作的返回值就取决于具体的实现:
eg.如果-13/5=2,-13%5=-3,如果-13/5=-3,-13%5=2
标准库函数总是向零取整,因此div(-13,2)=-2,Arith_div和Arith_mod的语义同样定义好了:它们总是趋近数轴的左侧取整,因此Arith_div(-13,5)=-3,Arith_div(x,y)是不超过实数z的最大整数,其中z满足z*y=x。
Arith_mod(x,y)被定义为x-y*Arith_div(x,y)。因此Arith_mod(-13,5)=-13-5*(-3)=2
函数Arith_ceiling和Arith_floor遵循类似的约定,Arith_ceiling(x,y)返回不小于实数商x/y的最小整数
Arith_floor(x,y)返回不超过实数商x/y的最大整数
完整实现代码如下:
arith.c
抽象数据类型
抽象数据类型(abstract data type,ADT)是一个定义了数据类型以及基于该类型值提供的各种操作的接口
一个高级类型是抽象的,因为接口隐藏了它的表示细节,以免客户调用程序依赖这些细节,
下面是一个抽象数据类型(ADT)的规范化例子--堆栈,它定义了该类型以及五种操作:
stack.h
实现
包含相关头文件:
#include
#include ”assert.h“
#include ”mem.h“
#include ”stack.h“
#define T Stack_T
Stack_T的内部是一个结构,该结构有个字段指向一个栈内指针的链表以及一个这些指针的计数:
struct T {
int count;
struct elem {
void *x;
struct elem *link;
} *head;
};
Stack_new分配并初始化一个新的T:
T Stack_new(void) {
T stk;
NEW(stk);
stk->count = 0;
stk->head = NULL;
return stk;
}
其中NEW是一个另一个接口中的一个分配宏指令。NEW(p)将分配该结构的一个实例,并将其指针赋给p,因此Stack_new中使用它就可以分配一个新的Stack_T
当count=0时,Stack_empty返回1,否则返回0:
int Stack_empty(T stk) {
assert(stk);
return stk->count == 0;
}
assert(stk)实现了可检查的运行期错误,它禁止空指针传给Stack中的任何函数。
Stack_push和Stack_pop从stk->head所指向的链表的头部添加或移出元素:
void Stack_push(T stk, void *x) {
struct elem *t;
assert(stk);
NEW(t);
t->x = x;
t->link = stk->head;
stk->head = t;
stk->count++;
}
void *Stack_pop(T stk) {
void *x;
struct elem *t;
assert(stk);
assert(stk->count > 0);
t = stk->head;
stk->head = t->link;
基于VRTX操作系统实现V5接口协议
摘要:V5接口是接入网(AN)和本地交换机(LE)之间标准化的开放接口。本文首先简要介绍V5接口协议结构,然后给出IDS综合数字通信系统中V5接口的实现方法;着重介绍第三层协议的软件实现方法,并对嵌入式系统编程进行一些探讨。关键词:接入网 V5接口 实时操作系统 本地交换机
引言
接入网的概念是由传统的用户线发展起来的。近年来,由于电话业务的发展、新业务种类的增多、对线路带宽要求的提高以及各种新兴技术(特别是光纤通信技术)的发展,使得传统的用户线的概念逐渐发展成为接入网的概念。前几年,国际电信联盟(ITU)正式定义了用户接入网。引入接入网后,现有的电信网将减少端局数量,简化中继网的复杂程度,有利于中继网的管理。
接入网所覆盖的范围可以由三类接口来界定,如图1所示。接口的标准化、综合性直接影响到接入网的建设成本及接入网能承载的业务能力,为此,ITU-T综合考虑各种需要,终于通过了关于接入网和本地交换机之间标准化的V5.1和V5.2接口的建议[1,2]。
V5接口是一种在接入网中适用范围广、标准化程度高的新型开放的数字接口,对于设备的开发应用、多种业务的发展和网络的更新起着重要的作用。V5接口的标准化代表了重要的'网络演进方向,影响深远。其意义在于交换机通过此接口可以支持多种类型的用户接入,而且V5接口的开放性意味着交换机和接入网的技术和业务演进完全独立开来。接入网的发展可以不受交换机的限制,使得接入网市场完全开放。
一、V5接口协议结构
窄带V5接口包括V5.1和V5.2接口。V5.1接口由1条2048Kb/s链路构成,通过时隙传递公共控制信号,支持模拟电话接入,基于64Kb/s的综合业务数字网基本接入(2B+D)和用于半永久连接的、不加带外信令信息的其他模拟接入和数字接入。这些接入类型都具有指配的承载通路分配,即用户端口与V5.1接口内承载通路有固定的对应关系。V5.2接口按需要可以由1~16个2048Kb/s链路构成,除支持V5.1接口的业务外,还支持ISDN PRI(30B+D)接入,其基于呼叫的时隙分配使得V5.2接口具有集中功能。V5.1接口是V5.2接口的子集,V5.1接口应当能够升级到V5.2接口。
V5接口协议由3层组成,接入网侧和本地交换机侧呈不对称布置,层与层之间的信息传递采用原语实现,而同层子层间的信息传递则采用映射。
V5接口物理层由1~16条2048Kb/s的链路构成,电气和物理特性符合G.703建议,帧结构符合G.704/G.706建议。每帧由32个时隙组成,其中:时隙TS0用作帧定位和CRC-4规程;时隙TS15、TS16和TS31可以用作通信通路(C通路),运载信令信息和控制信息,通过指配来分配;其余时隙可用作承载通路。
V5接口的数据链路层仅对于C通路而言。第二层协议(LAPV5)规范以建议Q.921中规定的LAPD协议和规程为基础,允许将不同的信息流灵活地复用到C通路上去。第二层协议分为两个子层:封装功能子层(LAPV5-EF)和数据链路子层(LAPV5-DL)。此外,第二层功能中还应包括帧中继功能(AN-FR)。
V5接口的第三层协议簇包括
[1] [2] [3] [4] [5]
C语言实现串行通信接口程序
摘 要 本文说明了异步串行通信(RS-232)的工作方式,探讨了查询和中断两种软件接口利弊,并给出两种方式的C语言源程序。的I/O通道之一,以最简单方式组成的串行双工线路只需两条信号线和一条公共地线,因此串行通信既有线路简单的优点同时也有它的缺点,即通信速率无法同并行通信相比,实际上EIA RS-232C在标准条件下的最大通信速率仅为20Kb/S。
尽管如此,大多数外设都提供了串行口接口,尤其在工业现场RS-232C的应用更为常见。IBM PC及兼容机系列都有RS-232的适配器,操作系统也提供了编程接口,系统接口分为DOS功能调用和BIOS功能调用两种:DOS INT 21H的03h和04h号功能调用为异步串行通信的接收和发送功能;而BIOS INT 14H有4组功能调用为串行通信服务,但DOS和BIOS功能调用都需握手信号,需数根信号线连接或彼此间互相短接,最为不便的是两者均为查询方式,不提供中断功能,难以实现高效率的通信程序,为此本文采用直接访问串行口硬件端口地址的方式,用C语言编写了串行通信查询和中断两种方式的接口程序。
1.串行口工作原理
微机串行通信采用EIA RS-232C标准,为单向不平衡传输方式,信号电平标准±12V,负逻辑,即逻辑1(MARKING)表示为信号电平-12V,逻辑0(SPACING)表示为信号电平+12V,最大传送距离15米,最大传送速率19.6K波特,其传送序列如图1,平时线路保持为1,传送数据开始时,先送起始位(0),然后传8(或7,6,5)个数据位(0,1),接着可传1位奇偶校验位,最后为1~2个停止位(1),由此可见,传送一个ASCII字符(7位),加上同步信号最少需9位数据位。
@@T8S12300.GIF;图1@@
串行通信的工作相当复杂,一般采用专用芯片来协调处理串行数据的发送接收,称为通用异步发送/接收器(UART),以节省CPU的时间,提高程序运行效率,IBM PC系列采用8250 UART来处理串行通信。
在BIOS数据区中的头8个字节为4个UART的端口首地址,但DOS只支持2个串行口:COM1(基地址0040:0000H)和COM2(基地址0040:0002H)。8250 UART共有10个可编程的单字节寄存器,占用7个端口地址,复用地址通过读/写操作和线路控制寄存器的第7位来区分。这10个寄存器的具体功能如下:
COM1(COM2) 寄存器
端口地址 功能 DLAB状态
3F8H(2F8H) 发送寄存器(写) 0
3F8H(2F8H) 接收寄存器(读) 0
3F8H(2F8H) 波特率因子低字节 1
3F9H(2F9H) 波特率因子高字节 1
3F9H(2F9H) 中断允许寄存器 0
3FAH(2FAH) 中断标志寄存器
3FBH(2FBH) 线路控制寄存器
3FCH(2FCH) MODEM控制寄存器
3FDH(2FDH) 线路状态寄存器
3FEH(2FEH) MODEM状态寄存器
注:DLAB为线路控制寄存器第七位在编写串行通信程序时,若采用低级方式,只需访问UART的.这10个寄存器即可,相对于直接控制通信的各个参量是方便可靠多了。其中MODEM控制/状态寄存器用于调制解调器的通信控制,一般情况下不太常用;中断状态/标志寄存器用于中断方式时的通信控制,需配合硬件中断控制器8259的编程;波特率因子高/低字节寄存器用于初始化串行口时通信速率的设定;线路控制/状态寄存器用于设置通信参数,反映当前状态;发送/接收寄存器通过读写操作来区分,不言而喻用于数据的发送和接收。
UART可向CPU发出一个硬件中断申请,此中断信号接到中断控制器8259,其中COM1接IRQ4(中断OCH),COM2接IRQ3(中断OBH)。用软件访问8259的中断允许寄存器(地址21H)来设置或屏蔽串行口的中断,需特别指出的是,设置中断方式串行通信时,MODEM控制寄存器的第三位必须置1,此时CPU才能响应UART中断允许寄存器许可的任何通信中断。
2.编程原理
程序1为查询通信方式接口程序,为一典型的数据采集例程。其中bioscom函数初始化COM1(此函数实际调用BIOS INT 14H中断0号功能)。这样在程序中就避免了具体设置波特率因子等繁琐工作,只需直接访问发送/接收寄存器(3F8H)和线路状态寄存器(3FDH)来控制UART的工作。线路状态寄存器的标志内容如下:
第0位 1=收到一字节数据
第1位 1=所收数据溢出
第2位 1=奇偶校验错
第3位 1=接收数据结构出错
第4位 1=断路检测
第5位 1=发送保存寄存器空
第6位 1=发送移位寄存器空
第7位 1=超时
当第0位为1时,标志UART已收到一完整字节,此时应及时将之读出,以免后续字符重叠,发生溢出错误,UART有发送保持寄存器和发送移位寄存器。发送数据时,程序将数据送入保持寄存器(当此寄存器为空时),UART自动等移位寄存器为空时将之写入,然后把数据转换成串行形式发送出去。
本程序先发送命令,然后循环检测,等待接收数据,当超过一定时间后视为数据串接收完毕。若接收到数据后返回0,否则返回1。
若以传送一个ASCII字符为例,用波特率9600 b/s,7个数据位,一个起始位,一个停止位来初始化UART,则计算机1秒可发送/接收的最大数据量仅为9600/9=1074字节,同计算机所具有的高速度是无法相比的,CPU的绝大部分时间耗费在循环检测标志位上。在一个有大量数据串行输入/输出的应用程序中,这种消耗是无法容忍的,也不是一种高效率通信方式,而且可以看到,在接收一个长度未知的数据串时,有可能发生遗漏。
程序2是一组中断方式通信接口程序。微机有两条用于串行通信的硬件中断通道IRQ3(COM2)和IRQ4(COM1),对应中断向量为OBH和OCH,可通过设置中断屏蔽寄存器(地址21H)来开放中断。置1时屏蔽该中断,否则开放中断。硬件中断例程必须在程序末尾往中断命令寄存器(地址20H)写入20H,即
MOV AL, 20H
OUT 20H, AL用以将当前中断服务寄存器清零,避免中断重复响应。
每路UART有4组中断,程序可通过中断允许寄存器(3F9H)来设置开放那路中断。这4组中断的位标志如下:
第0位 1=接收到数据
第1位 1=发送保持寄存器为空
第2位 1=接收数据出错
第3位 1=MODEM状态寄存器改变
第4~7位为0
在中断例程中检查UART的中断标志寄存器(3FAH),确定是哪一组事件申请中断。该寄存器第0位为0时表示有中断申请,响应该中断并采取相应措施后,UART自动复位中断标志;第2,1位标志中断类型,其位组合格式如下:代码 中断类型 复位措施11接收出错读线路状态寄存器10接收到数据读接收寄存器01发送寄存器空输出字符至发送寄存器00MODEM状态改变读MODEM状态寄存器这4组中断
的优先级为0号最低,3号最高。
在本组程序中,函数setinterrupt()和clearinterrupt()设置和恢复串行通信中断向量;cominit()初始化指定串行口并开放相应中断;sendcomdata()和getcomeomdata()用于发送和接收数据串;com1()和com2()为中断例程,二者均调用fax2()函数,fax2()函数为实际处理数据接收和发送的例程。明确了串行口的工作原理,就不难理解其具体程序。
3.结论
上述程序采用C语言编写,在BORLAND C++2.0集成环境中调试通过,为简单起见,只考虑了使用发送/接收两条信号线的情况,并未考虑使用握手信号线。
在实际应用中这两组程序尚有一些可修改之处。比如,中断接收程序中的缓冲区可改为循环表,以防数据溢出,尽可能保留最新数据。由于笔者水平所限,文中不足疏漏之处尚希行家指正。
程序1:
static int receive_delay=10000;
int may(unsigned par,char *comm,char *ss)
{int cs=0,j=0;
char *p;
bioscom(0,par,0); //com1
loop:p=comm;
inportb(0x3f8); //reset
do{ while((inportb(0x3f8+5)&0x20)==0); outportb(0x3f8,*p++);
}while(*p); //send command
os=0;j=0;
do{ if((inportb(0x3fd)&0x01)==0)
if(os〉receive_delay) break;
else { cs++;
continue; } ss[j++]=inportb(0x3f8); cs=0;
}while(l);
ss[j]=' ';
if(j) return 0;
else return 1;
程序2:
#include
#include
#include
#include
#inolude
#define maxsize 4096
#define SEND 2
#define RECEIVE 1
#define COM1 0
#define COM2 1
static unsigned char Hardinterrupt=0;
struct ComInterrupt
{int portadd;
int intbit;
char buf[maxsize],*comm;
int bufh,recount,sendcount;
}com[2]={{0x3f8,0x0c,”“,”“,0,0,0},
{0x2f8,0x0b,”“,”",0,0,0} };
void static interrupt (*old_com[2])(void);
vold interrupt coml(vold);
void interrupt com2(void);
void fax2(int comnum);
void setinterrupt(int comnum);
void clearinterrupt(int comnum);
void cominit(int comnum, int para, int interruptmark);
void sendcomdata (int comnum,char *command);
int getcomdata (int comnum, char *buf);
void interrupt com1(void)
{fax2(0);}
void interrupt com2(void)
{fax2(1);}
// set cominterrupt, comnum 0=com1, 1=com2
void setinterrupt (int comnum)
{
old_com[comnum]=getvect(com[comnum].intbit);
if (!oomnum)
setvect(com[comnum].intbit,coml); //com1
else
setvect(com[comnum].intbit,com2); //com2
//set hard int
Hardinterrupt = inportb(0x21);
if(comnum)
outportb(0x21,Hardinterrupt&0xf7); //com2 ,0
else
outportb(0x21,Hardinterrupt&0xef); //com1 0,
}
void clear interrupt(int comnum)
{
if(comnum)
outportb(0x21,Hardinterrupt | 0x08); //COM2
else
outportb(0x21,Hardinterrupt|0x10); //COM1
setvect(com[comnum].intbit,old_com[comnum]);
for( i=0;i