X Window System Protocol
X Version 11, Release 6.7
Protocol是X11众多文档规范里最重要的一份,它规定了X的Server和Client之间的通信规则。
X Window System的重要特点之一是它具有网络通透性,client与server的连接是“松散”的,图形设备和输入输出设备在server端,而应用程序在client端,通过网络,client向server发送请求,比如创建窗口、读取属性、绘制文本等等,server向client发送回复以及事件通知。
Server和client并不关心对方的实现,它们各自只需要向通信协议负责即可。只要符合通信协议,它们甚至可以是由不同的编程语言来书写的。这样的灵活性和多样性是X的特点之一。
Protocol这篇文档规定了X11通信的格式以及内容。
1 协议格式
请求格式(Request Format)
每一个请求由请求头和请求体组成。
在每一个请求的最前面,有一个4字节的请求头,其中第一个字节是8位的主操作码(major opcode),接下来的两字节是16位的请求长度(这个长度值也包括头在内),最后的一个字节在本协议里暂时不用。
跟在头后面的是请求体,格式是不固定的,依请求种类而不同。如果实际的请求体内容长度小于头里说明的长度,不必用0来补位。
++++++++ | ++++++++ ++++++++ | ++++++++ | ++++++++++++++++++++++++
m-opcode | length of request | blank | request content ...
++++++++ | ++++++++ ++++++++ | ++++++++ | ++++++++++++++++++++++++
(每个"+"代表一位)
在本核心协议中,主操作码在0~127之间。128~255保留做扩展用。很多情况下,扩展的请求里可能包含多个操作码,这时请求的结构会有一些改变。比如,一种典型的用法是,在头的第二个字节里存放一个副操作码(minor opcode),把长度值移到后面两个字节去。
响应格式(Reply Format)
每一个响应由三部分组成:响应头,响应体以及扩展部分
响应头由4字节的组成,并没有特殊的标识,这4个字节中的内容是整个响应的长度。
接下来是32个字节的响应体。
如果响应的内容比较长,超过了32字节,可继续在响应体后接续,这是响应体的扩展部分,长度向字节对齐。
在响应体和扩展部分中如果有空位的话,server并不保证把空位置0。
在响应的内容中还包含所对应请求的序列号。(但协议没有指明哪几个字节是这一内容。)
错误报告格式(Error Format)
每一个错误报告的长度是固定的32字节。其中包含一个字节的错误码(error code)。本规范中错误码在0~127之间,128~255保留做扩展用。
在错误报告中还包含所对应请求的主操作码、副操作码和序列号。
对于以下几种资源方面的错误,报告中还包含资源ID:
Colormap, Cursor, Drawable, Font, GContext, IDChoice, Pixmap, Window.
如果出错的是atom,原atom也会被包含在报告中;如果出错的是value,其实会被包含。其它类型的错误报告不再包含更多的信息。
同样,报告中如果有空位的话,server并不保证把空位置0。
事件通知格式(Event Format)
事件通知的长度也是固定的32字节。
其中前8位是类型码。在这8位中,会有一位用于标识该事件是否是由客户端发起的,即如果这一事件是因响应客户端的SendEvent请求而发出的,这将被置位。
类型码的64~127段保留用于扩展。
除KeymapNotify之外的所有事件通知中还将包含此前最后一个来自client的请求序列号。
2 建立连接
X的server与client之间的连接是通过网络进行的,面向字节流。它们之间的通信不依赖特定的平台,也不依赖特定网络。
2.1 连接初始化
首先,client需要向server发送一个字节来指明字节序,这个字节的内容称为“字节序位”(byte-order byte)。这个字节是一个ASCII字符,只可能是两个值,要么是"B"(8进制102),要么是"l"(8进制154)。前者表明在接下来的通信中,如果数据大于一个字节的话,高字节在前,低字节在后;后者相反,表明低字节在前,高字节在后。Client和server双方都需要遵守这一协定。
在字节序位之后,client发送以下内容:
协议主版本号(protocol-major-version) - 16位无符号数(CARD16)
协议副版本号(protocol-minor-version) - 16位无符号数(CARD16)
认证协议名(authorization-protocol-name) - 字符串(STRING8)
认证协议数据(authorization-protocol-data) - 字符串(STRING8)
认证协议名/数据是出于安全性考虑,client可以请求server使用某种安全认证协议,但这并不是必须的,如果server不支持client所申请的认证协议,只需简单地简略即可。认证协议数据也可为空。
2.2 服务器的响应
Client发送完以上初始化数据以后,server应该给出如下的响应:
success: {Failed, Success, Authenticate}
Failed表明连接失败,接下来会收到以下附加信息:
protocol-major-version: CARD16
protocol-minor-version: CARD16
reason: STRING8
Authenticate表明连接因认证原因而失败,接下来会收到以下附加信息:
reason: STRING8
Success表明连接已成功建立,并附带以下信息:
protocol-major-version: CARD16
protocol-minor-version: CARD16
vendor: STRING8
release-number: CARD32
resource-id-base, resource-id-mask: CARD32
image-byte-order: {LSBFirst, MSBFirst}
bitmap-scanline-unit: {8, 16, 32}
bitmap-scanline-pad: {8, 16, 32}
bitmap-bit-order: {LeastSignificant, MostSignificant}
pixmap-formats: LISTofFORMAT
roots: LISTofSCREEN
motion-buffer-size: CARD32
maximum-request-length: CARD16
min-keycode, max-keycode: KEYCODE
其中出现4种特殊的数据类型:FORMAT, SCREEN, DEPTH, VISUALTYPE解释如下:
FORMAT:
[depth: CARD8,
bits-per-pixel: {1, 4, 8, 16, 24, 32}
scanline-pad: {8, 16, 32}]
SCREEN:
[root: WINDOW
width-in-pixels, height-in-pixels: CARD16
width-in-millimeters, height-in-millimeters: CARD16
allowed-depths: LISTofDEPTH
root-depth: CARD8
root-visual: VISUALID
default-colormap: COLORMAP
white-pixel, black-pixel: CARD32
min-installed-maps, max-installed-maps: CARD16
backing-stores: {Never, WhenMapped, Always}
save-unders: BOOL
current-input-masks: SETofEVENT]
DEPTH:
[depth: CARD8
visuals: LISTofVISUALTYPE]
VISUALTYPE:
[visual-id: VISUALID
class: {StaticGray, StaticColor, TrueColor, GrayScale,
PseudoColor, DirectColor}
red-mask, green-mask, blue-mask: CARD32
bits-per-rgb-value: CARD8
colormap-entries: CARD16]
2.3 Server信息
如果连接建立成功的话,server在Success之后返回给client的附加信息表明server的能力。
协议号:
协议号即server支持的协议版本,目前最新为11。版本号具有向下兼容的特性,即高版本兼容低版本。一般来说,server会返回与client的请求相同的版本号,但由于向下兼容性,版本号不同的情况也时有发生,client的版本号可以比server小。
Resource-id-mask包含一个连续的比特序列(至少18位)。Client为WINDOW, PIXMAP, CURSOR, FONT, GCONTEXT和COLORMAP这些资源申请ID时,需要以它做为掩码,即ID值需要与resource-id-mask做“与”运算;然后再与resource-id-base做“或”运算,其结果才是有效的ID值。资源ID的前三位总是0;资源ID的分布并不保证是线性依次分配的,一旦一个ID被释放,即可马上重用。
各种不同种类的资源ID,它们的分配空间是相同的,但不同的资源ID不需要处于不同的段,可以混在一起的,因为它们最终是被不同种类的句柄来索引,不会因为分配空间相同而造成混乱。所以,一个ID不但是在所有同类资源中是唯一的,在所有种类的资源中也必须是唯一的。
在连接的初始化时,client可以指定字节序,server要遵守client的“建议”;但是对于图像数据来说就是另外一回事,图像数据的字节序是由server在image-byte-order中指定的。
接下来的几行内容是关于位图(bitmap)的。位图是以扫描线(scanline)的形式描述的。扫描线的长度通常是要向若干比特数对齐的,不足的用0补位,这个数字即在bitmap-scanline-pad定义,是固定的。扫描线也有自己的最小构成单位,即bitmap-scanline-unit,bitmap-scanline-unit小于或等于bitmap-scanline-pad。在扫描线的每个最小单位(unit)里,存放的数据是高字节在前还是低字节在前,由bitmap-bit-order规定。
如果一个像素图(pixmap)是以XY格式描述的,它的每一个“小块”(plane)就是一个位图,每个plane内的位图数据都是高字节数据在前,低字节在后;在各plane之间不需加0填充。
Pixmap-formats数据是一个列表,列表的每一项对应于一种色深,描述的是在相应色深下像素图的Z格式。在Z格式下,像素是以扫描线的形式表示的,一个像素即是一条从左到右的扫描线。Bits-per-pixel定义了一个像素有多少位组成。理论上说,一个像素的位数应该与深度相同,但由于需要向字节对齐,bits-per-pixel可能会比depth更大,如果是这样的话,pixel的低位是真实数据,高位不用,也无需置0。
指针设备如何在屏幕上及屏幕间移动是由server的实现来决定的,本协议不做要求。
Server可以保存指针设备移动的历史记录并发出MotionNotify事件。Client可以通过GetMotionEvents请求来取得这些历史记录。Motion-buffer-size指的就是用来存储移动历史记录的缓冲区的大小。
Maximum-request-length表明的是server可以接受的请求的最大长度,单位是4字节。也就是说,maximum-request-length是client的请求数据头中“长度”的最大值,如果超过了这个值,就会产生一个Length错误,server将乎略这个请求。
Maximum-request-length不会太小,至少是4096,也就是说,server能处理的请求长度至少要达到4096*4=16384字节。
Min-keycode和maz-keycode定义了server支持的最小和最大的键值,最小不能小于8,最大不能大于255,在这个区间内,也并不是所有的值都有对应的键,可以有空位。
与屏幕有关的信息有:
Allowed-depth指明像素图(pixmap)和窗口支持哪些深度。对于pixmap,任何一种深度都要支持;而对于窗口来说,只要支持那些有对应“视”(visual)类型的就可以了。深度为1的像素图一定要支持,但深度为1的窗口可以不支持。
Root-depth和root-visual指明根窗口的深度和视类型;Width-in-pixels和height-in-pixels指明根窗口的大小,这个值是固定的,不能改。Width-in-millimeters和height-in-millimeters指明了根窗口的物理尺寸大小。
Default-colormap是根窗口使用的初始色彩表,也是色彩的最低配置。如果client在色彩方面的请求非常低的话,将使用这一配置。
Black-pixel和white-pixel可以用来实现黑白两色的应用,它们分别是黑白两色的像素值。它们一定会存在于default-colormap色彩表中。在有些情况下,黑白两色可能使用比较多,而且比较典型,所以把它们单独列出来,比如根窗口的边缘就是由black-pixel指定的黑色填充的,根窗口的默认背景也是由这两种色彩来组成的。
Min-installed-maps指的是server最少可以保证能够同时安装的色彩表数量;Max-installed-maps是与之相应的最大值,但并不保证每一次都能安装这么多的数量,还要看内存空间的情况,只是可能的最大值,可以达不到。
Backing-stores可以取三个值:Never, WhenMapped, Always。表明server在什么时候会在后台缓存窗口的图像。可能永远也不缓存,可能只在map的时候缓存,也可能总是会缓存。即使缓存的话,也要视内存的占用情况而定,并不能保证每次都能缓存成功。
视觉(visual)信息
前面提到的有关颜色的数据都是有关颜色衡量尺度的,比如色深等。这一部分(视觉/visual)是有关颜色与人的感观之间关系的。这些内存存放在VISUALTYPE信息下面。
Class的几种类型{StaticGray, StaticColor, TrueColor, GrayScale, PseudoColor, DirectColor}定义的是色彩描述的类型,
比如,我要监听sd卡的目录创建事件,新建一个类SDCardListener继承FileObserver:
Java代码
1. import android.os.FileObserver;
2. import android.util.Log;
3.
4. /**
5. * sd卡上的目录创建监听器
6. * @author chroya
7. *
8. */
9. public class SDCardListener extends FileObserver {
10.
11. public SDCardListener(String path) {
12. /*
13. * 这种构造方法是默认监听所有事件的,如果使用super(String,int)这种构造方法,
14. * 则int参数是要监听的事件类型.
15. */
16. super(path);
17. }
18.
19. @Override
20. public void onEvent(int event, String path) {
21. switch(event) {
22. case FileObserver.ALL_EVENTS:
23. Log.d("all", "path:"+ path);
24. break;
25. case FileObserver.CREATE:
26. Log.d("Create", "path:"+ path);
27. break;
28. }
29. }
30. }
import android.os.FileObserver;
import android.util.Log;
/**
* sd卡上的目录创建监听器
* @author chroya
*
*/
public class SDCardListener extends FileObserver {
public SDCardListener(String path) {
/*
* 这种构造方法是默认监听所有事件的,如果使用super(String,int)这种构造方法,
* 则int参数是要监听的事件类型.
*/
super(path);
}
@Override
public void onEvent(int event, String path) {
switch(event) {
case FileObserver.ALL_EVENTS:
Log.d("all", "path:"+ path);
break;
case FileObserver.CREATE:
Log.d("Create", "path:"+ path);
break;
}
}
}
onEvent是回调,系统监听到事件后会触发此事件,参数event就是上面所说的事件类型,根据类型,我们可以做相应的处理,参数path就是触发事件的目录。
然后使用它:
Java代码
1. SDCardListener listener = new SDCardListener("/sdcard");
2. //开始监听
3. listener.startWatching();
4. /*
5. * 在这里做一些操作,比如创建目录什么的
6. */
7. //最后停止监听
8. listener.stopWatching();
SDCardListener listener = new SDCardListener("/sdcard");
//开始监听
listener.startWatching();
/*
* 在这里做一些操作,比如创建目录什么的
*/
//最后停止监听
listener.stopWatching();
如果要在onEvent中做较多操作,最好用线程去做 ,以免因为阻塞接收不到后面的事件。
最后补充一点,比较重要 :
FileObserver对象必须保持一个引用,确保不被垃圾收集器回收掉,否则就不会触发事件了。
目前Android操作系统的手机大部分支持WIFI,GSM,3G网络通信,但是每次链接到网络时只能选择一种链接方式,
比如运营商定制的,还必须要求特定的网络环境(CMWAP,CTWAP等)如果要切换网络还需要先关闭现有的网络,
然后再启动新的网络,这个转换过程还需要一定的时间,
可能程序这时还需要知道心的网络是否链接成功后自动登录到新的网络服务器中,
那怎么知道几时链接成功呢?这个时间需要多久呢?
也许用一个线程去监听网络状态是否链接成功;
我们可以用另外一种方法,PhoneStateListener
没错,你没看错,就是用PhoneStateListener。
很多应用PhoneStateListener都是监听来电去电,PhoneStateListener还可以监听网络断开、
正在连接和连接成功。
final TelephonyManager mTelephonyMgr = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); mTelephonyMgr.listen(new PhoneStateListener() { @Override public void onDataConnectionStateChanged(int state) { switch (state) { case TelephonyManager.DATA_DISCONNECTED:// 网络断开 break; case TelephonyManager.DATA_CONNECTING:// 网络正在连接 break; case TelephonyManager.DATA_CONNECTED:// 网络连接上 break; } } }, PhoneStateListener.LISTEN_DATA_CONNECTION_STATE);
我们只要重载onDataConnectionStateChanged方法,根据state判断做相应的处理。