|
2008年11月12日
Linux
2.6内核的一个重要特色是提供了统一的内核设备模型。随着技术的不断进步,系统的拓扑结构越来越复杂,对智能电源管理、热插拔以及plug and
play的支持要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,2.6内核开发了全新的设备模型。
1. Sysfs文件系统
Sysfs文件系统是一个类似于proc文件系统的特殊文件系统,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。其顶层目录主要有:
Block目录:包含所有的块设备
Devices目录:包含系统所有的设备,并根据设备挂接的总线类型组织成层次结构
Bus目录:包含系统中所有的总线类型
Drivers目录:包括内核中所有已注册的设备驱动程序
Class目录:系统中的设备类型(如网卡设备,声卡设备等)
2. 内核对象机制关键数据结构
2.1 kobject内核对象
Kobject 是Linux 2.6引入的新的设备管理机制,在内核中由struct
kobject表示。通过这个数据结构使所有设备在底层都具有统一的接口,kobject提供基本的对象管理,是构成Linux
2.6设备模型的核心结构,它与sysfs文件系统紧密关联,每个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录。
Kobject结构定义为:
struct kobject {
char * k_name; // 指向设备名称的指针
char name[KOBJ_NAME_LEN]; // 设备名称
struct kref kref; // 对象引用计数
struct list_head entry; // 挂接到所在kset中去的单元
struct kobject * parent; // 指向父对象的指针
struct kset * kset; // 所属kset的指针
struct kobj_type * ktype; // 指向其对象类型描述符的指针
struct dentry * dentry; // sysfs文件系统中与该对象对应的文件节点路径指针
};
其中的kref域表示该对象引用的计数,内核通过kref实现对象引用计数管理,内核提供两个函数kobject_get()、kobject_put()分别用于增加和减少引用计数,当引用计数为0时,所有该对象使用的资源将被释放。
Ktype
域是一个指向kobj_type结构的指针,表示该对象的类型。Kobj_type数据结构包含三个域:一个release方法用于释放kobject占
用的资源;一个sysfs_ops指针指向sysfs操作表和一个sysfs文件系统缺省属性列表。Sysfs操作表包括两个函数store()和
show()。当用户态读取属性时,show()函数被调用,该函数编码指定属性值存入buffer中返回给用户态;而store()函数用于存储用户态
传入的属性值。
2.2 kset内核对象集合
Kobject通常通过kset组织成层次化的结构,kset是具有相同类型的kobject的集合,在内核中用kset数据结构表示,定义为:
struct kset {
struct subsystem * subsys; // 所在的subsystem的指针
struct kobj_type * ktype; // 指向该kset对象类型描述符的指针
struct list_head list; // 用于连接该kset中所有kobject的链表头
struct kobject kobj; // 嵌入的kobject
struct kset_hotplug_ops * hotplug_ops; // 指向热插拔操作表的指针
};
包 含在kset中的所有kobject被组织成一个双向循环链表,list域正是该链表的头。Ktype域指向一个kobj_type结构,被该
kset中的所有kobject共享,表示这些对象的类型。Kset数据结构还内嵌了一个kobject对象(由kobj域表示),所有属于这个kset
的kobject对象的parent域均指向这个内嵌的对象。此外,kset还依赖于kobj维护引用计数:kset的引用计数实际上就是内嵌的
kobject对象的引用计数。
2.3 subsystem内核对象子系统
Subsystem是一系列kset的集合,描述系统中某一
类设备子系统,如block_subsys表示所有的块设备,对应于sysfs文件系统中的block目录。类似的,devices_subsys对应于
sysfs中的devices目录,描述系统中所有的设备。Subsystem由struct subsystem数据结构描述,定义为:
struct subsystem {
struct kset kset; // 内嵌的kset对象
struct rw_semaphore rwsem; // 互斥访问信号量
};
每 个kset必须属于某个subsystem,通过设置kset结构中的subsys域指向指定的subsystem可以将一个kset加入到该
subsystem。所有挂接到同一subsystem的kset共享同一个rwsem信号量,用于同步访问kset中的链表。
3. 内核对象机制主要相关函数
针对内核对象不同层次的数据结构,linux 2.6内核定义了一系列操作函数,定义于lib/kobject.c文件中。
3.1 kobject相关函数
void kobject_init(struct kobject * kobj);// kobject初始化函数。设置kobject引用计数为1,entry域指向自身,其所属kset引用计数加1
int kobject_set_name(struct kobject *kobj, const char *format, );// 设置指定kobject的名称。
void kobject_cleanup(struct kobject * kobj);
void kobject_release(struct kref *kref);// kobject清除函数。当其引用计数为0时,释放对象占用的资源。
struct kobject *kobject_get(struct kobject *kobj);// 将kobj 对象的引用计数加1,同时返回该对象的指针。
void kobject_put(struct kobject * kobj);// 将kobj对象的引用计数减1,如果引用计数降为0,则调用kobject_release()释放该kobject对象。
int kobject_add(struct kobject * kobj);// 将kobj对象加入Linux设备层次。挂接该kobject对象到kset的list链中,增加父目录各级kobject的引用计数,在其parent指向的目录下创建文件节点,并启动该类型内核对象的hotplug函数。
int kobject_register(struct kobject * kobj);// kobject注册函数。通过调用kobject_init()初始化kobj,再调用kobject_add()完成该内核对象的注册。
void kobject_del(struct kobject * kobj);// 从Linux设备层次(hierarchy)中删除kobj对象。
void kobject_unregister(struct kobject * kobj);// kobject注销函数。与kobject_register()相反,它首先调用kobject_del从设备层次中删除该对象,再调用kobject_put()减少该对象的引用计数,如果引用计数降为0,则释放该kobject对象。
3.2 kset相关函数
与kobject
相似,kset_init()完成指定kset的初始化,kset_get()和kset_put()分别增加和减少kset对象的引用计数。
Kset_add()和kset_del()函数分别实现将指定keset对象加入设备层次和从其中删除;kset_register()函数完成
kset的注册而kset_unregister()函数则完成kset的注销。
3.3 subsystem相关函数
subsystem有一组完成类似的函数,分别是:
void subsystem_init(struct subsystem *subsys);
int subsystem_register(struct subsystem *subsys);
void subsystem_unregister(struct subsystem *subsys);
struct subsystem *subsys_get(struct subsystem *subsys)
void subsys_put(struct subsystem *subsys);
4. 设备模型组件
在上述内核对象机制的基础上,Linux的设备模型建立在几个关键组件的基础上,下面我们详细阐述这些组件。
4.1 devices
系统中的任一设备在设备模型中都由一个device对象描述,其对应的数据结构struct device定义为:
struct device {
struct list_head g_list;
struct list_head node;
struct list_head bus_list;
struct list_head driver_list;
struct list_head children;
struct device *parent;
struct kobject kobj;
char bus_id[BUS_ID_SIZE];
struct bus_type *bus;
struct device_driver *driver;
void *driver_data;
/* Several fields omitted */
};
g_list
将该device对象挂接到全局设备链表中,所有的device对象都包含在devices_subsys中,并组织成层次结构。Node域将该对象挂接
到其兄弟对象的链表中,而bus_list则用于将连接到相同总线上的设备组织成链表,driver_list则将同一驱动程序管理的所有设备组织为链
表。此外,children域指向该device对象子对象链表头,parent域则指向父对象。Device对象还内嵌一个kobject对象,用于引
用计数管理并通过它实现设备层次结构。Driver域指向管理该设备的驱动程序对象,而driver_data则是提供给驱动程序的数据。Bus域描述设
备所连接的总线类型。
内核提供了相应的函数用于操作device对象。其中Device_register()函数将一个新的device对象插
入设备模型,并自动在/sys/devices下创建一个对应的目录。Device_unregister()完成相反的操作,注销设备对象。
Get_device()和put_device()分别增加与减少设备对象的引用计数。通常device结构不单独使用,而是包含在更大的结构中作为一
个子结构使用,比如描述PCI设备的struct pci_dev,其中的dev域就是一个device对象。
4.2 drivers
系统中的每个驱动程序由一个device_driver对象描述,对应的数据结构定义为:
struct device_driver {
char *name; // 设备驱动程序的名称
struct bus_type *bus; // 该驱动所管理的设备挂接的总线类型
struct kobject kobj; // 内嵌kobject对象
struct list_head devices; // 该驱动所管理的设备链表头
int (*probe)(struct device *dev); // 指向设备探测函数,用于探测设备是否可以被该驱动程序管理
int (*remove)(struct device *dev); // 用于删除设备的函数
/* some fields omitted*/
};
与device 结构类似,device_driver对象依靠内嵌的kobject对象实现引用计数管理和层次结构组织。内核提供类似的函数用于操作
device_driver对象,如get_driver()增加引用计数,driver_register()用于向设备模型插入新的driver对
象,同时在sysfs文件系统中创建对应的目录。Device_driver()结构还包括几个函数,用于处理热拔插、即插即用和电源管理事件。
4.3 buses
系统中总线由struct bus_type描述,定义为:
struct bus_type {
char * name; // 总线类型的名称
struct subsystem subsys; // 与该总线相关的subsystem
struct kset drivers; // 所有与该总线相关的驱动程序集合
struct kset devices; // 所有挂接在该总线上的设备集合
struct bus_attribute * bus_attrs; // 总线属性
struct device_attribute * dev_attrs; // 设备属性
struct driver_attribute * drv_attrs; // 驱动程序属性
int (*match)(struct device * dev, struct device_driver * drv);
int (*hotplug) (struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
int (*suspend)(struct device * dev, u32 state);
int (*resume)(struct device * dev);
};
每 个bus_type对象都内嵌一个subsystem对象,bus_subsys对象管理系统中所有总线类型的subsystem对象。每个
bus_type对象都对应/sys/bus目录下的一个子目录,如PCI总线类型对应于/sys/bus/pci。在每个这样的目录下都存在两个子目
录:devices和drivers(分别对应于bus_type结构中的devices和drivers域)。其中devices子目录描述连接在该总
线上的所有设备,而drivers目录则描述与该总线关联的所有驱动程序。与device_driver对象类似,bus_type结构还包含几个函数
(match()、hotplug()等)处理相应的热插拔、即插即拔和电源管理事件。
4.4 classes
系统中的设备类由 struct
class描述,表示某一类设备。所有的class对象都属于class_subsys子系统,对应于sysfs文件系统中的/sys/class目录。
每个class对象包括一个class_device链表,每个class_device对象表示一个逻辑设备,并通过struct
class_device中的dev域(一个指向struct
device的指针)关联一个物理设备。这样,一个逻辑设备总是对应于一个物理设备,但是一个物理设备却可能对应于多个逻辑设备。此外,class结构中
还包括用于处理热插拔、即插即拔和电源管理事件的函数,这与device对象和driver对象相似。
C中的位运算
能够运用到任何整形的数据类型上(包括char, int), 无论有没有short, long, unsigned这样的限定词.
位运算的应用
// 交换指针变量x,y所指向的存储位置处存放的值
// 优势是不需要第三个位置来临时存储另一个值
// 但是这个方法并没有明显的性能优势,只是一个智力上的消遣
void inplace_swap(int *x, int *y)
{
*x = *x ^ *y;
*x = *x ^ *y;
*x = *x ^ *y;
}
位运算常见用法:
实现掩码运算
-----------------------------------
Java中的位运算
因为现行的计算机都是以八位一个字节为存储单位,那么一个16位的整数,也就是C语言中的short,在内存中可能有两种存储顺序big-
endian和litte-endian.考虑一个short整数0x3132(0x32是低位,0x31是高位),把它赋值给一个short变量,那么它在内存中的存储可
能有如下两种情况:
大端字节(Big-endian):
short变量地址
0x1000 0x1001
___________________________________
| |
| 0x31 | 0x32
|________________ | ________________
高位字节在低位字节的前面,也就是高位在内存地址低的一端.可以这样记住(大端->高位->在前->正常的逻辑顺序)
小端字节(little-endian):
short变量地址
0x1000 0x1001
_____________________________________
| |
| 0x32 | 0x31
|________________ | __________________
低位字节在高位字节的前面,也就是低位在内存地址低的一端.可以这样记住(小端->低位->在前->与正常逻辑顺序相反)
可以做个实验
在windows上下如下程序
#include <stdio.h>
#include <assert.h>
void main( void )
{
short test;
FILE* fp;
test = 0x3132; //(31ASIIC码的’1’,32ASIIC码的’2’)
if ((fp = fopen ("c:""test.txt", "wb")) == NULL)
assert(0);
fwrite(&test, sizeof(short), 1, fp);
fclose(fp);
}
然后在C盘下打开test.txt文件,可以看见内容是21,而test等于0x3132,可以明显的看出来x86的字节顺序是低位在前.如果我们
把这段同样的代码放到(big-endian)的机器上执行,那么打出来的文件就是12.这在本机中使用是没有问题的.但当你把这个文件从一
个big- endian机器复制到一个little-endian机器上时就出现问题了.
如上述例子,我们在big-endian的机器上创建了这个test文件,把其复制到little-endian的机器上再用fread读到一个 short里
面,我们得到的就不再是0x3132而是0x3231了,这样读到的数据就是错误的,所以在两个字节顺序不一样的机器上传输数据时需要特别
小心字节顺序,理解了字节顺序在可以帮助我们写出移植行更高的代码.
正因为有字节顺序的差别,所以在网络传输的时候定义了所有字节顺序相关的数据都使用big-endian,BSD的代码中定义了四个宏来处
理:
#define ntohs(n) //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表short
#define htons(n) //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表short
#define ntohl(n) //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表 long
#define htonl(n) //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表 long
举例说明下这其中一个宏的实现:
#define sw16(x) "
((short)( "
(((short)(x) & (short)0x00ffU) << 8) | "
(((short)(x) & (short)0xff00U) >> 8) ))
这里实现的是一个交换两个字节顺序.其他几个宏类似.
我们改写一下上面的程序
#include <stdio.h>
#include <assert.h>
#define sw16(x) "
((short)( "
(((short)(x) & (short)0x00ffU) << 8) | "
(((short)(x) & (short)0xff00U) >> 8) ))
// 因为x86下面是低位在前,需要交换一下变成网络字节顺序
#define htons(x) sw16(x)
void main( void )
{
short test;
FILE* fp;
test = htons(0x3132); //(31ASIIC码的’1’,32ASIIC码的’2’)
if ((fp = fopen ("c:""test.txt", "wb")) == NULL)
assert(0);
fwrite(&test, sizeof(short), 1, fp);
fclose(fp);
}
如果在高字节在前的机器上,由于与网络字节顺序一致,所以我们什么都不干就可以了,只需要把#define htons(x) sw16(x)宏替
换为 #define htons(x) (x).
一开始我在理解这个问题时,总在想为什么其他数据不用交换字节顺序?比如说我们write一块buffer到文件,最后终于想明白了,
因为都是unsigned char类型一个字节一个字节的写进去,这个顺序是固定的,不存在字节顺序的问题.
【用函数判断系统是Big Endian还是Little Endian】
bool IsBig_Endian()
//如果字节序为big-endian,返回true;
//反之为 little-endian,返回false
{
unsigned short test = 0x1122;
if(*( (unsigned char*) &test ) == 0x11)
return TRUE;
else
return FALSE;
}//IsBig_Endian()
【打印程序对象的字节表示】
// 可在不同平台与硬件架构的机器中测试运行这段代码,理解大端表示和小端表示的不同.
// 这段代码使用强制类型转换规避类型系统
#incluede <stdio.h>
// 假设每个字节都是非负整数
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start, int len)
{
for(int i = 0; i < len; i++)
printf(" %.2x", start[i]);
printf("\n");
}
void show_int(int x)
{
show_bytes((byte_pointer) &x, sizeof(int));
}
void show_float(float x)
{
show_bytes((byte_pointer) &x, sizeof(float));
}
// 在使用相同编码(如ASCII编码)的系统中,字符串字节表示得到的结果一般是相同的.所以文本数据比二进制数据具有更强的平台无关性
void show_string(char *x)
{
show_bytes((byte_pointer) x, strlen(x));
}
void show_pointer(void *x)
{
show_bytes((byte_pointer) &x, sizeof(void *));
}
void test_show_bytes(int val)
{
int ival = val;
float fval = (float)ival;
int *pval = &ival;
show_int(ival); // 各个机器因为大端表示和小端表示的不同,从而只是字节顺序不同
show_float(fval); // 各个机器因为大端表示和小端表示的不同,从而只是字节顺序不同
show_pointer(pval); // 指针值是与机器相关的(linux,sun使用4字节地址, 而alpha使用八字节地址)
}
---------------------------------------------
对于如数值12345在int型和float型时的编码表示
2008年11月4日
基本点:
Generic Connections
In the CLDC Generic Connection framework, all connections are created using
the open static method from the Connector class. If
successful, this method returns an object that implements one of the generic
connection interfaces. Figure 1 shows how these interfaces form an is-a
hierarchy. The Connection interface is the base interface such
that StreamConnectionNotifier is a Connection and
InputConnection is a Connection too.
Figure 1: Connection interface hierarchy
- The
Connection interface is the most basic connection type. It
can only be opened and closed.
- The
InputConnection interface represents a device from which
data can be
read. Its openInputStream method returns an input stream for the
connection.
- The
OuputConnection interface represents a device to which data
can be
written. Its openOutputStream method returns an output stream for
the connection.
- The
StreamConnection interface combines the input and output
connections.
- The
ContentConnection is a subinterface of
StreamConnection . It
provides access to some of the basic meta data information provided by HTTP
connections.
- The
StreamConnectionNotified waits for a connection to be
established.
It returns a StreamConnection on which a communication link has ben
established.
- The
DatagramConnection represents a datagram endpoint.
The open method of the Connector class has the
following syntax, where the String parameter has the format
"protocol:address;parameters" .
Connector.open(String);
Here are a few examples:
HTTP Connection
Connector.open("http://java.sun.com/developer");
Datagram Connection
Connector.open("datagram://address:port#");
Communicate with a Port
Connector.open("comm:0;baudrate=9600');
Open Files
Connector.open("file:/myFile.txt");
The HttpConnection Interface:
The HTTP protocol is a request-response application protocol in which the
parameters of the request must be set before the request is sent. The connection
could be in one of the three following states:
- Setup: No connection yet
- Connected: Connection has been made, the request has been sent, and some
response is
expected
- Closed: Connection is closed
In the setup state the following methods can be invoked:
setRequestMethod
setRequestProperty
For example, suppose you have this connection:
HttpConnection c = (HttpConnection)
Connector.open("http://java.sun.com/developer");
Then, you can set the request method to be of type POST as follows:
c.setRequestMethod(HttpConnection.POST);
And likewise, you can set some of the HTTP properties. For example, you
can set the User-Agent as follows:
c.setRequestProperty("User-Agent","Profile/MIDP-1.0 Configuration/CLDC-1.0");
If there is a method that requires data to be sent or received from the
server, there is a state transition from Setup to Connected.
Examples of methods that cause the transition include:
openInputStream
openOutputStream
openDataInputStream
openDataOutputStream
getLength
getType
getDate
getExpiration
And while the connection is open, some of these methods that may
be invoked:
getURL
getProtocol
getHost
getPort
------------------------------------------------------------
要注意的问题:
开发中遇到个很头疼的问题, 与服务器通信write()数据时报java.io.IOException: Couldn't write to socket.
但是服务器抓不到任何包. 一开始怀疑是连建立连接出的问题, 实际上服务器抓不到包也有可能是流在没有close的时候就已经报错了.
如:
conn.open("url");
out = conn.openDataOutputStream();//此时将进行与服务器的三次握手;
//但是如果在out.close()之前出现异常服务器是抓不到任何包的
out.write(byte[] bb);
关于这个的解释应该是流的缓冲机制.
所以正确的写法应该是捕捉到异常之后在catch块中把流close掉 .
服务器端开发人员一般会说收不到包所以连接有问题,会把责任推给客户端,抓住这个证据在跟服务器端的同事扯皮时将处于有利的位置,嘎嘎.
还有就是要多做小实验, 注意代码要规范严格.
发现的几个问题:
1. java.io.IOException: Couldn't write to socket
2.
java. io. IOException: Couldn't read from socket
CMNET联网方案:
CMWAP联网方案:
移动资费页的处理:
一个通用的HTTP连接封装:
2008年11月3日
vim简介
Vim(Vi Improved) 是一个类似于vi 的文本编辑器,在Vi的基础上增加了很多新的特性和功能。Vim以其强大的功能和可定制能力
,成为Linux/Unix环境下开源的最重要的编辑器之一(另一个是 Emacs),被众多开发者所喜爱。笔者此时所用的是最新的7.1版本
。
与大部分其它编辑器不同,进入 Vim 后,缺省状态下键入的字符并不会插入到所编辑的文件之中。Vim 的模式(mode,可以简单地
理解为“状态”)概念非常重要。需要知道,Vim 有以下几个模式:
1) 正常(normal)模式,缺省的编辑模式;下面如果不加特殊说明,提到的命令都直接在正常模式下输入;任何其它模式中都
可以通过键盘上的 Esc 键回到正常模式。
2) 命令(command)模式,用于执行较长、较复杂的命令;在正常模式下输入“:”(一般命令)、“/”(正向搜索)或“?”
(反向搜索)即可进入该模式;命令模式下的命令要输入回车键(Enter)才算完成。
3) 插入(insert)模式,输入文本时使用;在正常模式下键入“i”(insert)或“a”(append)即可进入插入模式(也有另
外一些命令,如“c”,也可以进入插入模式,但这些命令有其它的作用)。
4) 可视(visual)模式,用于选定文本块;可以在正常模式下输入“v”(小写)来按字符选定,输入“V”(大写)来按行选
定,或输入“Ctrl-V”来按方块选定。
5) 选择(select)模式,与普通的 Windows 编辑器较为接近的选择文本块的方式;在以可视模式和选择模式之一选定文本块之
后,可以使用“Ctrl-G”切换到另一模式——该模式很少在 Linux 上使用,本文中就不再介绍了。
------------------------------------------
首先 vim ~/.vimrc 打开编辑文件
[转]
1、VI或VIM的配置文件的路径
发现/usr/share/vim/vimrc和/etc/vim/vimrc指向是同一个文件,即vimrc,为vi和vim的配置文件,修改这个文件即可。这个路径在不同的LINUX版本中可能会不同。
2、配置颜色
配轩VI和VIM的颜色显示,使它能够高亮度显示一些特别的单词,这对编写程序很有用。后来打开文件发现里面其实已经有一行了,只是用引号注释掉了,只需
将syntax on 所在行前面的引号去掉即可。或者另外独立添加一行:syntax on 也行,另外编辑/etc/profile
增加一行alias vi="vim"就行了。
3、设置鼠标
使用VI编辑文本时,如果想修文件中改离光标较远的位置,这时候想用鼠标定位,可默认情况下,鼠标是不可用的。如果你想使用鼠标,只需另起一行,写上:set mouse=a 即可
4、设置自动缩进
默认情况下,VI和VIM都没有缩进的,每换一行,光标均定位在顶格,如果你想自动对齐,请将 set
autoindent所在行前面的引号去掉,或者另外添加一行:set
autoindent也可。这与配置颜色类似。这样的设置的结果是按回车后新行与上一行自动对齐。
5、设置tab的缩进量
如果用python编写程序,那么行缩进量是一个极其重要的概念,同一个块的缩进量必须相同。你可能喜欢在行前加空格来表示缩进,但每次必须敲多次空格
键,如果你喜欢用按TAB键来表示缩进,你可能觉得写的文本或程序不太好看,因为默认情况下,VI和VIM的TAB缩进量比较大(至少六,七个字符)。设
置TAB键缩进量的方法:set shiftwidth=3 你也可以选一 个你自己喜欢的缩进量,比如2,或4.
----------------------------------------------
首先从视觉方面:
第一个要做的是缩进,修改你的VIM配置文件_vimrc,在最后加入set cindent,这样就设置了c风格的缩进,在这里缩进的大小是shiftwidth的值。
第二个要做的是语法高亮,这个是必须的,在中_vimrc加入syntax enable。
第三个要做的字体的设置,设置一个舒服的字体可以让你编程的时候舒服好多,用editplus的时候我就用的Consolas,在vim中我还是用的这种字体,在_vimrc中加入
if has(”gui_running”)
set guifont=Consolas:h9
endif
表示运行界面vim的时候就用这种字体。
第四,设置配色方案,可以到点击这儿下载,然后放到$VIM"vimfiles"colors这个目录下,然后在中加入如下配置
if has(”gui_running”)
set guifont=Consolas:h9
” set color schema
colorscheme colorscheme_name
endif
colorscheme_name为你需要设置的配色方案的名称。
接下来是在运行程序中用到的:
第一,使用ctag
VIM中已经带了Ctags这个程序。尽管ctags也可以支持其它编辑器,但是它正式支持的只有VIM。Ctags可以帮助程序员很容易地浏览源代码。用下面的命令可以在源代码的根目录下创建“tags”文件:
[/home/brimmer/src]$ ctags -R
“-R”表示递归创建,也就包括源代码根目录下的所有子目录下的源程序。“tags”文件中包括这些对象的列表:
l 用#define定义的宏
l 枚举型变量的值
l 函数的定义、原型和声明
l 名字空间(namespace)
l 类型定义(typedefs)
l 变量(包括定义和声明)
l 类(class)、结构(struct)、枚举类型(enum)和联合(union)
l 类、结构和联合中成员变量或函数
VIM用这个“tags”文件来定位上面这些做了标记的对象,下面介绍一下定位这些对象的方法:
1) 用命令行。在运行vim的时候加上“-t”参数,例如:
[/home/brimmer/src]$ vim -t foo_bar
这个命令将打开定义“foo_bar”(变量或函数或其它)的文件,并把光标定位到这一行。
2) 在vim编辑器内用“:ta”命令,例如:
:ta foo_bar
3) 最方便的方法是把光标移到变量名或函数名上,然后按下“Ctrl-]”。用“Ctrl-o”退回原来的地方。
注意:运行vim的时候,必须在“tags”文件所在的目录下运行。否则,运行vim的时候还要用“:set tags=”命令设定“tags”文件的路径,这样vim才能找到“tags”文件。
你还可以选择使用taglist这个插件,这个插件可以在右侧显示函数,变量等的列表
第二,改正程序中的错误
在VIM编辑器的环境下用“:make”(make工具的使用已经在我昨天的文章中
提到的云风的几篇文章中详细介绍到)就可以编译程序,当然其前提是在当前目录下有Makefile文件。运行完“:make”之后,如果程序中有错误,就
会显示出来。这时候,光标会自动指向第一个出现错误的地方,而且你还可以看到错误的提示。然后,你就可以改正错误,而不用手工找到出错的那一行。记住下面
几个有用的命令:
l “:cl”列出错误
l “:cn”让光标指向下一个错误
l “:cp”让光标指向上一个错误
l “:cnew”从头开始
你甚至可以让VIM识别出其它编译器而不是gcc的错误提示。这对一些开发嵌入式系统的程序员这很有用,因为他们很可能用的不是gcc而是其它编译器。通过设置“errorformat”的值,可以让VIM识别出编译器的出错提示。因为不同的编译器的出错提示是不同的,所以如果用的不是gcc就要重新设置。
“errorformat”的值是一个字符串,它的格式和C语言的scanf的字符串格式相识。
gcc的“errorformat”的值为:%f:%l:"%m。其中“%f”表示文件名,“%l”表示行号,“%m”表示出错信息。
用“:h errorformat”查看详细的帮助信息。
用“:h quickfix”、“:h make”、“:h makeprg”、“:h errorfile”查看其它的信息。
第三,使用快捷键
下面的这些快捷键对程序员很有帮助:
在函数中移动光标
[[ 转到上一个位于第一列的“{”
]] 转到下一个位于第一列的“{”
{ 转到上一个空行
} 转到下一个空行
gd 转到当前光标所指的局部变量的定义
* 转到当前光标所指的单词下一次出现的地方
# 转到当前光标所指的单词上一次出现的地方
括号的匹配
% 用来进行小括号、中括号和大括号的匹配。这要看当前光标指向的是什么符号了。
----------------------------------------------
一个ubuntu 下的vimrc配置例子:
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 一般设定
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 设定默认解码
set fenc=utf-8
set fencs=utf-8,usc-bom,euc-jp,gb18030,gbk,gb2312,cp936
"设定搜索是的高亮显示
set hlsearch
" 不要使用vi的键盘模式,而是vim自己的
set nocompatible
" history文件中需要记录的行数
set history=100
" 在处理未保存或只读文件的时候,弹出确认
set confirm
" 与windows共享剪贴板
set clipboard+=unnamed
" 侦测文件类型
filetype on
" 载入文件类型插件
filetype plugin on
" 为特定文件类型载入相关缩进文件
filetype indent on
" 保存全局变量
set viminfo+=!
" 带有如下符号的单词不要被换行分割
set iskeyword+=_,$,@,%,#,-
" 语法高亮
syntax on
" 高亮字符,让其不受100列限制
:highlight OverLength ctermbg=red ctermfg=white guibg=red guifg=white
":match OverLength '"%101v.*'
" 状态行颜色
highlight StatusLine guifg=SlateBlue guibg=Yellow
highlight StatusLineNC guifg=Gray guibg=White
"高亮当前行
set cursorline
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 文件设置
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 不要备份文件(根据自己需要取舍)
set nobackup
" 不要生成swap文件,当buffer被丢弃的时候隐藏它
setlocal noswapfile
set bufhidden=hide
" 字符间插入的像素行数目
set linespace=0
" 增强模式中的命令行自动完成操作
set wildmenu
" 在状态行上显示光标所在位置的行号和列号
set ruler
set rulerformat=%20(%2*%<%f%=" %m%r" %3l" %c" %p%%%)
" 命令行(在状态行下)的高度,默认为1,这里是2
set cmdheight=2
" 使回格键(backspace)正常处理indent, eol, start等
set backspace=2
" 允许backspace和光标键跨越行边界
set whichwrap+=<,>,h,l
" 可以在buffer的任何地方使用鼠标(类似office中在工作区双击鼠标定位)
set mouse=a
set selection=exclusive
set selectmode=mouse,key
" 启动的时候不显示那个援助索马里儿童的提示
set shortmess=atI
" 通过使用: commands命令,告诉我们文件的哪一行被改变过
set report=0
" 不让vim发出讨厌的滴滴声
set noerrorbells
" 在被分割的窗口间显示空白,便于阅读
set fillchars=vert:" ,stl:" ,stlnc:"
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 搜索和匹配
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 高亮显示匹配的括号
set showmatch
" 匹配括号高亮的时间(单位是十分之一秒)
set matchtime=5
" 在搜索的时候不忽略大小写
set noignorecase
" 不要高亮被搜索的句子(phrases)
"set nohlsearch
" 在搜索时,输入的词句的逐字符高亮(类似firefox的搜索)
set incsearch
" 输入:set list命令是应该显示些啥?
set listchars=tab:"|" ,trail:.,extends:>,precedes:<,eol:$
" 光标移动到buffer的顶部和底部时保持3行距离
set scrolloff=3
" 不要闪烁
set novisualbell
" 我的状态行显示的内容(包括文件类型和解码)
set statusline=%F%m%r%h%w" [FORMAT=%{&ff}]" [TYPE=%Y]" [POS=%l,%v][%p%%]" %{strftime(""%d/%m/%y" -" %H:%M"")}
" 总是显示状态行
set laststatus=2
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 文本格式和排版
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 自动格式化
set formatoptions=tcrqn
" 继承前一行的缩进方式,特别适用于多行注释
set autoindent
" 为C程序提供自动缩进
set smartindent
" 使用C样式的缩进
set cindent
" 制表符为4
set tabstop=4
" 统一缩进为4
set softtabstop=4
set shiftwidth=4
" 不要用空格代替制表符
set noexpandtab
" 不要换行
"set nowrap
"设置每行80个字符自动换行
set textwidth=80
" 在行和段开始处使用制表符
set smarttab
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" CTags的设定
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 按照名称排序
let Tlist_Sort_Type = "name"
" 在右侧显示窗口
let Tlist_Use_Right_Window = 1
" 压缩方式
let Tlist_Compart_Format = 1
" 如果只有一个buffer,kill窗口也kill掉buffer
let Tlist_Exist_OnlyWindow = 1
" 不要关闭其他文件的tags
let Tlist_File_Fold_Auto_Close = 0
" 不要显示折叠树
let Tlist_Enable_Fold_Column = 1
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Autocommands
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" 只在下列文件类型被侦测到的时候显示行号,普通文本文件不显示
if has("autocmd")
autocmd FileType xml,html,c,cs,java,perl,shell,bash,cpp,python,vim,php,ruby set number
autocmd FileType xml,html vmap <C-o> <ESC>'<i<!--<ESC>o<ESC>'>o-->
autocmd FileType java,c,cpp,cs vmap <C-o> <ESC>'<o/*<ESC>'>o*/
autocmd FileType html,text,php,vim,c,java,xml,bash,shell,perl,python setlocal textwidth=80
autocmd Filetype html,xml,xsl source $VIMRUNTIME/plugin/closetag.vim
autocmd BufReadPost *
" if line("'""") > 0 && line("'""") <= line("$") |
" exe "normal g`""" |
" endif
endif " has("autocmd")
" C/C++的编译和运行
map <F5> :call CompileRunGcc()<CR>
func! CompileRunGcc()
exec "w"
exec "!make"
exec "! ./%<"
endfunc
" shell script运行
map <F6> :call CompileRunSH()<CR>
func! CompileRunSH()
exec "w"
exec "!chmod a+x %"
exec "!./%"
endfunc
" python运行
map <F7> :call CompileRunPyhton()<CR>
func! CompileRunPyhton()
exec "w"
exec "!chmod a+x %"
exec "!./%"
endfunc
" 能够漂亮地显示.NFO文件
set encoding=utf-8
function! SetFileEncodings(encodings)
let b:myfileencodingsbak=&fileencodings
let &fileencodings=a:encodings
endfunction
function! RestoreFileEncodings()
let &fileencodings=b:myfileencodingsbak
unlet b:myfileencodingsbak
endfunction
au BufReadPre *.nfo call SetFileEncodings('cp437')|set ambiwidth=single
au BufReadPost *.nfo call RestoreFileEncodings()
" 高亮显示普通txt文件(需要txt.vim脚本)
au BufRead,BufNewFile * setfiletype txt
" 用空格键来开关折叠
set foldenable
set foldlevel=0
set foldmethod=indent
nnoremap <space> @=((foldclosed(line('.')) < 0) ? 'zc' : 'zo')<CR>
" minibufexpl插件的一般设置
let g:miniBufExplMapWindowNavVim = 1
let g:miniBufExplMapWindowNavArrows = 1
let g:miniBufExplMapCTabSwitchBufs = 1
let g:miniBufExplModSelTarget = 1
----------------------
一个相关帖子
http://forum.ubuntu.org.cn/viewtopic.php?f=68&t=138212&st=0&sk=t&sd=a
2008年10月28日
1. Servlet过滤器基础
Servlet过滤器是Servlet的一种特殊用法,主要用来完成一些通用的操作。比如编码的过滤,判断用户的登陆状态等等。Servlet过滤器的适用场合:
A.认证过滤
B.登录和审核过滤
C.图像转换过滤
D.数据压缩过滤
E.加密过滤
F.令牌过滤
G.资源访问触发事件过滤
Servlet过滤器接口的构成:
所有的Servlet过滤器类都必须实现javax.servlet.Filter接口。这个接口含有3个过滤器类必须实现的方法:
方法 说明
init(FilterConfig cfg) 这是Servlet过滤器的初始化方法,性质等同与servlet的init方法。
doFilter(ServletRequest,ServletResponse,FilterChain) 完成实际的过滤操作,当请求访问过滤器关联的URL时,Servlet容器将先调用过滤器的doFilter方法。FilterChain参数用于访问后续过滤器
destroy() Servlet容器在销毁过滤器实例前调用该方法,这个方法中可以释放Servlet过滤器占用的资源。性质等同与servlet的destory()方法。
Servlet过滤器的创建步骤:
A.实现javax.servlet.Filter接口的servlet类
B.实现init方法,读取过滤器的初始化函数
C.实现doFilter方法,完成对请求或过滤的响应
D.调用FilterChain接口对象的doFilter方法,向后续的过滤器传递请求或响应
F.在web.xml中配置Filter
2.使用过滤器处理中文问题
当用用户登陆页面输入帐号时,如果输入是中文,后台servlet再次输出这个内容时,可能就会是乱码,这是因为serlvet中默认是以ISO-8859-1格式编码的,如果后台有多个Servlet,多个参数,这样就不合适,这个问题,我们可以通过一个过滤器统一解决,使后台的输出输出都支持中文!将ISO-8859-1转码为GBK的那段代码!
3.使用过滤器认证用户:
每个过滤器也可以配置初始化参数,可以将不需要过滤的地址配置到这个Filter的配置参数中,过滤时,如果请求地址在配置参数中,则放行,这样
就避免了在程序中硬编码。每个Filter中初始化时,都可以得到配置对象,在Filter中配置二个不需要过滤的地址,一个是登陆页面,一个是执行登陆
认证的servlet;
4.Servlet监听器
类似与Swing界面应用开发,Servlet也可以创建监听器,以对Servlet容器,或Servlet中以象的事件做出反应。Servlet监听器主要有以下几种:
ServletRequestListener ,ServletRequestAttributeListener,
HttpSessionActivationListener ,HttpSessionBindingListener ,
HttpSessionAttributeListener,HttpSessionListener,
ServletContextListener等等。
这些监听器主要用来监听session,request,application这三个对象里存取数据的变化。
----------------------------------------------------------------------------------------------------------------
servlet API中最重要的一个功能就是能够为servlet和JSP页面定义过滤器。过滤器提供了某些早期服务器所支持的非标准“servlet链接”的一种功能强大且标准的替代品。
过滤器是一个程序,它先于与之相关的servlet或JSP页面运行在服务器上。过滤器可附加到一个或多个servlet或JSP页面上,并且可以检查进入这些资源的请求信息。在这之后,过滤器可以作如下的选择:
1. 以常规的方式调用资源(即,调用servlet或JSP页面)。
2.利用修改过的请求信息调用资源。
3. 调用资源,但在发送响应到客户机前对其进行修改
4. 阻止该资源调用,代之以转到其他的资源,返回一个特定的状态代码或生成替换输出。
过滤器提供了几个重要好处 :
首先,它以一种模块化的或可重用的方式封装公共的行为。你有30个不同的serlvet或JSP页面,需要压缩它们的内容以减少下载时间吗?没问题:构造一个压缩过滤器,然后将它应用到30个资源上即可。
其次,利用它能够将高级访问决策与表现代码相分离。这对于JSP特别有价值,其中一般希望将几乎整个页面集中在表现上,而不是集中在业务逻辑上。例如,希
望阻塞来自某些站点的访问而不用修改各页面(这些页面受到访问限制)吗?没问题:建立一个访问限制过滤器并把它应用到想要限制访问的页面上即可。
最后,过滤器使你能够对许多不同的资源进行批量性的更改。你有许多现存资源,这些资源除了公司名要更改外其他的保持不变,能办到么?没问题:构造一个串替换过滤器,只要合适就使用它。
但要注意,过滤器只在与servlet规范2.3版兼容的服务器上有作用。如果你的Web应用需要支持旧版服务器,就不能使用过滤器。
1. 建立基本过滤器
建立一个过滤器涉及下列五个步骤:
1)建立一个实现Filter接口的类。这个类需要三个方法,分别是:doFilter、init和destroy。
doFilter方法包含主要的过滤代码(见第2步),init方法建立设置操作,而destroy方法进行清楚。
2)在doFilter方法中放入过滤行为。doFilter方法的第一个参数为ServletRequest对象。此对象给过滤器提供了对进入的信息
(包括表单数据、cookie和HTTP请求头)的完全访问。第二个参数为ServletResponse,通常在简单的过滤器中忽略此参数。最后一个参
数为FilterChain,如下一步所述,此参数用来调用servlet或JSP页。
3)调用FilterChain对象的doFilter方法。Filter接口的doFilter方法取一个FilterChain对象作为它的一个参
数。在调用此对象的doFilter方法时,激活下一个相关的过滤器。如果没有另一个过滤器与servlet或JSP页面关联,则servlet或JSP
页面被激活。
4)对相应的servlet和JSP页面注册过滤器。在部署描述符文件(web.xml)中使用filter和filter-mapping元素。
5)禁用激活器servlet。防止用户利用缺省servlet URL绕过过滤器设置。
1.1 建立一个实现Filter接口的类
所有过滤器都必须实现javax.servlet.Filter。这个接口包含三个方法,分别为doFilter、init和destroy。
public void doFilter(ServletRequset request,
ServletResponse response,
FilterChain chain)
thows ServletException, IOException
每当调用一个过滤器(即,每次请求与此过滤器相关的servlet或JSP页面)时,就执行其doFilter方法。正是这个方法包含了大部分过滤逻辑。
第一个参数为与传入请求有关的ServletRequest。对于简单的过滤器,大多数过滤逻辑是基于这个对象的。如果处理HTTP请求,并且需要访问诸
如getHeader或getCookies等在ServletRequest中无法得到的方法,就要把此对象构造成
HttpServletRequest。
第二个参数为ServletResponse。除了在两个情形下要使用它以外,通常忽略这个参数。首先,如果希望完全阻塞对相关servlet或JSP页
面的访问。可调用response.getWriter并直接发送一个响应到客户机。其次,如果希望修改相关的servlet或JSP页面的输出,可把响
应包含在一个收集所有发送到它的输出的对象中。然后,在调用serlvet或JSP页面后,过滤器可检查输出,如果合适就修改它,之后发送到客户机。
DoFilter的最后一个参数为FilterChain对象。对此对象调用doFilter以激活与servlet或JSP页面相关的下一个过滤器。如果没有另一个相关的过滤器,则对doFilter的调用激活servlet或JSP本身。
public void init(FilterConfig config) thows ServletException
init方法只在此过滤器第一次初始化时执行,不是每次调用过滤器都执行它。对于简单的过滤器,可提供此方法的一个空体,但有两个原因需要使用init。
首先,FilterConfig对象提供对servlet环境及web.xml文件中指派的过滤器名的访问。因此,普遍的办法是利用init将
FilterConfig对象存放在一个字段中,以便doFilter方法能够访问servlet环境或过滤器名.其次,FilterConfig对象具
有一个getInitParameter方法,它能够访问部署描述符文件(web.xml)中分配的过滤器初始化参数。
public void destroy( )
大多数过滤器简单地为此方法提供一个空体,不过,可利用它来完成诸如关闭过滤器使用的文件或数据库连接池等清除任务。
1.2 将过滤行为放入doFilter方法
doFilter方法为大多数过滤器地关键部分。每当调用一个过滤器时,都要执行doFilter。对于大多数过滤器来说,doFilter执行的步骤是
基于传入的信息的。因此,可能要利用作为doFilter的第一个参数提供的ServletRequest。这个对象常常构造为
HttpServletRequest类型,以提供对该类的更特殊方法的访问。
1.3 调用FilterChain对象的doFilter方法
Filter接口的doFilter方法以一个FilterChain对象作为它的第三个参数。在调用该对象的doFilter方法时,激活下一个相关的
过滤器。这个过程一般持续到链中最后一个过滤器为止。在最后一个过滤器调用其FilterChain对象的doFilter方法时,激活servlet或
页面自身。
但是,链中的任意过滤器都可以通过不调用其FilterChain的doFilter方法中断这个过程。在这样的情况下,不再调用JSP页面的serlvet,并且中断此调用过程的过滤器负责将输出提供给客户机。
1.4 对适当的servlet和JSP页面注册过滤器
部署描述符文件的2.3版本引入了两个用于过滤器的元素,分别是:filter和filter-mapping。filter元素向系统注册一个过滤对象,filter-mapping元素指定该过滤对象所应用的URL。
1.filter元素
filter元素位于部署描述符文件(web.xml)的前部,所有filter-mapping、servlet或servlet-mapping元素之前。filter元素具有如下六个可能的子元素:
1、 icon 这是一个可选的元素,它声明IDE能够使用的一个图象文件。
2、filter-name 这是一个必需的元素,它给过滤器分配一个选定的名字。
3、display-name 这是一个可选的元素,它给出IDE使用的短名称。
4、 description 这也是一个可选的元素,它给出IDE的信息,提供文本文档。
5、 filter-class 这是一个必需的元素,它指定过滤器实现类的完全限定名。
6、 init-param 这是一个可选的元素,它定义可利用FilterConfig的getInitParameter方法读取的初始化参数。单个过滤器元素可包含多个init-param元素。
请注意,过滤是在serlvet规范2.3版中初次引入的。因此,web.xml文件必须使用DTD的2.3版本。下面介绍一个简单的例子:
<xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>MyFilterfilter-name>
<filter-class>myPackage.FilterClassfilter-class>
filter>
<filter-mapping>...filter-mapping>
<web-app>
2.filter-mapping元素
filter-mapping元素位于web.xml文件中filter元素之后serlvet元素之前。它包含如下三个可能的子元素:
1、 filter-name 这个必需的元素必须与用filter元素声明时给予过滤器的名称相匹配。
2、 url-pattern
此元素声明一个以斜杠(/)开始的模式,它指定过滤器应用的URL。所有filter-mapping元素中必须提供url-pattern或
servlet-name。但不能对单个filter-mapping元素提供多个url-pattern元素项。如果希望过滤器适用于多个模式,可重复
整个filter-mapping元素。
3、 servlet-name
此元素给出一个名称,此名称必须与利用servlet元素给予servlet或JSP页面的名称相匹配。不能给单个filter-mapping元素提供
多个servlet-name元素项。如果希望过滤器适合于多个servlet名,可重复这个filter-mapping元素。
下面举一个例子:
xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<filter>
<filter-name>MyFilterfilter-name>
<filter-class>myPackage.FilterClassfilter-class>
filter>
<filter-mapping>
<filter-name>MyFilterfilter-name>
<url-pattern>/someDirectory/SomePage.jspurl-pattern>
filter-mapping>
web-app>
1.5 禁用激活器servlet
在对资源应用过滤器时,可通过指定要应用过滤器的URL模式或servlet名来完成。如果提供servlet名,则此名称必须与web.xml的
servlet元素中给出的名称相匹配。如果使用应用到一个serlvet的URL模式,则此模式必须与利用web.xml的元素servlet-
mapping指定的模式相匹配。但是,多数服务器使用“激活器servlet”为servlet体统一个缺省的URL:http:
//host/WebAppPrefix/servlet/ServletName。需要保证用户不利用这个URL访问servlet(这样会绕过过滤器
设置)。
例如,假如利用filter和filter-mapping指示名为SomeFilter的过滤器应用到名为SomeServlet的servlet,则如下:
<filter>
<filter-name>SomeFilterfilter-name>
<filter-class>somePackage.SomeFilterClassfilter-class>
<filter>
<filter-mapping>
<filter-name>SomeFilterfilter-name>
<servlet-name>SomeServletservlet-name>
<filter-mapping>
接着,用servlet和servlet-mapping规定URL http://host/webAppPrefix/Blah 应该调用SomeSerlvet,如下所示:
<filter>
<filter-name>SomeFilterfilter-name>
<filter-class>somePackage.SomeFilterClassfilter-class>
filter>
<filter-mapping>
<filter-name>SomeFilterfilter-name>
<servlet-name>/Blahservlet-name>
<filter-mapping>
现在,在客户机使用URL http://host/webAppPrefix/Blah 时就会调用过滤器。过滤器不应用到
http://host/webAppPrefix/servlet/SomePackage.SomeServletClass。
尽管有关闭激活器的服务器专用方法。但是,可移植最强的方法时重新映射Web应用钟的/servlet模式,这样使所有包含此模式的请求被送到相同的
servlet中。为了重新映射此模式,首先应该建立一个简单的servlet,它打印一条错误消息,或重定向用户到顶层页。然后,使用servlet和
servlet-mapping元素发送包含/servlet模式的请求到该servlet。程序清单9-1给出了一个简短的例子。
程序清单9-1 web.xml(重定向缺省servlet URL的摘录)
xml version="1.0" encoding="ISO-8859-1"?>
DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Errorservlet-name>
<servlet-class>somePackage.ErrorServletservlet-class>
servlet>
<servlet-mapping>
<servlet-name>Errorservlet-name>
<url-pattern>/servlet/*url-pattern>
servlet-mapping>
<web-app>
-------------------------------------------------------------------------------------------
解决乱码
web.xml加配置
<!-- 过滤器 -->
<filter>
<filter-name>Filter</filter-name>
<filter-class>
com.util.EncodingFilter<!-- 过滤器类 -->
</filter-class>
<init-param>
<param-name>Encoding</param-name>
<param-value>gb2312</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
EncodingFilter.java
package com.hibernate.util;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class EncodingFilter implements Filter {
protected FilterConfig config;
protected String Encoding = null;
public void init(FilterConfig config) throws ServletException {
this.config = config;
this.Encoding = config.getInitParameter("Encoding");
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request.getCharacterEncoding() == null) {
if (Encoding != null) {
request.setCharacterEncoding(Encoding);
response.setCharacterEncoding(Encoding);
}
}
chain.doFilter(request,response);
}
public void destroy() {}
}
OK!!!
----------------------------------------------------------------------------------------------------
很简单的过滤器,就是为了记录一个url的请求时间 filter:
- package com.javaeye.wqf;
- import javax.servlet.*;
-
- public class CounterFilter implements Filter {
- public void doFilter(ServletRequest request, ServletResponse response,
- FilterChain chain) throws IOException, ServletException {
- long start = System.currentTimeMillis();
- System.out.println("Filter start at "+start);
- chain.doFilter(request, response);
- long end = System.currentTimeMillis();
- System.out.println("Filter end at "+end);
- }
- }
- <filter>
- <filter-name>test</filter-name>
- <filter-class>com.javaeye.wqf.CounterFilter</filter-class>
- </filter>
-
- <filter-mapping>
- <filter-name>test</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
一般情况下是没什么问题,但是当我下载一个稍微大的文件时,跳出确认窗口,如果选择的是cancel,
就会发现filter并没有返回,也就是说
- System.out.println("Filter end at "+end);
并没有执行. 原因是:
chain.doFilter(request, response);
执行到这里时会从这里调用剩下的filter和servlet,所以这个调用将会是一个很长的过程。
在这个调用里,将会完全通过request和resonse去操作连接,取得/发送数据,如果连接出现异常,将直接弹出Exception
你的代码里没有捕获异常,所以如果出现异常,chain.doFilter后面的就不会执行。
可以把chain.doFilter放到try finally结构中,保证后续会被执行
2008年10月7日
Anatomy of an Android Application
There are four building blocks to an Android application:
- Activity
- Intent Receiver
- Service
- Content Provider
Not every application needs to have all four, but your application will be written with some combination of these.
Once you have decided what components you need for your application,
you should list them in a file called AndroidManifest.xml. This is an
XML file where you declare the components of your application and what
their capabilities and requirements are. See the Android manifest file
documentation for complete details.
Activity
Activities are the most common of the four Android building blocks.
An activity is usually a single screen in your application. Each
activity is implemented as a single class that extends the Activity
base class. Your class will display a user interface composed of Views
and respond to events. Most applications consist of multiple screens.
For example, a text messaging application might have one screen that
shows a list of contacts to send messages to, a second screen to write
the message to the chosen contact, and other screens to review old
messages or change settings. Each of these screens would be implemented
as an activity. Moving to another screen is accomplished by a starting
a new activity. In some cases an activity may return a value to the
previous activity -- for example an activity that lets the user pick a
photo would return the chosen photo to the caller.
When a new screen opens, the previous screen is paused and put onto
a history stack. The user can navigate backward through previously
opened screens in the history. Screens can also choose to be removed
from the history stack when it would be inappropriate for them to
remain. Android retains history stacks for each application launched
from the home screen.
Intent and Intent Filters
Android uses a special class called an Intent to move from screen
to screen. An intent describes what an application wants done. The two
most important parts of the intent data structure are the action and
the data to act upon. Typical values for action are MAIN (the front
door of the activity), VIEW, PICK, EDIT, etc. The data is expressed as
a URI. For example, to view contact information for a person, you would
create an intent with the VIEW action and the data set to a URI
representing that person.
There is a related class called an IntentFilter. While an intent is
effectively a request to do something, an intent filter is a
description of what intents an activity (or intent receiver, see below)
is capable of handling. An activity that is able to display contact
information for a person would publish an IntentFilter that said that
it knows how to handle the action VIEW when applied to data
representing a person. Activities publish their IntentFilters in the
AndroidManifest.xml file.
Navigating from screen to screen is accomplished by resolving
intents. To navigate forward, an activity calls
startActivity(myIntent). The system then looks at the intent filters
for all installed applications and picks the activity whose intent
filters best matches myIntent. The new activity is informed of the
intent, which causes it to be launched. The process of resolving
intents happens at run time when startActivity is called, which offers
two key benefits:
* Activities can reuse functionality from other components simply by making a request in the form of an Intent
* Activities can be replaced at any time by a new Activity with an equivalent IntentFilter
Intent Receiver
You can use an IntentReceiver when you want code in your
application to execute in reaction to an external event, for example,
when the phone rings, or when the data network is available, or when
it's midnight. Intent receivers do not display a UI, although they may
use the NotificationManager to alert the user if something interesting
has happened. Intent receivers are registered in AndroidManifest.xml,
but you can also register them from code using
Context.registerReceiver(). Your application does not have to be
running for its intent receivers to be called; the system will start
your application, if necessary, when an intent receiver is triggered.
Applications can also send their own intent broadcasts to others with
Context.broadcastIntent().
Service
A Service is code that is long-lived and runs without a UI. A good
example of this is a media player playing songs from a play list. In a
media player application, there would probably be one or more
activities that allow the user to choose songs and start playing them.
However, the music playback itself should not be handled by an activity
because the user will expect the music to keep playing even after
navigating to a new screen. In this case, the media player activity
could start a service using Context.startService() to to run in the
background to keep the music going. The system will then keep the music
playback service running until it has finished. (You can learn more
about the priority given to services in the system by reading Lifecycle
of an Android Application.) Note that you can connect to a service (and
start it if it's not already running) with the Context.bindService()
method. When connected to a service, you can communicate with it
through an interface exposed by the service. For the music service,
this might allow you to pause, rewind, etc.
Content Provider
Applications can store their data in files, an SQLite database, or
any other mechanism that makes sense. A content provider, however, is
useful if you want your application's data to be shared with other
applications. A content provider is a class that implements a standard
set of methods to let other applications store and retrieve the type of
data that is handled by that content provider.
To get more details on content providers, see Accessing Content Providers.
2008年9月24日
当不能预先确定报文体的长度时,不可能在头中包含Content-Length域来指明报文体长度,此时就需要通过Transfer-Encoding域来确定报文体长度。
通常情况下,Transfer-Encoding域的值应当为chunked,表明采用chunked编码方式来进行报文体的传输。chunked编码是HTTP/1.1 RFC里定义的一种编码方式,因此所有的HTTP/1.1应用都应当支持此方式。
chunked编码的基本方法是将大块数据分解成多块小数据,每块都可以自指定长度,其具体格式如下(BNF文法):
Chunked-Body = *chunk //0至多个chunk
last-chunk //最后一个chunk
trailer //尾部
CRLF //结束标记符
chunk = chunk-size [ chunk-extension ] CRLF
chunk-data CRLF
chunk-size = 1*HEX
last-chunk = 1*("0") [ chunk-extension ] CRLF
chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
chunk-ext-name = token
chunk-ext-val = token | quoted-string
chunk-data = chunk-size(OCTET)
trailer = *(entity-header CRLF)
解释:
Chunked-Body表示经过chunked编码后的报文体。报文体可以分为chunk,
last-chunk,trailer和结束符四部分。chunk的数量在报文体中最少可以为0,无上限;每个chunk的长度是自指定的,即,起始的数
据必然是16进制数字的字符串,代表后面chunk-data的长度(字节数)。这个16进制的字符串第一个字符如果是“0”,则表示chunk-
size为0,该chunk为last-chunk,无chunk-data部分。可选的chunk-extension由通信双方自行确定,如果接收者
不理解它的意义,可以忽略。
trailer是附加的在尾部的额外头域,通常包含一些元数据(metadata, meta means "about information"),这些头域可以在解码后附加在现有头域之后。
实例分析:
下面分析用ethereal抓包使用Firefox与某网站通信的结果(从头域结束符后开始):
Address 0.......................... f
000c0 31
000d0 66 66 63 0d 0a ............... // ASCII码:1ffc"r"n, chunk-data数据起始地址为000d5
很明显,“1ffc”为第一个chunk的chunk-size,转换为int为8188.由于1ffc后马上就是
CRLF,因此没有chunk-extension.chunk-data的起始地址为000d5, 计算可知下一块chunk的起始
地址为000d5+1ffc + 2=020d3,如下:
020d0 .. 0d 0a 31 66 66 63 0d 0a .... // ASCII码:"r"n1ffc"r"n
前一个0d0a是上一个chunk的结束标记符,后一个0d0a则是chunk-size和chunk-data的分隔符。
此块chunk的长度同样为8188, 依次类推,直到最后一块
100e0 0d 0a 31
100f0 65 61 39 0d 0a...... //ASII码:"r"n"1ea9"r"n
此块长度为0x1ea9 = 7849, 下一块起始为100f5 + 1ea9 + 2 = 11fa0,如下:
100a0 30 0d 0a 0d 0a //ASCII码:0"r"n"r"n
“0”说明当前chunk为last-chunk, 第一个0d 0a为chunk结束符。第二个0d0a说明没有trailer部分,整个Chunk-body结束。
解码流程:
对chunked编码进行解码的目的是将分块的chunk-data整合恢复成一块作为报文体,同时记录此块体的长度。
RFC2616中附带的解码流程如下:(伪代码)
length := 0 //长度计数器置0
read chunk-size, chunk-extension (if any) and CRLF //读取chunk-size, chunk-extension
//和CRLF
while(chunk-size > 0 ) { //表明不是last-chunk
read chunk-data and CRLF //读chunk-size大小的chunk-data,skip CRLF
append chunk-data to entity-body //将此块chunk-data追加到entity-body后
read chunk-size and CRLF //读取新chunk的chunk-size 和 CRLF
}
read entity-header //entity-header的格式为name:valueCRLF,如果为空即只有CRLF
while (entity-header not empty) //即,不是只有CRLF的空行
{
append entity-header to existing header fields
read entity-header
}
Content-Length:=length //将整个解码流程结束后计算得到的新报文体length
//作为Content-Length域的值写入报文中
Remove "chunked" from Transfer-Encoding //同时从Transfer-Encoding中域值去除chunked这个标记
length最后的值实际为所有chunk的chunk-size之和,在上面的抓包实例中,一共有八块chunk-size为0x1ffc(8188)的chunk,剩下一块为0x1ea9(7849),加起来一共73353字节。
注:对于上面例子中前几个chunk的大小都是8188,可能是因为:"1ffc" 4字节,""r"n"2字节,加上块尾一个""r"n"2字节一共8字节,因此一个chunk整体为8196,正好可能是发送端一次TCP发送的缓存大小。
HTTP Connections
最近初涉网络编程,分析了下HTTP协议,下面为第一篇关于HTTP连接控制方面的学习日志,主要参考RFC2616,肯定有疏漏之处,还望指出。
HTTP协议是位于传输层之上的应用层协议,其网络层基础通常是TCP协议。TCP协议是面向连接和流的,因此连接的状态和控制对于HTTP协议而言相当重要。同时,HTTP是基于报文的,因此如何确定报文长度也是协议中比较重要的一点。
Persistent Connections持久连接
目的
在使用持久连接前,HTTP协议规定为获取每个URL资源都需要使用单独的一个TCP连接,这增加了HTTP服务端的负载,引起互联网拥塞。例如内嵌图片以及其他类似数据的使用要求一个客户端在很短时间内向同一个服务端发起多个请求。
使用持久连接的优点:
减少TCP连接数量
在一个连接上实现HTTP请求和应答的流水,即允许客户端发出多个请求,而不必在接收到前一请求的应答后才发出下一请求,极大减少时间消耗
后续请求延迟减少,无需再在TCP握手上耗时
可以更加优雅地实现HTTP协议,由于持续连接的存在无需报告错误后无需关闭连接,因此客户端可使用最新的协议特性发出请求,如果接收到表示错误的应答,则换用更旧的语义。
总体描述
HTTP/1.1和之前版本的显著区别是HTTP/1.1默认使用持久连接。即,除非服务端在应答中明确指出,客户端应当假定服务端会维持一个持久连接,即使从服务端收到的应答是报告错误。
持
久连接对关闭TCP连接的行为提供信号量机制支持。这个信号量是在HTTP头中的Connection域设置,注意Client向Proxy发出请求时该
域可能被Proxy-Connection域替换。一旦close信号被表明,客户端绝不能再通过该连接发送更多的请求。
协商(Negotiation)
HTTP/1.1
服务端可以假定HTTP/1.1客户端会维持持久连接,除非请求中Connection域的值是"close".同样的,如果服务端打算在送出应答后立即
关闭连接,它应当在应答中包含同样的Connection域。(TCP连接关闭是双向的,此时TCP进入半关闭状态)
同样的,HTTP/1.1客户端可以期望连接是持久的,除非如前所述收到表示连接关闭的应答。当然,也可以主动发出一个包含Connection:close的请求以表明终止连接。
无论客户端还是服务端发出的报文包含Connection:close,则该请求均为连接上的最后一个请求(服务端发出此应答后关闭,因此不可能接收更多的请求)
报文传输长度
为保证持久性,连接上的报文都必须有一个自定义的报文传输长度(否则必须通过连接的关闭表示报文结束,因为TCP连接是面向流的),确定的规则按优先级由高到低排列如下:
报文传输长度指报文中出现的报文体的长度(即,不包括头长度,因为报文头的结束可通过连续两个CRLF确定)
1.任何绝不能包含报文体(如1xx,204,304)的应答消息总是以头域后的第一个空行结束,无视头中所有的entity类型域的设置,包括Content-Length域。
2.Transfer-Encoding域出现,其值为除"identify"以外的其他值,则用"chunked"传输编码方式确定传输长度,具体方式留待下篇分析。
3.Content-
Length域出现,且Transfer-Encoding域未出现(出现则忽略Content-Length域)。Content-Length域的值
为十进制数的字节序,如Content-Length:1234,则1、2、3、4是分别作为一个octet传输的,因此需要atoi转换成数值。
4.如果报文使用了"multipart/byteranges"的媒体类型,且没对传输长度做前面的指明,则这种自分割的媒体类型定义了传输长度。具体参见Range头域的说明。
5.服务端关闭连接(此方法不可用于客户端发出的请求报文,因为客户端关闭连接则使得服务端无法发送应答).
为保持和HTTP/1.0的兼容性,
包含报文体的HTTP/1.1请求必须包含合法的Content-Length头域,除非明确知道服务端是HTTP/1.1兼容的.如果请求包含消息体,
而没有Content-Length域,那么如果服务端无法确定消息长度时,它会返回400(无效请求),或者坚持获取合法Content-Length
而返回411(要求包含长度).
所有接收实体的HTTP/1.1应用程序必须接受"chunked"传输编码, 这样允许当报文长度无法预先确定时可以运用此机制获取报文长度.
报文不能同时包含Content-Length头域和非"identity" Transfer-Encoding.如果出现了, Content-Length域必须被忽略.
当Content-Length域在允许报文体的报文中存在时, 其域值必须严格等于消息体中的8比特字节.HTTP/1.1 user agent 必须在接收并检测到一个错误的长度时提醒用户.
以上方法中,最常见的还是使用Content-Length域表示报文体长度,Transfer-Encoding需要按格式解码才能还原出发送编码前的报文。
流水
支持持久连接的客户端可以流水发送请求,服务端必须按发送的顺序发送应答。
假定持久连接和连接后即可流水的客户端应当做好在第一次流水失败后重新尝试此连接。在这样的尝试中,在确定连接是持久的之前,客户端不能再流水。
客户端同样必须准备好在服务端送回所有相关应答前就关闭连接时重发请求。
不应流水non-idempotent方法
Proxy Servers
对于代理服务端而言,正确实现Connection头域指定的属性尤为重要。
代理服务端必须分立通告它的客户端和连接的原始服务端持久连接的属性,每个持久连接设置仅针对一个传输连接。
实践考量
超时值,服务端通常会为每个连接维护一个定时器,一旦某个连接不活跃超过一定时间值,服务端会关闭此连接。考虑到一个客户端可能通过代理服务端发出更多连接,代理服务端通常会将超时值设置得更高。
还有一些关于从异步关闭中恢复的讨论。
报文传输要求
使用TCP流控制来解决服务端临时负载过高问题,而不是简单的依赖客户端重连而关闭连接。
监视连接情况以获取错误状态消息
关于使用100(继续)状态码
100状态码用于客户端发送请求体之前测试是否可以发送该请求,对于Proxy,有以下要求:
1.如果代理服务端接收到包含Expect头域值为"100-continue"的请求, 而不明确知道下一跳服务不支持HTTP/1.1以上版本, 则它必须转发这个请求, 包括Expect头域.
2.如果代理知道下一跳服务端为HTTP/1.0或者更低版本, 则它不能转发此请求, 且必须以407应答客户端.
3.如果明确知道发出请求的客户端版本为HTTP/1.0或者更低,则代理服务端绝不能转发100应答,这条规则凌驾于转发1xx应答的一般准则.
Connection头域说明
BNF文法:
Connection = "Connection" ":" 1#(connection-token)
connection-token = token
Connection头域中的token用于指定对于特定连接有意义的选项,因此proxy在转发前要扫描此域,从头中去除和token同名的域。例如Connection:Range,则要去掉Range域。
HTTP/1.1定义了close这个token,发送者用此token表示在完成这个报文所属请求/应答的收发后连接将关闭。
一些讨论:
http://topic.csdn.net/t/20061214/22/5231907.html
|