??xml version="1.0" encoding="utf-8" standalone="yes"?> Q 什么是MIMEQ什么是MIME邮gQ?
A MIME, 全称?#8220;Multipurpose Internet Mail Extensions”, 比较切的中文名UCؓ“多用途互联网邮g扩展”。它是当前广泛应用的一U电子邮件技术规范,基本内容定义于RFC 2045-2049?
自然QMIME邮g是W合MIME规范的电子邮Ӟ或者说ҎMIME规范~码而成的电子邮件?
在MIME出台之前Q用RFC
822只能发送基本的ASCII码文本信息,邮g内容如果要包括二q制文g、声韛_动画{,实现h非常困难。MIME提供了一U可以在邮g中附加多U不
同编码文件的ҎQI补了原来的信息格式的不。实际上不仅仅是邮g~码Q现在MIMEl成为HTTP协议标准的一个部分?
下面丑և个MIME邮g的例子,让我们先对MIME~码的格式有个直观的印象。例1是最单的Q只带纯文本
正文Q基本上是RFC
822格式Q例2复杂一些,包含U文本和文本正文;?是最复杂的,包含U文本正文、超文本正文、内嵌资源和文g附g。其中,行号和行号后的空格是Z
分析方便而另外加的,“... ... ... ...”表示此处省略了大D늼码?
?
?
?
Q 在开始研IMIME邮g的时候,如何得到q样的源码?
A
一些功能比较完善的邮g客户端YӞ如微软的Outlook
ExpressQ国产的Foxmail{,都提供了查看和保存邮件源?原始信息)的功能。在Foxmail中,选择邮g列表右键菜单?#8220;原始信息”q行
查看Q主菜单?#8220;文g-导出”q行保存。在Outlook
Express中,对应的操作分别是“属?#8221;?#8220;另存?#8221;。所保存?eml文gQ可以调用这些程序打开?
Q 请介l一下MIME邮g的组成?
A
M来说QMIME消息由消息头和消息体两大部分l成。现在我们关注的是MIME邮gQ因此在以下的讨Z姑且U?#8220;消息”?#8220;邮g”。在上面的例子中Q例
1?-6行,???行,??-18行,是邮件头Q例1??行,??0?2行,??0?128行,是邮件体。邮件头与邮件体?
间以Iq行分隔Q如?的第7行,?的第9行,?的第19行。邮件头中不允许出现I。有一些邮件不能被邮g客户端Y件识别,昄的是原始码,是
因ؓ首行是空行?
邮g头包含了发g人、收件h、主题、时间、MIME版本、邮件内容的cd{重要信息。每条信息称Z个域Q?
由域名后?#8220;:
”和信息内Ҏ成,可以是一行,较长的也可以占用多行。域的首行必?#8220;头”写,卛_边不能有I白字符Q空格和制表W)Q箋行则必须以空白字W打_且第
一个空白字W不是信息本w固有的Q解码时要过滤掉。如??-8行,??-5行,13-14行,分别属于一个域?
邮g体包含邮件的内容Q它的类型由邮g头的“Content-Type”域指出。常见的单类型有text/plain(U文?和text/html(文??
?和例3中出现的multipartcdQ是MIME邮g的精髓。邮件体被分为多个段Q每个段又包含段头和
D体两部分,q两部分之间也以I分隔。常见的multipartcd有三U:multipart/mixed,
multipart/related和multipart/alternative。从它们的名Uͼ不难推知q些cd各自的含义和用处。它们之间的层次?
pd归纳Z图所C:
可以看出Q如果在邮g中要d附gQ必d义multipart/mixedD;如果存在内嵌资源Q至要定义
multipart/relatedD;如果U文本与文本共存,臛_要定义multipart/alternativeDc什么是“臛_”QD个例?
_如果只有U文本与文本正文,那么在邮件头中将cd扩大化,定义为multipart/relatedQ甚至multipart/mixedQ都是允
许的?
multipart诸类型的共同特征是,在段头指?#8220;boundary”参数字符ԌD体内的每个子段以此
串定界。所有的子段都以“--”+boundary行开始,父段则以“--”+boundary+“--”行结束。段与段之间也以I分隔。在邮g体是
multipartcd的情况下Q邮件体的开始部?W一?#8220;--”+boundary行之?可以有一些附加的文本行,相当于注释,解码时应忽略。段?
也可以有一些附加的文本行,不会昄出来Q如果有兴趣Q不妨验证一下?
l合boundary定界和multipart层次关系图,我们分析一下例2和例3的邮件体层次与段嵌套关系?
在例2中,10-12行是附加文本行,13-82行是multipart/alternative型的D,包含两个子段Q?3-30行是U文本正文,32-79行是文本正文?
在例3中,20-21行是附加文本行,22-3127行是multipart/mixed型的D,包含3个子
D:22-171行是multipart/relatedD,173-1688行与1690-3125行是两个附g。multipart/related
D又包含两个子段Q?7-61行是multipart/alternativeD,63-169行是一个内嵌资?囄)?
multipart/alternativeD又包含两个子段Q?1-48行是U文本正文,40-59行是文本正文?
?只有U文本正文,实际上属于multipart层次关系图中的一个特D情c如果非要避qQ写成下面的形式Q也是完全符合MIME_的?
Q 在邮件头和段头中Q有哪一些常见的域?
A 在邮件头中,有很多从RFC 822沿用的域名,MIME也增加了一些。常见的标准域名和含义如?
1 Date: Thu, 18 Apr 2002 09:32:45 +0800
2 From: <bhw98@sina.com>
3 To: <bhwang@jlonline.com>
4 Subject: Test
5 Mime-Version: 1.0
6 Content-Type: text/plain; charset="iso-8859-1"
7
8 This is a simple mail.
9 1 From: "bhw98" <bhw98@sina.com>
2 Reply-To: bhw98@sina.com
3 To: <bluesky7810@163.com>
4 Subject: Re: help
5 X-Mailer: Foxmail 4.2 [cn]
6 Mime-Version: 1.0
7 Content-Type: multipart/alternative;
8 boundary="=====002_Dragon307572345230_====="
9
10
11 This is a multi-part message in MIME format.
12
13 --=====002_Dragon307572345230_=====
14 Content-Type: text/plain; charset="GB2312"
15 Content-Transfer-Encoding: quoted-printable
16
17 bluesky7810=A3=AC=C4=FA=BA=C3=A3=A1
18
19 =A1=A1=A1=A1=D4=DA=CF=C2=C6=AA=D7=EE=BA=F3=BF=C9=D2=D4=CF=C2=D4=D8=B0=A1=A3=AC=C4=E3
... ... ... ...
30 =A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A1=A12003-04-07
31
32 --=====002_Dragon307572345230_=====
33 Content-Type: text/html; charset="GB2312"
34 Content-Transfer-Encoding: quoted-printable
35
36 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
37 <HTML><HEAD>
38 <META content=3D"text/html; charset=3Dgb2312"=
39 http-equiv=3DContent-Type>
40 <META content=3D"MSHTML 5.00.2920.0" name=3DGENERATOR>
... ... ... ...
79 </HTML>
80
81 --=====002_Dragon307572345230_=====--
82 1 Return-Path: <bluesky7810@163.com>
2 Delivered-To: bhw98@sina.com
3 Received: (qmail 75513 invoked by alias); 20 May 2002 02:19:53 -0000
4 Received: from unknown (HELO bluesky) (61.155.118.135)
5 by 202.106.187.143 with SMTP; 20 May 2002 02:19:53 -0000
6 Message-ID: <007f01c3111c$742fec00$0100007f@bluesky>
7 From: "=?gb2312?B?wLbAtrXEzOwNCg==?=" <bluesky7810@163.com>
8 To: "bhw98" <bhw98@sina.com>
9 Cc: <bhwang@jlonline.com>
10 Subject: =?gb2312?B?ztK1xLbgtK6/2rPM0PI=?=
11 Date: Sat, 20 May 2002 10:03:36 +0800
12 MIME-Version: 1.0
13 Content-Type: multipart/mixed;
14 boundary="----=_NextPart_000_007A_01C3115F.80DFC5E0"
15 X-Priority: 3
16 X-MSMail-Priority: Normal
17 X-Mailer: Microsoft Outlook Express 5.00.2919.6700
18 X-MimeOLE: Produced By Microsoft MimeOLE V5.00.2919.6700
19
20 This is a multi-part message in MIME format.
21
22 ------=_NextPart_000_007A_01C3115F.80DFC5E0
23 Content-Type: multipart/related; type="multipart/alternative";
24 boundary="----=_NextPart_001_007B_01C3115F.80DFC5E0"
25
26
27 ------=_NextPart_001_007B_01C3115F.80DFC5E0
28 Content-Type: multipart/alternative;
29 boundary="----=_NextPart_002_007C_01C3115F.80DFC5E0"
30
31 ------=_NextPart_002_007C_01C3115F.80DFC5E0
32 Content-Type: text/plain; charset="gb2312"
33 Content-Transfer-Encoding: quoted-printable
34
35 bhw98, =C4=E3=BA=C3!
36 =D5=E2=CA=C7=CE=D2=D0=B4=B5=C4=B6=E0=B4=AE=BF=DA=CD=A8=D0=C5=B5=C4=B3=CC=D0=
37 =F2, =C7=EB=D6=B8=BD=CC!
38
39
40 ------=_NextPart_002_007C_01C3115F.80DFC5E0
41 Content-Type: text/html; charset="gb2312"
42 Content-Transfer-Encoding: quoted-printable
43
44 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
45 <HTML><HEAD><TITLE>=C7=E7=C0=CA</TITLE>
46 <META content=3D"text/html; charset=3Dgb2312" http-equiv=3DContent-Type>
47 <STYLE>BODY {
48 COLOR: #0033cc; FONT-FAMILY: =CB=CE=CC=E5, Arial, Helvetica; FONT-SIZE: =
49 9pt; MARGIN-LEFT: 10px; MARGIN-TOP: 25px
50 }
51 </STYLE>
52 <META content=3D"MSHTML 5.00.2920.0" name=3DGENERATOR></HEAD>
53 <BODY background=3Dcid:007901c3111c$72b978a0$0100007f@bluesky =
54 bgColor=3D#ffffff>
55 <DIV>
56 <DIV>bhw98, =C4=E3=BA=C3!</DIV>
57 <P>=D5=E2=CA=C7=CE=D2=D0=B4=B5=C4=B6=E0=B4=AE=BF=DA=CD=A8=D0=C5=B5=C4=B3=CC=
58 =D0=F2, =C7=EB=D6=B8=BD=CC!</P></DIV>
59 <P> </P></BODY></HTML>
60
61 ------=_NextPart_002_007C_01C3115F.80DFC5E0--
62
63 ------=_NextPart_001_007B_01C3115F.80DFC5E0
64 Content-Type: image/jpeg; name="=?gb2312?B?x+fAyrGzvrAuSlBH?="
65 Content-Transfer-Encoding: base64
66 Content-ID: <007901c3111c$72b978a0$0100007f@bluesky>
67
68 /9j/4AAQSkZJRgABAgEASABIAAD/7QVoUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA
69 AQBIAAAAAQABOEJJTQPzAAAAAAAIAAAAAAAAAAA4QklNBAoAAAAAAAEAADhCSU0nEAAAAAAACgAB
70 AAAAAAAAAAI4QklNA/UAAAAAAEgAL2ZmAAEAbGZmAAYAAAAAAAEAL2ZmAAEAoZmaAAYAAAAAAAEA
... ... ... ...
169 RxVw98Vawq12xQ44q0cKtHFDWKGsKt4EtiuKt4q//9k=
170
171 ------=_NextPart_001_007B_01C3115F.80DFC5E0--
172
173 ------=_NextPart_000_007A_01C3115F.80DFC5E0
174 Content-Type: application/msword; name="readme.doc"
175 Content-Transfer-Encoding: base64
176 Content-Disposition: attachment; filename="readme.doc"
177
178 0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAAAAAABAAAAJgAAAAAAAAAA
179 EAAAKAAAAAEAAAD+////AAAAACUAAAD/////////////////////////////////////////////
180 ////////////////////////////////////////////////////////////////////////////
... ... ... ...
1688 AAAAAAAAAAAAAAAAAAA=
1689
1690 ------=_NextPart_000_007A_01C3115F.80DFC5E0
1691 Content-Type: application/x-zip-compressed;
1692 name="=?gb2312?B?tuC0rr/azajQxbXE1LTC6y56aXA=?="
1693 Content-Transfer-Encoding: base64
1694 Content-Disposition: attachment;
1695 filename="=?gb2312?B?tuC0rr/azajQxbXE1LTC6y56aXA=?="
1696
1697 UEsDBBQAAAAIAFKAoi7qOMOvLw0AAABWAAAUAAAAtuC0rr/azajQxbXE1LTC6y5kb2PtXHtwVNUZ
1698 /+4+kk3IQoAkBkRYQkSgbrKb7IYNEMwmm6ckG0jCI0boZneTbJJ9sNlAEsdOtFqd8Z846tQ6PhB1
1699 hrZTJoK0Vhgf1aGt4rMy6D8tdugfTjuOpcBIR9j+vvsIy4YkRNTRen87v/ud53cee+6557vn7L73
... ... ... ...
3125 zajQxbXE1LTC6y5kb2NQSwUGAAAAAAEAAQBCAAAAYQ0AAA==
3126
3127 ------=_NextPart_000_007A_01C3115F.80DFC5E0--
3128+------------------------- multipart/mixed ----------------------------+
| |
| +----------------- multipart/related ------------------+ |
| | | |
| | +----- multipart/alternative ------+ +----------+ | +------+ |
| | | | | 内嵌资源 | | | 附g | |
| | | +------------+ +------------+ | +----------+ | +------+ |
| | | | U文本正?| | 文本正?| | | |
| | | +------------+ +------------+ | +----------+ | +------+ |
| | | | | 内嵌资源 | | | 附g | |
| | +----------------------------------+ +----------+ | +------+ |
| | | |
| +------------------------------------------------------+ |
| |
+----------------------------------------------------------------------+
Date: Thu, 18 Apr 2002 09:32:45 +0800
From: <bhw98@sina.com>
To: <bhwang@jlonline.com>
Subject: Test
Mime-Version: 1.0
Content-Type: multipart/alternative; boundary="{[(^_^)]}"
--{[(^_^)]}
Content-Type: text/plain; charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
This is a simple mail.
--{[(^_^)]}--
域名
含义
d?
Received
传输路径
各邮g服务?
Return-Path
回复地址
目标邮g服务?
Delivered-To
发送地址
目标邮g服务?
Reply-To
回复地址
邮g的创?
From
发g人地址
邮g的创?
To
收g人地址
邮g的创?
Cc
抄送地址
邮g的创?
Bcc
暗送地址
邮g的创?
Date
日期和时?/td>
邮g的创?
Subject
主题
邮g的创?
Message-ID
消息ID
邮g的创?
MIME-Version
MIME版本
邮g的创?
Content-Type
内容的类?/td>
邮g的创?
Content-Transfer-Encoding
内容的传输编码方?/td>
邮g的创?
非标准的、自定义域名都以X-开_例如X-Mailer, X-MSMail-Priority{,通常在接收和发送邮件的是同一E序时才能理解它们的意义?
在段头中Q大致有如下一些域
域名 | 含义 |
Content-Type | D体的类? |
Content-Transfer-Encoding | D体的传输编码方? |
Content-Disposition | D体的安排方? |
Content-ID | D体的ID |
Content-Location | D体的位|?路径) |
Content-Base | D体的基位置 |
有的域除了g外,q带有参数。g参数、参C参数之间?#8220;;”分隔。参数名与参数g间以“=”分隔。如 ??8-29行,Content-Type域的gؓ“multipart/alternative”Q此外有一个参数boundaryQgؓ"--- -=_NextPart_002_007C_01C3115F.80DFC5E0"。又如例3的第176行,Content-Disposition域的 gؓ“attachment”Q此外有一个参数filenameQgؓ“readme.doc”?
Q Content-Type以及它们的参数有哪些形式Q?
A Content-Type都是“ȝ?子类?#8221;的Ş式。主cd有text, image, audio, video, application, multipart, message{,分别表示文本、图片、音频、视频、应用、分Dc消息等。每个主cd都可能有多个子类型,如textcd包含plain, html, xml, css{子cd。以X-开头的ȝ型和子类型,同样表示自定义的cdQ未向IANA正式注册Q但大多已经U定成俗了。如application/x- zip-compressed是ZIP文gcd。在Windows中,注册表的“HKEY_CLASSES_ROOT\MIME\Database\ Content Type”内列举了除multipart之外大部分已知的Content-Type?
关于参数的Ş式,RFC里有很多补充规定Q有的允许带几个参数Q较为常见的?
ȝ?/td> | 参数?/td> | 含义 |
text | charset | 字符? |
image | name | 名称 |
application | name | 名称 |
multipart | boundary | 边界 |
其中字符集也能在Windows注册表的“HKEY_CLASSES_ROOT\MIME\Database\Charset”内见到?
Q Content-Transfer-Encoding有哪些?有什么特点?
A Content-Transfer-Encoding共有Base64, Quoted-printable, 7bit, 8bit, Binary{几U。其?bit是缺省的~码方式。电子邮件源码最初设计ؓ全部是可打印的ASCII码的形式。非ASCII码的文本或数据要~码成要? 的格式,如上面的三个例子。Base64, Quoted-Printable是在非英语国家用最q的编码方式。Binary方式只具有象征意义,而没有Q何实用h倹{?
Base64输入的字符串或一D|据编码成只含有{'A'-'Z', 'a'-'z', '0'-'9', '+', '/'}q?4个字W的Ԍ'='用于填充。其~码的方法是Q将输入数据每ơ取6 bitQ用? bit的?0-63)作ؓ索引L表,输出相应字符。这P?个字节将~码?个字W?3×8 → 4×6)Q不?个字W的?='填充。有的场合,?#8220;=?charset?B?xxxxxxxx?=”表示xxxxxxxx是Base64~码Q且原文 的字W集是charset。如?W??=?gb2312?B?wLbAtrXEzOwNCg==?="是由体中?#8220;蓝蓝的天”~码而成的。在D体? 则直接编码,适当时机换行QMIME每行最?6个字W。如??697-3125行,是一个ZIP文g的Base64~码?
Quoted-printableҎ输入的字W串或字节范围进行编码,若是不需~码的字W,直接输出Q若 需要编码,则先输出'='Q后面跟着?个字W表C的十六q制字节倹{有的场合,?#8220;=?charset?Q?xxxxxxxx?=”表示 xxxxxxxx是Quoted-printable~码Q且原文的字W集是charset。在D体内则直接~码Q适当时机换行Q换行前额外输出一?= '。如??4-59行,是HTML文本的Quoted-printable~码。其中第45?#8220;=C7=E7=C0=CA”原文?#8220;晴朗”Q因? “?#8221;的GB2312码是C7E7Q?#8220;?#8221;的GB2312码是C0CA。第48?3?7行末֏有孤雉?='Q表C是由~码造成的Y回RQ而非 原文固有的?
q年来,国内多数邮g服务器已l支?bit方式Q因此只在国内传输的邮gQ特别是在邮件头中,可直接?bit~码Q对汉字不做处理。如果邮件要出国Q还是老老实实地按Base64或Quoted-printable~码才行?
Q 什么是内嵌资源Q它有哪些Ş式?
A 内嵌资源也是MIME的一个发光点Q它能邮g内容变得生动zL、丰富多彩。可在邮件的multipart/related框架内定义一些与正文兌的图 片、动甅R声音甚至CSS样式和脚本的Dc通常在HTML正文内,使用链接与内嵌资源相联系。如在例3中,HTML正文53-54行,解码后ؓ
<BODY background=cid:007901c3111c$72b978a0$0100007f@bluesky bgColor=#ffffff>
它指出用一个Content-ID?07901c3111c$72b978a0$0100007f@bluesky的图片作?cid:xxxxxxxx也是一U超U链?。?4-169行恰好就是这样一个内嵌资源?
除了用Content-IDq行联系外,q有另外一U常用Ş式:用普通超U连接和Content-Location。例如:
在HTML正文中,
... ... ... ...
<IMG SRC="http://www.dangdang.com/images/all/anti_joyo_dm_book.gif">
... ... ... ...
<IMG SRC="http://www.dangdang.com/dd2001/getimage_small.asp?id=486341">
... ... ... ...
对应的内嵌资源ؓ
Content-Type: image/gif; name="anti_joyo_dm_book.gif"
Content-Transfer-Encoding: base64
Content-Location: http://www.dangdang.com/images/all/anti_joyo_dm_book.gif
... ... ... ...
Content-Type: application/octet-stream; name="getimage_small.asp?id=486341"
Content-Transfer-Encoding: base64
Content-Location: http://www.dangdang.com/dd2001/getimage_small.asp?id=486341
... ... ... ...
另外Q?
Content-Location: http://www.dangdang.com/images/all/anti_joyo_dm_book.gif
?
Content-Location: anti_joyo_dm_book.gif
Content-Base: http://www.dangdang.com/images/all/
是等效的?
Q 邮g病毒如何利用附g和内嵌资源传播?
A 有的邮g附g可能带有病毒Q容易理解。附件毕竟是文gQ也好预Ԍ不轻易打开是了。但内嵌资源是在览邮g内容时就要访问的Q若其中藏有病毒或恶意代码,你在不知不觉中就中招了。如前两q曾l在全球范围内流行的Nimda病毒Q功能性源码如下:
MIME-Version: 1.0
Content-Type: multipart/related;
type="multipart/alternative";
boundary="====_ABC1234567890DEF_===="
--====_ABC1234567890DEF_====
Content-Type: multipart/alternative;
boundary="====_ABC0987654321DEF_===="
--====_ABC0987654321DEF_====
Content-Type: text/html;
charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
<HTML><HEAD></HEAD><BODY bgColor=#ffffff>
<iframe src=cid:EA4DMGBP9p height=0 width=0>
</iframe></BODY></HTML>
--====_ABC0987654321DEF_====--
--====_ABC1234567890DEF_====
Content-Type: audio/x-wav; name="readme.exe"
Content-Transfer-Encoding: base64
Content-ID: <EA4DMGBP9p>
TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAA2AAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1v
ZGUuDQ0KJAAAAAAAAAA11CFvcbVPPHG1TzxxtU88E6pcPHW1TzyZqkU8dbVPPJmqSzxytU88cbVO
... ... ... ... ... ... ... ...
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
--====_ABC1234567890DEF_====
它将一个可执行文g作ؓ资源嵌入了框架型面Q却声明q段可执行代码是波Ş声音cd。由于当时微软的IE(版本5.0 及以?存在重大安全漏洞Q没有检查Content-Type与name的扩展名是否匚wQ于是就被轻易骗q了Q致使点选或打开邮g时自动运行了q个 “readme.exe”Q机器就感染上病毒。带毒的机器利用地址向别h发送带毒的邮gQ一传十Q十传百QNimda蠕虫大行光?
U观历史Q病毒刚出来时是厉害Q但没有M一U能够持l肆虐下厅RNimda如此QSARS亦当如此。曰Q?#8220;多难兴邦Q众志成?#8221;Q又曎ͼ“非典l将倒下Q城市精永?#8221;Q相信我们定能很快战?#8220;非典”!
病毒库升U是跟在新病毒屁股后q行的,不要q分依赖杀毒Y件。一个良好的习惯是关闭邮仉览功能,或者设定预览纯文本部分Q先查看邮g源码Q确信排除病毒嫌疑后再打开。对陌生人发来的带超文本正文的邮Ӟ其要当心。永q不要在邮g客户端Y件内直接打开附g?
Q 一些垃N仉取隐藏发件h的方式,如何q查它们来自哪里Q?
A 从上面的邮g头域名表中可以看出,邮g的创可以掌握大部分的域的内容,但Received{域由各U服务器自动dQ发件h是鞭长莫及。垃N件一? 采用了群发Y件发送,邮g头的From?发g人地址)可以L伪造,甚至写成收g人地址(收到了自己ƈ没有发过的垃NӞ气愤吧?)。查? Received?传输路径)铑֏以找到真正的出处。每个服务器d的Received语句都在邮g首,故最下面一个Received包含了发g人所 用的SMTP或HTTP服务器,及最初的|关外部IP地址?
Receive语句的基本格式是Qfrom A by B。A为发送方QB为接收方。例如:
Received: (qmail 45304 invoked from network); 4 May 2003 17:05:47 -0000
Received: from unknown (HELO bjapp9.163.net) (202.108.255.197)
by 202.106.182.244 with SMTP; 4 May 2003 17:05:47 -0000
Received: from localhost (localhost [127.0.0.1])
by bjapp9.163.net (Postfix) with SMTP id E1C761D84C631
for <bhw98@sina.com>; Mon, 5 May 2003 01:07:26 +0800 (CST)
Received: from fanyingxxxx@tom.com (unknown [211.99.162.194])
by bjapp9.163.net (Coremail) with SMTP id OgEAAM1ItT7MNaLC.1
for <bhw98@sina.com>; Mon, 05 May 2003 01:07:26 +0800 (CST)
从上面的例子中不隄出,该邮件的传输路径是:211.99.162.194 → bjapp9.163.net (Coremail 202.108.255.197?) → bjapp9.163.net (Postfix, 202.108.255.197?) → 202.106.182.244。恰好出C发g人邮fanyingxxxx@tom.comQ但多数情况不一定能列出来?
此例的localhost [127.0.0.1]Q意味着bjapp9.163.net上安装了邮g服务代理性质的Y件?
q有一U方式是对J2EE容器q行~码讄Q如果J2EE应用pȝq该容器,则会发生qQ而且指定容器配置不符合J2EE应用和容器分ȝ原则?/p>
在Java内部q算中,涉及到的所有字W串都会被{化ؓUTF-8~码来进行运。那么,在被Java转化之前Q字W串是什么样的字W集Q? JavaLҎ操作pȝ的默认编码字W集来决定字W串的初始编码,而且Javapȝ的输入和输出的都是采取操作系l的默认~码?/p>
? 此,如果能统一Javapȝ的输入、输出和操作pȝ3者的~码字符集合Q将能够使Javapȝ正确处理和显C汉字。这是处理Javapȝ汉字的一个原则, 但是在实际项目中Q能够正抓住和控制住Javapȝ的输入和输出部分是比较难的。J2EE中,׃涉及到外部浏览器和数据库{,所以中文问题ؕ码显得非 常突出?/p>
J2EE应用E序是运行在J2EE容器中。在q个pȝ中,输入途径有很多种Q一U是通过面表单打包成请求(requestQ? 发往服务器的Q第二种是通过数据库读入;q有W?U输入比较复杂,JSP在第一ơ运行时L被编译成ServletQJSP中常常包含中文字W,那么~译 使用javacӞJava根据默认的操作pȝ~码作ؓ初始~码。除非特别指定,如在Jbuilder/eclipse中可以指定默认的字符集?/p>
输出途径也有几种Q第一U是JSP面的输出。由于JSP面已经被编译成ServletQ那么在输出Ӟ也将Ҏ操作pȝ的默认编码来选择输出~码Q除非指定输出编码方式;q有输出途径是数据库Q将字符串输出到数据库?/p>
由此看来Q一个J2EEpȝ的输入输出是非常复杂Q而且是动态变化的Q而Java是跨q_q行的,在实际编译和q行中,都可能涉及到不同的操作系l,如果ȝJava自由Ҏ操作pȝ来决定输入输出的~码字符集,q将不可控制地出Cؕ码?/p>
正是׃Java的跨q_Ҏ,使得字符集问题必ȝ具体pȝ来统一解决Q所以在一个Java应用pȝ中,解决中文q的根本办法是明确指定整个应用pȝl一字符集?/strong>
指定l一字符集时Q到底是指定ISO8859_1 、GBKq是UTF-8呢?
Q?Q如l一指定为ISO8859_1Q因为目前大多数软g都是西方人编制的Q他们默认的字符集就是ISO8859_1Q包括操作系lLinux和数据库MySQL{。这P如果指定Jivel一~码为ISO8859_1Q那么就有下?个环节必L握:
开发和~译代码时指定字W集为ISO8859_1?/p>
q行操作pȝ的默认编码必LISO8859_1Q如Linux?/p>
在JSP头部声明Q?lt;%@ page contentType="text/html;charset=ISO8859_1" %>?/p>
Q?Q如果统一指定为GBK中文字符集,上述3个环节同样需要做刎ͼ不同的是只能q行在默认编码ؓGBK的操作系l,如中文Windows?/p>
l一~码为ISO8859_1和GBK虽然带来~制代码的方便,但是各自只能在相应的操作pȝ上运行。但是也破坏了Java跨^台运行的优越性,只在一定范围内行得通。例如,Z使得GBK~码在linux上运行,讄Linux~码为GBK?/p>
那么有没有一U除了应用系l以外不需要进行Q何附加设|的中文~码Ҏ解决Ҏ呢?
Java/J2EEpȝ的统一~码定义为UTF-8。UTF-8~码是一U兼Ҏ有语a的编码方式,惟一比较ȝ的就是要扑ֈ应用pȝ的所有出入口Q然后用UTF-8?#8220;l扎”它?/p>
一个J2EE应用pȝ需要做下列几步工作Q?/p>
作者:郎云鹏(dev2dev ID: hippiewolfQ?/p>
摘要Q虽然session机制在web应用E序中被采用已经很长旉了,但是仍然有很多h不清楚session机制的本质,以至不能正确的应用这一技术。本文将详细讨论session的工作机制ƈ且对在Java web application中应用session机制时常见的问题作出解答?/p>
目录Q?br />一、术语session
二、HTTP协议与状态保?/a>
三、理解cookie机制
四、理解session机制
五、理解javax.servlet.http.HttpSession
六、HttpSession常见问题
七、跨应用E序的session׃n
八、ȝ
参考文?/a>
一、术语session
在我的经验里Qsessionq个词被滥用的程度大概仅ơ于transactionQ更加有的是transaction与session在某些语境下的含义是相同的?/p>
sessionQ中文经常翻译ؓ会话Q其本来的含义是指有始有l的一pd动作/消息Q比如打电话时从拿v电话拨号到挂断电话这中间的一pdq程可以UCZ个session。有时候我们可以看到这L话“在一个浏览器会话期间Q?..”,q里的会话一词用的就是其本义Q是指从一个浏览器H口打开到关闭这个期间①。最混ؕ的是“用P客户端)在一ơ会话期间”这样一句话Q它可能指用L一pd动作Q一般情况下是同某个具体目的相关的一pd动作Q比如从d到选购商品到结账登样一个网上购物的q程Q有时候也被称Z个transactionQ,然而有时候也可能仅仅是指一ơ连接,也有可能是指含义①,其中的差别只能靠上下文来推断②?/p>
然而当session一词与|络协议相关联时Q它又往往隐含了“面向连接”和/或“保持状态”这样两个含义,“面向连接”指的是在通信双方在通信之前要先建立一个通信的渠道,比如打电话,直到Ҏ接了电话通信才能开始,与此相对的是写信Q在你把信发出去的时候你q不能确认对方的地址是否正确Q通信渠道不一定能建立Q但对发信h来说Q通信已经开始了。“保持状态”则是指通信的一方能够把一pd的消息关联v来,使得消息之间可以互相依赖Q比如一个服务员能够认出再次光的老顾客ƈ且记得上ơ这个顾客还Ơ店里一块钱。这一cȝ例子有“一个TCP session”或者“一个POP3 session”③?/p>
而到了web服务器蓬勃发展的时代Qsession在web开发语境下的语义又有了新的扩展Q它的含义是指一cȝ来在客户端与服务器之间保持状态的解决Ҏ④。有时候session也用来指q种解决Ҏ的存储结构,如“把xxx保存在session里”⑤。由于各U用于web开发的语言在一定程度上都提供了对这U解x案的支持Q所以在某种特定语言的语境下Qsession也被用来指代该语a的解x案,比如l常把Java里提供的javax.servlet.http.HttpSessionUCؓsession⑥?/p>
鉴于q种混ؕ已不可改变,本文中session一词的q用也会Ҏ上下文有不同的含义,请大家注意分辨?br />在本文中Q用中文“浏览器会话期间”来表达含义①,使用“session机制”来表达含义④,使用“session”表辑义⑤Q用具体的“HttpSession”来表达含义?/p>
二、HTTP协议与状态保?/strong>
然而聪明(或者贪心?Q的Z很快发现如果能够提供一些按需生成的动态信息会使web变得更加有用Q就像给有线电视加上Ҏ功能一栗这U需求一斚wqHTML逐步d了表单、脚本、DOM{客L行ؓQ另一斚w在服务器端则出现了CGI规范以响应客L的动态请求,作ؓ传输载体的HTTP协议也添加了文g上蝲、cookieq些Ҏ。其中cookie的作用就是ؓ了解决HTTP协议无状态的~陷所作出的努力。至于后来出现的session机制则是又一U在客户端与服务器之间保持状态的解决Ҏ?/p>
让我们用几个例子来描qC下cookie和session机制之间的区别与联系。笔者曾l常ȝ一家咖啡店有喝5杯咖啡免费赠一杯咖啡的优惠Q然而一ơ性消?杯咖啡的Z微乎其微Q这时就需要某U方式来U录某位֮的消Ҏ量。想象一下其实也无外乎下面的几种ҎQ?br />1、该店的店员很厉宻I能记住每位顾客的消费数量Q只要顾客一走进咖啡店,店员q道该怎么对待了。这U做法就是协议本w支持状态?br />2、发l顾客一张卡片,上面记录着消费的数量,一般还有个有效期限。每ơ消ҎQ如果顾客出C张卡片,则此ơ消费就会与以前或以后的消费相联pv来。这U做法就是在客户端保持状态?br />3、发l顾客一张会员卡Q除了卡号之外什么信息也不纪录,每次消费Ӟ如果֮出示该卡片,则店员在店里的纪录本上找到这个卡号对应的U录d一些消费信息。这U做法就是在服务器端保持状态?/p>
׃HTTP协议是无状态的Q而出于种U考虑也不希望使之成ؓ有状态的Q因此,后面两种Ҏ成为现实的选择。具体来说cookie机制采用的是在客L保持状态的ҎQ而session机制采用的是在服务器端保持状态的Ҏ。同时我们也看到Q由于采用服务器端保持状态的Ҏ在客L也需要保存一个标识,所以session机制可能需要借助于cookie机制来达C存标识的目的Q但实际上它q有其他选择?/p>
三、理解cookie机制
正统的cookie分发是通过扩展HTTP协议来实现的Q服务器通过在HTTP的响应头中加上一行特D的指示以提C浏览器按照指示生成相应的cookie。然而纯_的客户端脚本如JavaScript或者VBScript也可以生成cookie?/p>
而cookie的用是由浏览器按照一定的原则在后台自动发送给服务器的。浏览器查所有存储的cookieQ如果某个cookie所声明的作用范围大于等于将要请求的资源所在的位置Q则把该cookie附在h资源的HTTPh头上发送给服务器。意思是麦当劳的会员卡只能在麦当劳的店里出示Q如果某家分店还发行了自q会员卡,那么q这家店的时候除了要出示麦当劳的会员卡,q要出示q家店的会员卡?/p>
cookie的内容主要包括:名字Q|q期旉Q\径和域?br />其中域可以指定某一个域比如.google.comQ相当于d招牌Q比如宝z公司,也可以指定一个域下的具体某台机器比如www.google.com或者froogle.google.comQ可以用飘柔来做比?br />路径是跟在域名后面的URL路径Q比?或?foo{等Q可以用某飘柔专柜做比?br />路径与域合在一起就构成了cookie的作用范围?br />如果不设|过期时_则表C个cookie的生命期为浏览器会话期间Q只要关闭浏览器H口Qcookie消׃。这U生命期为浏览器会话期的cookie被称Z话cookie。会话cookie一般不存储在硬盘上而是保存在内存里Q当然这U行为ƈ不是规范规定的。如果设|了q期旉Q浏览器׃把cookie保存到硬盘上Q关闭后再次打开览器,q些cookie仍然有效直到过讑֮的过期时间?/p>
存储在硬盘上的cookie可以在不同的览器进E间׃nQ比如两个IEH口。而对于保存在内存里的cookieQ不同的览器有不同的处理方式。对于IEQ在一个打开的窗口上按Ctrl-NQ或者从文g菜单Q打开的窗口可以与原窗口共享,而用其他方式新开的IEq程则不能共享已l打开的窗口的内存cookieQ对于Mozilla Firefox0.8Q所有的q程和标{N都可以共享同Lcookie。一般来说是用javascript的window.open打开的窗口会与原H口׃n内存cookie。浏览器对于会话cookie的这U只认cookie不认人的处理方式l常l采用session机制的web应用E序开发者造成很大的困扰?/p>
下面是一个goolge讄cookie的响应头的例?br />HTTP/1.1 302 Found
四、理解session机制
当程序需要ؓ某个客户端的h创徏一个session的时候,服务器首先检查这个客L的请求里是否已包含了一个session标识 - UCؓsession idQ如果已包含一个session id则说明以前已lؓ此客L创徏qsessionQ服务器按照session id把这个session索出来用(如果索不刎ͼ可能会新Z个)Q如果客Lh不包含session idQ则为此客户端创Z个sessionq且生成一个与此session相关联的session idQsession id的值应该是一个既不会重复Q又不容易被扑ֈ规律以仿造的字符Ԍq个session id被在本ơ响应中q回l客L保存?/p>
保存q个session id的方式可以采用cookieQ这样在交互q程中浏览器可以自动的按照规则把q个标识发挥l服务器。一般这个cookie的名字都是类gSEEESIONIDQ而。比如weblogic对于web应用E序生成的cookieQJSESSIONID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764Q它的名字就是JSESSIONID?/p>
׃cookie可以被h为的止Q必L其他机制以便在cookie被禁止时仍然能够把session id传递回服务器。经常被使用的一U技术叫做URL重写Q就是把session id直接附加在URL路径的后面,附加方式也有两种Q一U是作ؓURL路径的附加信息,表现形式为http://...../xxx;jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764 另一U技术叫做表单隐藏字Dc就是服务器会自动修改表单,d一个隐藏字D,以便在表单提交时能够把session id传递回服务器。比如下面的表单 在谈论session机制的时候,常常听到q样一U误解“只要关闭浏览器Qsession消׃”。其实可以想象一下会员卡的例子,除非֮d对店家提出销卡,否则店家l对不会L删除֮的资料。对session来说也是一LQ除非程序通知服务器删除一个sessionQ否则服务器会一直保留,E序一般都是在用户做log off的时候发个指令去删除session。然而浏览器从来不会d在关闭之前通知服务器它要关闭Q因此服务器Ҏ不会有机会知道浏览器已经关闭Q之所以会有这U错觉,是大部分session机制都用会话cookie来保存session idQ而关闭浏览器后这个session id消׃Q再ơ连接服务器时也无法找到原来的session。如果服务器讄的cookie被保存到盘上,或者用某U手D|写浏览器发出的HTTPh_把原来的session id发送给服务器,则再ơ打开览器仍然能够找到原来的session?/p>
恰恰是由于关闭浏览器不会Dsession被删除,q服务器ؓseesion讄了一个失效时_当距dL上一ơ用session的时间超q这个失效时间时Q服务器可以认为客L已经停止了活动,才会把session删除以节省存储空间?/p>
五、理解javax.servlet.http.HttpSession
首先QWeblogic Server提供了一pd的参数来控制它的HttpSession的实玎ͼ包括使用cookie的开关选项Q用URL重写的开关选项Qsession持久化的讄Qsession失效旉的设|,以及针对cookie的各U设|,比如讄cookie的名字、\径、域Qcookie的生存时间等?/p>
一般情况下Qsession都是存储在内存里Q当服务器进E被停止或者重启的时候,内存里的session也会被清I,如果讄了session的持久化Ҏ,服务器就会把session保存到硬盘上Q当服务器进E重新启动或q些信息能够被再次使用QWeblogic Server支持的持久性方式包括文件、数据库、客Lcookie保存和复制?/p>
复制严格说来不算持久化保存,因ؓsession实际上还是保存在内存里,不过同样的信息被复制到各个cluster内的服务器进E中Q这样即使某个服务器q程停止工作也仍然可以从其他q程中取得session?/p>
cookie生存旉的设|则会媄响浏览器生成的cookie是否是一个会话cookie。默认是使用会话cookie。有兴趣的可以用它来试验我们在第四节里提到的那个误解?/p>
cookie的\径对于web应用E序来说是一个非帔R要的选项QWeblogic Server对这个选项的默认处理方式得它与其他服务器有明昄区别。后面我们会专题讨论?/p>
关于session的设|参考[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
六、HttpSession常见问题
׃session会消耗内存资源,因此Q如果不打算使用sessionQ应该在所有的JSP中关闭它?/p>
2、session何时被删?br />l合前面的讨论,session在下列情况下被删除a.E序调用HttpSession.invalidate();或b.距离上一ơ收到客L发送的session id旉间隔过了session的超时设|?或c.服务器进E被停止Q非持久sessionQ?/p>
3、如何做到在览器关闭时删除session 4、有个HttpSessionListener是怎么回事 5、存攑֜session中的对象必须是可序列化的?br />不是必需的。要求对象可序列化只是ؓ了session能够在集中被复制或者能够持久保存或者在必要时server能够暂时把session交换出内存。在Weblogic Server的session中放|一个不可序列化的对象在控制C会收C个警告。我所用过的某个iPlanet版本如果session中有不可序列化的对象Q在session销毁时会有一个ExceptionQ很奇怪?/p>
6、如何才能正的应付客户端禁止cookie的可能?br />Ҏ有的URL使用URL重写Q包括超链接Qform的actionQ和重定向的URLQ具体做法参见[6] 7、开两个览器窗口访问应用程序会使用同一个sessionq是不同的session 8、如何防止用h开两个览器窗口操作导致的session混ؕ 9、ؓ什么在Weblogic Server中改变session的值后要重新调用一ơsession.setValue 10、ؓ什么session不见?br />排除session正常失效的因素之外,服务器本w的可能性应该是微乎其微的,虽然W者在iPlanet6SP1加若q补丁的Solaris版本上倒也遇到q;览器插件的可能性次之,W者也遇到q?721插g造成的问题;理论上防火墙或者代理服务器在cookie处理上也有可能会出现问题?br />出现q一问题的大部分原因都是E序的错误,最常见的就是在一个应用程序中去访问另外一个应用程序。我们在下一节讨个问题?/p>
七、跨应用E序的session׃n 然而按照Servlet规范Qsession的作用范围应该仅仅限于当前应用程序下Q不同的应用E序之间是不能够互相讉KҎ的session的。各个应用服务器从实际效果上都遵守了q一规范Q但是实现的l节却可能各有不同,因此解决跨应用程序session׃n的方法也各不相同?/p>
首先来看一下Tomcat是如何实现web应用E序之间session的隔ȝQ从Tomcat讄的cookie路径来看Q它对不同的应用E序讄的cookie路径是不同的Q这样不同的应用E序所用的session id是不同的Q因此即使在同一个浏览器H口里访问不同的应用E序Q发送给服务器的session id也可以是不同的?br />
Ҏq个Ҏ,我们可以推测Tomcat中session的内存结构大致如下?br />
W者以前用q的iPlanet也采用的是同L方式Q估计SunONE与iPlanet之间不会有太大的差别。对于这U方式的服务器,解决的思\很简单,实际实行h也不难。要么让所有的应用E序׃n一个session idQ要么让应用E序能够获得其他应用E序的session id?/p>
iPlanet中有一U很单的Ҏ来实现共享一个session idQ那是把各个应用程序的cookie路径都设?Q实际上应该?NASAppQ对于应用程序来讲它的作用相当于根)?br /><session-info> 需要注意的是,操作׃n的session应该遵@一些编E约定,比如在session attribute名字的前面加上应用程序的前缀Q得setAttribute("name", "neo")变成setAttribute("app1.name", "neo")Q以防止命名I间冲突Q导致互相覆盖?/p>
我们再看一下Weblogic Server是如何处理session的?br />
从截屏画面上可以看到Weblogic ServerҎ有的应用E序讄的cookie的\径都?Q这是不是意味着在Weblogic Server中默认的可以共享session了呢Q然而一个小实验卛_证明即不同的应用程序用的是同一个sessionQ各个应用程序仍然只能访问自己所讄的那些属性。这说明Weblogic Server中的session的内存结构可能如?br />
对于q样一U结构,在session机制本n上来解决session׃n的问题应该是不可能的了。除了借助于第三方的力量,比如使用文g、数据库、JMS或者客LcookieQURL参数或者隐藏字D늭手段Q还有一U较为方便的做法Q就是把一个应用程序的session攑ֈServletContext中,q样另外一个应用程序就可以从ServletContext中取得前一个应用程序的引用。示例代码如下, 应用E序A 应用E序B 值得注意的是q种用法不可ULQ因为根据ServletContext的JavaDocQ应用服务器可以处于安全的原因对于context.getContext("/appA");q回I|以上做法在Weblogic Server 8.1中通过?/p>
那么Weblogic ServerZ么要把所有的应用E序的cookie路径都设?呢?原来是ؓ了SSOQ凡是共享这个session的应用程序都可以׃n认证的信息。一个简单的实验可以证明这一点,修改首先d的那个应用程序的描述Wweblogic.xmlQ把cookie路径修改?appA讉K另外一个应用程序会重新要求dQ即使是反过来,先访问cookie路径?的应用程序,再访问修改过路径的这个,虽然不再提示dQ但是登录的用户信息也会丢失。注意做q个实验时认证方式应该用FORMQ因为浏览器和web服务器对basic认证方式有其他的处理方式Q第二次h的认证不是通过session来实现的。具体请参看[7] secion 14.8 AuthorizationQ你可以修改所附的CZE序来做q些试验?/p>
八、ȝ 关于作者: 参考文档: 代码下蝲Q?a >sampleApp.zip
HTTP协议本n是无状态的Q这与HTTP协议本来的目的是相符的,客户端只需要简单的向服务器h下蝲某些文gQ无论是客户端还是服务器都没有必要纪录彼此过ȝ行ؓQ每一ơ请求之间都是独立的Q好比一个顾客和一个自动售货机或者一个普通的Q非会员Ӟ大卖Z间的关系一栗?/p>
cookie机制的基本原理就如上面的例子一L单,但是q有几个问题需要解冻I“会员卡”如何分发;“会员卡”的内容Q以及客户如何用“会员卡”?/p>
Location: http://www.google.com/intl/zh-CN/
Set-Cookie: PREF=ID=0565f77e132de138:NW=1:TM=1098082649:LM=1098082649:S=KaeaCFPo49RiA_d8; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Content-Type: text/html
q是使用HTTPLookq个HTTP Sniffer软g来俘LHTTP通讯U录的一部分
览器在再次讉Kgoolge的资源时自动向外发送cookie
使用Firefox可以很容易的观察现有的cookie的?br />使用HTTPLook配合Firefox可以很容易的理解cookie的工作原理?/p>
IE也可以设|在接受cookie前询?/p>
q是一个询问接受cookie的对话框?/p>
session机制是一U服务器端的机制Q服务器使用一U类g散列表的l构Q也可能是使用散列表)来保存信息?/p>
另一U是作ؓ查询字符串附加在URL后面Q表现Ş式ؓhttp://...../xxx?jsessionid=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764
q两U方式对于用h说是没有区别的,只是服务器在解析的时候处理的方式不同Q采用第一U方式也有利于把session id的信息和正常E序参数区分开来?br />Z在整个交互过E中始终保持状态,必d每个客户端可能请求的路径后面都包含这个session id?/p>
<form name="testform" action="/xxx">
<input type="text">
</form>
在被传递给客户端之前将被改写成
<form name="testform" action="/xxx">
<input type="hidden" name="jsessionid" value="ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng!-145788764">
<input type="text">
</form>
q种技术现在已较少应用Q笔者接触过的很古老的iPlanet6(SunONE应用服务器的前n)׃用了q种技术?br />实际上这U技术可以简单的用对action应用URL重写来代ѝ?/p>
HttpSession是Javaq_对session机制的实现规范,因ؓ它仅仅是个接口,具体到每个web应用服务器的提供商,除了对规范支持之外,仍然会有一些规范里没有规定的细微差异。这里我们以BEA的Weblogic Server8.1作ؓ例子来演C?/p>
Q在本小节中session的含义ؓ⑤和⑥的混合Q?/p>
1、session在何时被创徏
一个常见的误解是以为session在有客户端访问时p创徏Q然而事实是直到某server端程序调用HttpServletRequest.getSession(true)q样的语句时才被创徏Q注意如果JSP没有昄的?<%@page session="false"%> 关闭sessionQ则JSP文g在编译成Servlet时将会自动加上这样一条语句HttpSession session = HttpServletRequest.getSession(true);q也是JSP中隐含的session对象的来历?/p>
严格的讲Q做不到q一炏V可以做一点努力的办法是在所有的客户端页面里使用javascript代码window.oncolose来监视浏览器的关闭动作,然后向服务器发送一个请求来删除session。但是对于浏览器崩溃或者强行杀死进E这些非常规手段仍然无能为力?/p>
你可以创Llistenerȝ控session的创建和销毁事Ӟ使得在发生这L事g时你可以做一些相应的工作。注意是session的创建和销毁动作触发listenerQ而不是相反。类似的与HttpSession有关的listenerq有HttpSessionBindingListenerQHttpSessionActivationListener和HttpSessionAttributeListener?/p>
http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
参见W三节对cookie的讨论,对session来说是只认id不认人,因此不同的浏览器Q不同的H口打开方式以及不同的cookie存储方式都会对这个问题的{案有媄响?/p>
q个问题与防止表单多ơ提交是cM的,可以通过讄客户端的令牌来解冟뀂就是在服务器每ơ生成一个不同的idq回l客LQ同时保存在session里,客户端提交表单时必须把这个id也返回服务器Q程序首先比较返回的id与保存在session里的值是否一_如果不一致则说明本次操作已经被提交过了。可以参看《J2EE核心模式》关于表C层模式的部分。需要注意的是对于用javascript window.open打开的窗口,一般不讄q个idQ或者用单独的idQ以防主H口无法操作Q徏议不要再window.open打开的窗口里做修Ҏ作,q样可以不用设|?/p>
做这个动作主要是Z在集环境中提示Weblogic Server session中的值发生了改变Q需要向其他服务器进E复制新的session倹{?/p>
常常有这L情况Q一个大目被分割成若干项目开发,Z能够互不q扰Q要求每个小目作ؓ一个单独的web应用E序开发,可是C最后突然发现某几个项目之间需要共享一些信息,或者想使用session来实现SSO(single sign on)Q在session中保存login的用户信息,最自然的要求是应用E序间能够访问彼此的session?/p>
<path>/NASApp</path>
</session-info>
在Tomcat中则没有q么方便的选择。在Tomcat版本3上,我们q可以有一些手D|׃nsession。对于版?以上的TomcatQ目前笔者尚未发现简单的办法。只能借助于第三方的力量,比如使用文g、数据库、JMS或者客LcookieQURL参数或者隐藏字D늭手段?/p>
context.setAttribute("appA", session);
contextA = context.getContext("/appA");
HttpSession sessionA = (HttpSession)contextA.getAttribute("appA");
session机制本nq不复杂Q然而其实现和配|上的灵zL却使得具体情况复杂多变。这也要求我们不能把仅仅某一ơ的l验或者某一个浏览器Q服务器的经验当作普遍适用的经验,而是始终需要具体情况具体分析?/p>
郎云鹏(dev2dev ID: hippiewolfQ,软g工程师,从事J2EE开?br />电子邮gQlangyunpeng@yahoo.com.cn
地址Q大qY件园?1L技大厦A座大q博涵咨询服务有限公?/p>
[1] Preliminary Specification http://wp.netscape.com/newsref/std/cookie_spec.html
[2] RFC2109 http://www.rfc-editor.org/rfc/rfc2109.txt
[3] RFC2965 http://www.rfc-editor.org/rfc/rfc2965.txt
[4] The Unofficial Cookie FAQ http://www.cookiecentral.com/faq/
[5] http://e-docs.bea.com/wls/docs70/webapp/weblogic_xml.html#1036869
[6] http://e-docs.bea.com/wls/docs70/webapp/sessions.html#100770
[7] RFC2616 http://www.rfc-editor.org/rfc/rfc2616.txt
]]>
使用WindowsC本的“另存ؓ”,可以在GBK、Unicode、Unicode big endian和UTF-8q几U编码方式间怺转换。同htxt文gQWindows是怎样识别~码方式的呢Q?/font>
? 很早前就发现Unicode、Unicode big endian和UTF-8~码的txt文g的开头会多出几个字节Q分别是FF、FE QUnicodeQ?FE、FFQUnicode big endianQ?EF、BB、BFQUTF-8Q。但q些标记是基于什么标准呢Q?/font>
查了查相兌料,ȝ这些问题弄清楚了,带也了解了一些Unicode的细节。写成一文章,送给有过cM疑问的朋友。本文在写作时尽量做到通俗易懂Q但要求读者知道什么是字节Q什么是十六q制?/font>
big endian 和little endian是CPU处理多字节数的不同方式。例如“汉”字的Unicode~码?C49。那么写到文仉ӞI竟是将6C写在前面Q? q是?9写在前面Q如果将6C写在前面Q就是big endian。如果将49写在前面Q就是little endian?/font>
“endian”这个词《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开q是从小?Little-Endian)敲开Q由此曾发生q六ơ叛乱,一个皇帝送了命,另一个丢了王位?/font>
我们一般将endian译成“字节序”,big endian和little endianUC“大䏀和“小䏀?/font>
字符必须~码后才能被计算机处理。计机使用的缺省编码方式就是计机的内码。早期的计算Z?位的ASCII~码Qؓ了处理汉字,E序员设计了用于体中文的GB2312和用于繁体中文的big5?/font>
GB2312(1980q?一共收录了7445个字W,包括6763个汉字和682个其它符受汉字区的内码范围高字节从B0-F7Q低字节从A1-FEQ占用的码位?2*94=6768。其中有5个空位是D7FA-D7FE?/font>
GB2312支持的汉字太?995q的汉字扩展规范GBK1.0收录?1886个符P它分为汉字区和图形符号区。汉字区包括21003个字W?/font>
从ASCII? GB2312到GBKQ这些编码方法是向下兼容的,卛_一个字W在q些Ҏ中L有相同的~码Q后面的标准支持更多的字W。在q些~码中,英文和中文可? l一地处理。区分中文编码的Ҏ是高字节的最高位不ؓ0。按照程序员的称|GB2312、GBK都属于双字节字符集?DBCS)?/font>
2000 q的GB18030是取代GBK1.0的正式国家标准。该标准收录?7484个汉字,同时q收录了藏文、蒙文、维向ְ文等主要的少数民族文字。从汉字? 汇上_GB18030在GB13000.1?0902个汉字的基础上增加了CJK扩展A?582个汉字(Unicode?x3400- 0x4db5Q,一共收录了27484个汉字?/font>
CJK是中日韩的意思。UnicodeZ节省码位Q将中日韩三国语a中的文字l一~码。GB13000.1是ISO/IEC 10646-1的中文版Q相当于Unicode 1.1?/font>
GB18030 的编码采用单字节、双字节?字节Ҏ。其中单字节、双字节和GBK是完全兼容的?字节~码的码位就是收录了CJK扩展A?582个汉字。 例如: UCS?x3400在GB18030中的~码应该?139EF30QUCS?x3401在GB18030中的~码应该?139EF31?/font>
微Y提供了GB18030的升U包Q但q个升包只是提供了一套支持CJK扩展A?582个汉字的新字体:新宋?18030Qƈ不改变内码。Windows 的内码仍然是GBK?/font>
q里q有一些细节:
GB2312的原文还是区位码Q从Z码到内码Q需要在高字节和低字节上分别加上A0?/font>
? 于Q何字W编码,~码单元的顺序是q码方案指定的Q与endian无关。例如GBK的编码单元是字节Q用两个字节表示一个汉字。 这两个字节的顺序是? 定的Q不受CPU字节序的影响。UTF-16的编码单元是wordQ双字节Q,word之间的顺序是~码Ҏ指定的,word内部的字节排列才会受? endian的媄响。后面还会介lUTF-16?/font>
GB2312的两个字节的最高位都是1。但W合q个条g的码位只? 128*128=16384个。所以GBK和GB18030的低字节最高位都可能不?。不q这不媄响DBCS字符的解析Q在dDBCS字符时Q只 要遇到高位ؓ1的字节,可以将下两个字节作Z个双字节~码Q而不用管低字节的高位是什么?/font>
前面提到从ASCII、GB2312、GBK到GB18030的编码方法是向下兼容的。而Unicode只与ASCII兼容Q更准确地说Q是与ISO-8859-1兼容Q,与GB码不兼容。例如“汉”字的Unicode~码?C49Q而GB码是BABA?/font>
Unicode 也是一U字W编码方法,不过它是由国际组l设计,可以容纳全世界所有语a文字的编码方案。Unicode的学名是"Universal Multiple -Octet Coded Character Set"Q简UCؓUCS。UCS可以看作?Unicode Character Set"的羃写?/font>
Ҏl基癄全书(http://zh.wikipedia.org/wiki/)的记载:历史上存在两个试囄立设计Unicode的组l,卛_际标准化l织QISOQ和一个Y件制造商的协会(unicode.orgQ。ISO开发了ISO 10646目QUnicode协会开发了Unicode目?/font>
?991q前后,双方都认识到世界不需要两个不兼容的字W集。于是它们开始合q双方的工作成果Qƈ为创立一个单一~码表而协同工作。从Unicode2.0开始,Unicode目采用了与ISO 10646-1相同的字库和字码?/font>
目前两个目仍都存在Qƈ独立地公布各自的标准。Unicode协会现在的最新版本是2005q的Unicode 4.1.0。ISO的最新标准是ISO 10646-3:2003?/font>
UCS 只是规定如何~码Qƈ没有规定如何传输、保存这个编码。例如“汉”字的UCS~码?C49Q我可以?个ascii数字来传输、保存这个编码;也可以用 utf-8~码:3个连l的字节E6 B1 89来表C它。关键在于通信双方都要认可。UTF-8、UTF-7、UTF-16都是被广泛接受的Ҏ? UTF-8的一个特别的好处是它与ISO-8859-1完全兼容。UTF是“UCS Transformation Format”的~写?/font>
IETF 的RFC2781和RFC3629以RFC的一贯风|清晰、明快又不失严}地描qCUTF-16和UTF-8的编码方法。我LC得IETF? Internet Engineering Task Force的羃写。但IETF负责l护的RFC是Internet上一切规范的基础?/font>
目前Windows的内核已l支持Unicode字符集,q样在内怸可以支持全世界所有的语言文字。但是由于现有的大量E序和文都采用了某U特定语a的编码,例如GBKQWindows不可能不支持现有的编码,而全部改用Unicode?/font>
Windows使用代码?code page)来适应各个国家和地区。code page可以被理解ؓ前面提到的内码。GBK对应的code page是CP936?/font>
微Y也ؓGB18030定义了code pageQCP54936。但是由于GB18030有一部分4字节~码Q而Windows的代码页只支持单字节和双字节~码Q所以这个code page是无法真正用的?/font>
UCS有两U格式:UCS-2和UCS-4。顾名思义QUCS-2是用两个字节编码,UCS-4是?个字节(实际上只用了31位,最高位必须?Q编码。下面让我们做一些简单的数学游戏Q?/font>
UCS-2?^16=65536个码位,UCS-4?^31=2147483648个码位?/font>
UCS -4Ҏ最高位?的最高字节分?^7=128个group。每个group再根据次高字节分?56个plane。每个planeҎW?个字节分? 256行?rows)Q每行包?56个cells。当然同一行的cells只是最后一个字节不同,其余都相同?/font>
group 0的plane 0被称作Basic Multilingual Plane, 即BMP。或者说UCS-4中,高两个字节ؓ0的码位被UCBMP?/font>
UCS-4的BMPL前面的两个零字节得CUCS-2。在UCS-2的两个字节前加上两个零字节,得CUCS-4的BMP。而目前的UCS-4规范中还没有M字符被分配在BMP之外?/font>
UTF-8是?位ؓ单元对UCSq行~码。从UCS-2到UTF-8的编码方式如下:
UCS-2~码(16q制) | UTF-8 字节?二进? |
0000 - 007F | 0xxxxxxx |
0080 - 07FF | 110xxxxx 10xxxxxx |
0800 - FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
例如“汉”字的Unicode~码?C49?C49?800-FFFF之间Q所以肯定要?字节模板了: 1110 xxxx 10 xxxxxx 10 xxxxxx。将6C49写成二进制是Q?110 110001 001001Q 用q个比特依ơ代替模板中的xQ得刎ͼ 1110 0110 10 110001 10 001001Q即E6 B1 89?/font>
读者可以用C本测试一下我们的~码是否正确。需要注意,UltraEdit在打开utf-8~码的文本文件时会自动{换ؓUTF-16Q可能生淆。你可以在设|中xq个选项。更好的工具是Hex Workshop?/font>
UTF -16?6位ؓ单元对UCSq行~码。对于小?x10000的UCS码,UTF-16~码q于UCS码对应的16位无W号整数。对于不于 0x10000的UCS码,定义了一个算法。不q由于实际用的UCS2Q或者UCS4的BMP必然于0x10000Q所以就目前而言Q可以认为UTF -16和UCS-2基本相同。但UCS-2只是一个编码方案,UTF-16却要用于实际的传输,所以就不得不考虑字节序的问题?/font>
UTF -8以字节ؓ~码单元Q没有字节序的问题。UTF-16以两个字节ؓ~码单元Q在解释一个UTF-16文本前,首先要弄清楚每个~码单元的字节序。例? “奎”的Unicode~码?94EQ“乙”的Unicode~码?E59。如果我们收到UTF-16字节?94E”,那么q是“奎”还? “乙”?
Unicode规范中推荐的标记字节序的方法是BOM。BOM不是“Bill Of Material”的BOM表,而是Byte Order Mark。BOM是一个有点小聪明的想法:
在UCS ~码中有一个叫?ZERO WIDTH NO-BREAK SPACE"的字W,它的~码是FEFF。而FFFE在UCS中是不存在的字符Q所以不应该 出现在实际传输中。UCS规范我们在传输字节流前,先传输字W?ZERO WIDTH NO-BREAK SPACE"?/font>
q样如果接收者收到FEFFQ就表明q个字节是Big-Endian的;如果收到FFFEQ就表明q个字节是Little-Endian的。因此字W?ZERO WIDTH NO-BREAK SPACE"又被UCBOM?/font>
UTF -8不需要BOM来表明字节顺序,但可以用BOM来表明编码方式。字W?ZERO WIDTH NO-BREAK SPACE"的UTF-8~码? EF BB BFQ读者可以用我们前面介绍的编码方法验证一下)。所以如果接收者收CEF BB BF开头的字节,q道这是UTF-8~码了?/font>
Windows是使用BOM来标记文本文件的~码方式的?/font>
本文主要参考的资料是?Short overview of ISO-IEC 10646 and Unicode" (http://www.nada.kth.se/i18n/ucs/unicode-iso10646-oview.html)?/font>
我还找了两篇看上M错的资料Q不q因为我开始的疑问都找C{案Q所以就没有看:
我写qUTF-8、UCS-2、GBK怺转换的Y件包Q包括用Windows API和不使用Windows API的版本。以后有旉的话Q我会整理一下放到我的个Z上(http://fmddlmyy.home4u.china.com)?/font>
我是x楚所有问题后才开始写q篇文章的,原以Z会儿p写好。没惛_考虑措辞和查证细节花费了很长旉Q竟然从下午1:30写到9:00。希望有读者能从中受益?/font>
有的朋友Ҏ章中q句话还有疑问:
“GB2312的原文还是区位码Q从Z码到内码Q需要在高字节和低字节上分别加上A0。?/font>
我再详细解释一下:
“GB2312 的原文”是指国?980q的一个标准《中华h民共和国国家标准 信息交换用汉字编码字W集 基本集 GB 2312-80》。这个标准用两个数来~码? 字和中文W号。第一个数UCؓ“区”,W二个数UCؓ“位”。所以也UCؓZ码?-9区是中文W号Q?6-55区是一U汉字,56-87区是二汉字。现? Windows也还有区位输入法Q例如输?601得到“啊”。(q个Z输入法可以自动识?6q制的GB2312?0q制的区位码Q也是说输? B0A1同样会得到“啊”。)
内码是指操作pȝ内部的字W编码。早期操作系l的内码是与语言相关的。现在的Windows在系l内部支持UnicodeQ然后用代码适应各种语言Q“内码”的概念比较模p了。微软一般将~省代码|定的~码说成是内码?/font>
内码q个词汇Qƈ没有什么官方的定义Q代码页也只是微软这个公司的叫法。作为程序员Q我们只要知道它们是什么东西,没有必要q多地考证q些名词?/font>
所谓代码页(code page)是针对一U语a文字的字W编码。例如GBK的code page是CP936QBIG5的code page是CP950QGB2312的code page是CP20936?/font>
Windows中有~省代码늚概念Q即~省用什么编码来解释字符。例如Windows的记事本打开了一个文本文Ӟ里面的内Ҏ字节:BA、BA、D7、D6。Windows应该L么解释它呢Q?/font>
? 按照Unicode~码解释、还是按照GBK解释、还是按照BIG5解释Q还是按照ISO8859-1去解释?如果按GBK去解释,׃得到“汉字”两? 字。按照其它编码解释,可能找不到对应的字符Q也可能扑ֈ错误的字W。所谓“错误”是指与文本作者的本意不符Q这时就产生了ؕ码?/font>
{案是Windows按照当前的缺省代码页去解释文本文仉的字节流。缺省代码页可以通过控制面板的区域选项讄。记事本的另存ؓ中有一ANSIQ其实就是按照缺省代码页的编码方法保存?/font>
Windows的内码是UnicodeQ它在技术上可以同时支持多个代码c只要文件能说明自己使用什么编码,用户又安装了对应的代码页QWindowsp正确昄Q例如在HTML文g中就可以指定charset?/font>
?
的HTML文g作者,特别是英文作者,认ؓ世界上所有h都用英文,在文件中不指定charset。如果他使用?x80-0xff之间的字W,中文
Windows又按照缺省的GBK去解释,׃出现q。这时只要在q个html文g中加上指定charset的语句,例如Q?br /><meta http-equiv="Content-Type" content="text/html; charset=ISO8859-1">
如果原作者用的代码和ISO8859-1兼容Q就不会出现q了?/font>
?
说区位码Q啊的区位码?601Q写?6q制?x10,0x01。这和计机q泛使用的ASCII~码冲突。ؓ了兼?0-7f的ASCII~码Q我
们在Z码的高、低字节上分别加上A0。这样“啊”的~码成为B0A1。我们将加过两个A0的编码也UCؓGB2312~码Q虽然GB2312的原文根?
没提到这一炏V?
它定义于ISO/IEC 10646-1的附录QQ而RFC2781也定义了怼的做法?
在Unicode基本多文U^面定义的字符Q无论是拉丁字母、汉字或其他文字或符PQ一律?字节储存。而在辅助q面定义的字W,会以代理?/i>Qsurrogate pairQ的形式Q以两个2字节的值来储存?
UTF-16比vUTF-8Q好处在于大部分字符都以固定长度的字?(2字节) 储存Q但UTF-16却无法兼容于ASCII~码?
UTF-16的大ֺ和小ֺ储存形式都在用。一般来_以Macintosh制作或储存的文字使用大尾序格式,以Microsoft或Linux制作或储存的文字使用尾序格式?
Z弄清楚UTF-16文g的大尾序,在UTF-16文g的开首,都会攄一个U+FEFF字符作ؓByte Order Mark (UTF-16LE ?FF FE 代表QUTF-16BE ?FE FF 代表)Q以昄q个文字案是以UTF-16~码?
以下的例子有四个字符Q“朱”、半角逗号、“聿”、“𨮁”?
使用 UTF-16 ~码的例? | ||
---|---|---|
~码名称 | ~码ơ序 | ~码 |
UTF-16LE | 尾? | 31 67, 2C 00, 7F 80, 62 D8 81 DF |
UTF-16BE | 大尾? | 67 31, 00 2C, 80 7F, D8 62 DF 81 |
UTF-16 | 尾序,包含BOM | FF FE, 31 67, 2C 00, 7F 80, 62 D8 81 DF |
UTF-16 | 大尾序,包含BOM | FE FF, 67 31, 00 2C, 80 7F, D8 62 DF 81 |
UTF-16可看成是UCS-2的父集。在没有辅助q面字符前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助^面字W后Q就只称为UTF-16了。现在若有Y件声U自己支援UCS-2~码Q那其实是暗指它不能支援辅助q面字符的委婉语?
在所有字W集中,最知名可能 要数被称为ASCII?位字W集了。它是美国信息交换标准委员会 QAmerican Standards Committee for Information InterchangeQ的~写, 为美国英语通信所? 计。它?28个字W组成,包括大小写字母、数?-9、标点符受非打印字符Q换行符、制表符{?个)以及控制字符Q退根{响铃等Q组成?/font>
? 是,׃他是针对p设计的,当处理带有音调标P形如汉语的拼韻I的欧z文字时׃出现问题。因此,创徏Z一些包?55个字W的由ASCII扩展? 字符集。其中有一U通常被成为IBM字符集,它把gؓ128-255之间的字W用于画囑֒ȝQ以及一些特D的Ƨ洲字符。另一U?位字W集? ISO 8859-1 Latin 1Q也UCؓISO Latin-1。它把位?28-255之间的字W用于拉丁字母表中特D语a字符的编码,也因? 而得名?/font>
Ƨ洲语言不是地球上的唯一语言Q因此亚z和非洲语言q不能被8位字W? 集所支持。仅汉语Q或pictogramsQ字母表有80000以上个字W。但是把汉语、日语和南语的一些相似的字符l合hQ在不同的语a里,使不 同的字符代表不同的字Q这样只?个字节就可以~码地球上几乎所有地区的文字。因此,创徏了UNICODE~码。它通过增加一个高字节? ISO Latin-1字符集进行扩展,当这些高字节位ؓ0Ӟ低字节就是ISO Latin-1字符。UNICODE支持Ƨ洲、非zӀ中东、亚zԌ包括 l一标准的东亚像形汉字和韩国像Ş文字Q。但是,UNICODEq没有提供对诸如Braille, Cherokee, Ethiopic, Khmer, Mongolian, Hmong, Tai Lu, Tai Mau文字的支持。同时它也不支持如Ahom, Akkadian, Aramaic, Babylonian Cuneiform, Balti, Brahmi, Etruscan, Hittite, Javanese, Numidian, Old Persian Cuneiform, Syrian之类的古老的文字?/font>
? 实证明,对可以用ASCII表示的字W用UNICODEq不高效Q因为UNICODE比ASCII占用大一倍的I间Q而对ASCII来说高字节的0对他 毫无用处。ؓ了解册个问题,出C一些中间格式的字符集,他们被称为通用转换格式Q既UTF QUniversal Transformation FormatQ。目前存在的UTF格式有:UTF-7, UTF-7.5, UTF-8, UTF -16, 以及 UTF-32。本文讨论UTF-8字符集的基础?/font>
UTF_8字符?/font>
UTF -8是UNICODE的一U变长字W编码,由Ken Thompson?992q创建。现在已l标准化为RFC 3629。UTF-8??个字节编 码UNICODE字符。如果UNICODE字符?个字节表C,则编码成UTF-8很可能需?个字节,而如果UNICODE字符?个字节表C,则编? 成UTF-8可能需?个字节。用4个或6个字节去~码一个UNICODE字符可能太多了,但很会遇到那样的UNICODE字符?/font>
UFT-8转换表表C如下:
UNICODE UTF-8
00000000 - 0000007F 0xxxxxxx
00000080 - 000007FF 110xxxxx 10xxxxxx
00000800 - 0000FFFF 1110xxxx 10xxxxxx 10xxxxxx
00010000 - 001FFFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
00200000 - 03FFFFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
04000000 - 7FFFFFFF 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
? 际表CASCII字符的UNICODE字符Q将会编码成1个字节,q且UTF-8表示与ASCII字符表示是一L。所有其他的UNCODE字符转化? UTF-8需要至?个字节。每个字节由一个换码序列开始。第一个字节由唯一的换码序列,由n?加一?l成。n?表示字符~码所需的字节数?/font>
CZ
UNICODE uCA(11001010) ~码成UTF-8需?个字节:
uCA -> C3 8A
1100 1010
110xxxxx 10xxxxxx
1100 1010 -> 110xxxxx 10xxxxxx
-> 110xxxxx 10xxxxx0
-> 110xxxxx 10xxxx10
-> 110xxxxx 10xxx010
-> 110xxxxx 10xx1010
-> 110xxxxx 10x01010
-> 110xxxxx 10001010
-> 110xxxx1 10001010
-> 110xxx11 10001010
-> 11000011 10001010
-> C3 8A
UNICODE uF03F (11110000 00111111) ~码成UTF-8需?个字?
u F03F -> EF 80 BF
1111 0000 0011 1111 -> 1110xxxx 10xxxxxx 10xxxxxx
-> 11101111 10000000 10111111
-> EF 80 BF
译者注Q由上分析可以看刎ͼUNCODE到UTF-8的{换就是先定~码所需要的字节敎ͼ然后用UNICODE~码位从低位到高位依ơ填入上面表CZؓx的位上,不的高位以0补充。以上是个hl验Q如有错误,请不惜指教,谢过?)
UTF-8~码的优点:
UTF-8~码可以通过屏蔽位和UM操作快速读写?br />字符串比较时strcmp()和wcscmp()的返回结果相同,因此使排序变得更加容易?br />字节FF和FE在UTF-8~码中永q不会出玎ͼ因此他们可以用来表明UTF-16或UTF-32文本Q见BOMQ?br />UTF-8 是字节顺序无关的。它的字节顺序在所有系l中都是一LQ因此它实际上ƈ不需要BOM?/font>
UTF-8~码的缺点:
你无法从UNICODE字符数判断出UTF-8文本的字节数Q因为UTF-8是一U变长编?br />它需要用2个字节编码那些用扩展ASCII字符集只需1个字节的字符
ISO Latin-1 是UNICODE的子集,但不是UTF-8的子?br />8位字W的UTF-8~码会被email|关qoQ因为internet信息最初设计ؓ7为ASCII码。因此生了UTF-7~码?br />UTF-8 在它的表CZ使用?00xxxxx的几率超q?0%Q 而现存的实现如ISO 2022Q?873Q?429Q 和8859pȝQ会把它错认为是C1 控制码。因此生了UTF-7.5~码?/font>
修正的UTF-8Q?/font>
java使用UTF-16表示内部文本Qƈ支持用于字符串串行化的非标准的修正UTF-8~码。标准UTF-8和修正的UTF-8有两点不同:
?
正的UTF-8中,null字符~码?个字节(11000000 00000000Q 而不是标准的1个字节(00000000Q,q样作可以保证编?
后的字符串中不会嵌入null字符。因此如果在cC语言中处理字W串Q文本不会在W一个null字符时截断(C字符串以nulll尾Q?br />在标?
UTF-8~码中,出基本多语a范围QBMP - Basic Multilingual PlainQ的字符被编码ؓ4字节格式Q但是在修正的UTF
-8~码中,他们׃理编码对Qsurrogate pairsQ表C,然后q些代理~码对在序列中分别重新编码。结果标准UTF-8~码中需?个字?
的字W,在修正后的UTF-8~码中将需?个字节?/font>
位序标志BOM
BOMQByte Order MarkQ是一个字W,它表明UNICODE文本的UTF-16,UTF-32的编码字节顺序(高字节低字节序Q和~码方式QUTF-8,UTF-16,UTF-32Q 其中UTF-8~码是字节顺序无关的Q?/font>
如下所C:
Encoding Representation
UTF-8 EF BB BF
UTF-16 Big Endian FE FF
UTF-16 Little Endian FF FE
UTF-32 Big Endian 00 00 FE FF
UTF-32 Little Endian FF FE 00 00
UTF-8 C++ E序~码CZQ?/font>
下面是四个C++函数Q他们分别实?字节?字节UNICODE和UTF-8之间的{换?/font>
#define MASKBITS 0x3F
#define MASKBYTE 0x80
#define MASK2BYTES 0xC0
#define MASK3BYTES 0xE0
#define MASK4BYTES 0xF0
#define MASK5BYTES 0xF8
#define MASK6BYTES 0xFC
typedef unsigned short Unicode2Bytes;
typedef unsigned int Unicode4Bytes;
void UTF8Encode2BytesUnicode(std::vector< Unicode2Bytes > input,
std::vector< byte >& output)
{
for(int i=0; i < input.size(); i++)
{
// 0xxxxxxx
if(input < 0x80)
{
output.push_back((byte)input);
}
// 110xxxxx 10xxxxxx
else if(input < 0x800)
{
output.push_back((byte)(MASK2BYTES | input >> 6));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
// 1110xxxx 10xxxxxx 10xxxxxx
else if(input < 0x10000)
{
output.push_back((byte)(MASK3BYTES | input >> 12));
output.push_back((byte)(MASKBYTE | input >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
}
}
void UTF8Decode2BytesUnicode(std::vector< byte > input,
std::vector< Unicode2Bytes >& output)
{
for(int i=0; i < input.size();)
{
Unicode2Bytes ch;
// 1110xxxx 10xxxxxx 10xxxxxx
if((input & MASK3BYTES) == MASK3BYTES)
{
ch = ((input & 0x0F) << 12) | (
(input[i+1] & MASKBITS) << 6)
| (input[i+2] & MASKBITS);
i += 3;
}
// 110xxxxx 10xxxxxx
else if((input & MASK2BYTES) == MASK2BYTES)
{
ch = ((input & 0x1F) << 6) | (input[i+1] & MASKBITS);
i += 2;
}
// 0xxxxxxx
else if(input < MASKBYTE)
{
ch = input;
i += 1;
}
output.push_back(ch);
}
}
void UTF8Encode4BytesUnicode(std::vector< Unicode4Bytes > input,
std::vector< byte >& output)
{
for(int i=0; i < input.size(); i++)
{
// 0xxxxxxx
if(input < 0x80)
{
output.push_back((byte)input);
}
// 110xxxxx 10xxxxxx
else if(input < 0x800)
{
output.push_back((byte)(MASK2BYTES | input > 6));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
// 1110xxxx 10xxxxxx 10xxxxxx
else if(input < 0x10000)
{
output.push_back((byte)(MASK3BYTES | input >> 12));
output.push_back((byte)(MASKBYTE | input >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
else if(input < 0x200000)
{
output.push_back((byte)(MASK4BYTES | input >> 18));
output.push_back((byte)(MASKBYTE | input >> 12 & MASKBITS));
output.push_back((byte)(MASKBYTE | input >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if(input < 0x4000000)
{
output.push_back((byte)(MASK5BYTES | input >> 24));
output.push_back((byte)(MASKBYTE | input >> 18 & MASKBITS));
output.push_back((byte)(MASKBYTE | input >> 12 & MASKBITS));
output.push_back((byte)(MASKBYTE | input >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if(input < 0x8000000)
{
output.push_back((byte)(MASK6BYTES | input >> 30));
output.push_back((byte)(MASKBYTE | input >> 18 & MASKBITS));
output.push_back((byte)(MASKBYTE | input >> 12 & MASKBITS));
output.push_back((byte)(MASKBYTE | input >> 6 & MASKBITS));
output.push_back((byte)(MASKBYTE | input & MASKBITS));
}
}
}
void UTF8Decode4BytesUnicode(std::vector< byte > input,
std::vector< Unicode4Bytes >& output)
{
for(int i=0; i < input.size();)
{
Unicode4Bytes ch;
// 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
if((input & MASK6BYTES) == MASK6BYTES)
{
ch = ((input & 0x01) << 30) | ((input[i+1] & MASKBITS) << 24)
| ((input[i+2] & MASKBITS) << 18) | ((input[i+3]
& MASKBITS) << 12)
| ((input[i+4] & MASKBITS) << 6) | (input[i+5] & MASKBITS);
i += 6;
}
// 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
else if((input & MASK5BYTES) == MASK5BYTES)
{
ch = ((input & 0x03) << 24) | ((input[i+1]
& MASKBITS) << 18)
| ((input[i+2] & MASKBITS) << 12) | ((input[i+3]
& MASKBITS) << 6)
| (input[i+4] & MASKBITS);
i += 5;
}
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
else if((input & MASK4BYTES) == MASK4BYTES)
{
ch = ((input & 0x07) << 18) | ((input[i+1]
& MASKBITS) << 12)
| ((input[i+2] & MASKBITS) << 6) | (input[i+3] & MASKBITS);
i += 4;
}
// 1110xxxx 10xxxxxx 10xxxxxx
else if((input & MASK3BYTES) == MASK3BYTES)
{
ch = ((input & 0x0F) << 12) | ((input[i+1] & MASKBITS) << 6)
| (input[i+2] & MASKBITS);
i += 3;
}
// 110xxxxx 10xxxxxx
else if((input & MASK2BYTES) == MASK2BYTES)
{
ch = ((input & 0x1F) << 6) | (input[i+1] & MASKBITS);
i += 2;
}
// 0xxxxxxx
else if(input < MASKBYTE)
{
ch = input;
i += 1;
}
output.push_back(ch);
}
}
限译者水qx限,有不解之处请参考原文。版权属原文作者所有,转蝲h明出处及作者?/font>
原文参见Q?a target="_blank">http://www.codeguru.com/Cpp/misc ... article.php/c10451/