Blog、IM、MSN机器人

近期的IM业界实在是热闹,先是做IM的腾讯和做校友录的搜狐互相捞过界,接着又出了一个IMU,以开放式体系吸引合作者一起成长,而如今又有了一家叫 www.golb.cn的网站在推出自家的blog服务同时,一并推出了支持RSS阅读和blog发布的IM软件。毫无疑问,越来越多的网络应用正在努力的和IM融合,寻求新的成长空间。

  还是让我们先来看看golb.cn推出的IM软件GOLB BLOG TOOL。相信对于不少blog的研究者而言,这个软件的推出应该是一件令人愉快的事情——他们很早就在猜测的blog和IM合作如今终究有人开始尝试了。不过如果我们对于这款工具的功能仔细审视,恐怕就不会有太多的喜悦。说到底,GOLB BLOG TOOL不过是由三个功能RSS聚合阅读,BLOG更新,MSN即时消息简单的雷加在一起,而缺乏足够的整合,其远远无法发挥出1+1+1〉3的效果。

  是的,在blog和IM融合问题上,GOLB BLOG TOOL离我设想的相差太远了。虽然在不久前的一篇文章(《谁应该和IM合作?》)中笔者指出过IM的终端软件特性决定了它在执行很多应用方面比Web有更好的表现,但是如果IM软件只是满足于把其他的软件功能简单的累加在自身身上的话,那么这种累加除了把软件变得越发臃肿以外,能够收到的效果是极其有限的。正如笔者在另一篇文章(《当IM和同学录走到一起》)中所提及的,对于IM软件和其他应用的融合,社会网络的融合是最关键的。

  在笔者的脑海中,blog除了是一个自由的发表平台以外,其通过彼此之间的链接构建起来的那个blogger关系网络同样也是其非常独特而引人关注的一个新特性。如果IM和blog要融合的话,那么基于IM的关系网络和基于blog相互连接的关系网络的融合也就应当是首先应当考虑的一个问题。

  想象一下吧,当你在你的IM中添加了一位新的好友,这位好友事先登记的blog也会立刻出现在你的RSS阅读软件中,甚至每次对方更新了blog之后,无须RSS阅读软件的反复检查,blog服务商就会自动通过IM把更新的消息发给你;或者与此相反,当你在自己的blog中添加了另一个blog的链接,对方登记的IM帐号也会自动添加入你的IM软件,你在对方blog中留下的评论都会自动通过IM发送给对方,你们彼此之间除了blog以外还可以依托IM进行更加方便的沟通——如果你们愿意,沟通的所有内容也可以反向的补充在blog上,供其他人参阅。

  这样的一套应用,是不是比起现在简单的功能累加要来得有趣得多?是的,一切的关键就在于不同软件融合的不仅是他们的功能,更重要的是他们彼此的网络,从而发挥最大的效用——而这正是GOLB BLOG TOOL目前所匮乏的。

  事实上,GOLB BLOG TOOL引发我思考的,不仅仅在于其IM和blog融合的思路,更在于其融合的第三条道路的选择。从已有的案例来看,Web应用和IM融合融合,搜狐的校友录走的是完全开发自己的IM软件从零开始的道路;IMU的思路非常具有创新性,它力图将自己的IM平台半公开化,让其他的合作者能够利用定制版或者插件的方法简单的完成融合——就个人观点,在技术上这是最理想的方法;而GOLB BLOG TOOL无疑是第三道路,通过纯粹与现有IM软件兼容的方法来实现融合——与其他一些一方面兼容其它IM软件一方面发展自己的IM软件,把兼容作为抢夺份额的敌意行为不同,这种融合只不过是在原有的基础上增加了一些与IM无关的功能,可以看作是一个新的Shell,一种善意的兼容。

  对于这种第三道路,有的用户表示颇为不解,GOLB BLOG TOOL为什么不以一个MSN插件的方式发布,而是要制作成一个全新的MSN Shell。的确从用户的角度来说,在功能一致的前提下,我宁可选择装了插件的MSN也不要另外一个MSN Shell——插件可以装几个,Shell却只能用一个,有局限性。在这一点上,笔者也持同样的看法。但是笔者认为更值得我们思考的事,为什么在IM与WEB融合的问题上,我们一定要纯粹的从客户终端角度去思考,老是想着要在用户的终端软件上做点手脚呢?其实有另一种方法可能成本更低,而且更加有创造性。

  你有玩过MSN机器人吗?没错,我说的就是从它身上发展出来的思路。所谓MSN机器人,其实也是一个与MSN兼容的Shell,但是与其他的Shell必须由最终用户安装不同,MSN机器人是采取类似服务器的安装方法,你在自己的服务器上安装这个Shell,并且利用对应注册的MSN帐号上线。其他的用户只需要把对应的MSN帐号加入自己的MSN好友,就可以通过类似以前DOS命令行的方式进行互动,从而获得MSN机器人需要的消息。如果把这个思路发展一下的话,就会发现很多好玩的应用。

  还是以电子邮件为例吧。假如我是XXX.net这个电子邮件服务商的用户,XXX.ne基于MSN推出了一个IM邮件提醒的功能,只需要对方的MSN机器人把我的MSN帐号加为好友,并且把这个帐号和我的XXX.net邮件账号绑定,那么一旦我的邮箱有了新的邮件,XXX.net架设的机器人就会通过MSN 短消息告诉我有了新的邮件,大致的内容是什么。这是不是一个非常简单而且使用的IM和WEB应用融合的范例?要知道,这种融合的方式既不需要改变用户的客户端,也不需要在用户的客户端上增加什么插件,只需要用户被作为一个好友添加,一切融合就由此开始了,这是不是一种非常简单而且有效的方法?当然,这种融合方法的一个重要缺陷就在于MSN 消息只能是文字或者图片的,这个就局限了融合的应用,不过即使如此,对于许多的WEB特别是新闻、资讯网站,即使是这种程度的融合,也足够能够开发出许多实际的新应用来的。

  事实上,目前阻碍这种思路实用化商用化的一个巨大阻碍就在于MSN的先天限制。在这里,我并不认为对于MSN通信协议的逆向工程利用会是一个问题(微软仅仅公开过MSN最初的通信协议,但是此后一直到如今V10的通信协议都是保密的),因为前面已经说过了,这是一种良性的兼容,微软恐怕应该鼓励才对。真正令人讨厌的在于MSN武断的最多150个好友的限制。要知道,我们目前看到的诸多MSN机器人都是被动式的,只需要用户添加他们,所以每个机器人的服务限制只是受到程序的负荷影响(比如机器人小布是每个3000人),但是笔者刚刚提及的那种与WEB应用融合的MSN机器人是主动式的,必须主动添加用户的MSN才行,这样就会直接面对每个机器人只能为最多150个用户服务的问题,对于那些有上万甚至几十万用户的服务上岂不是要同时启动成百上千个机器人才行。虽然150人的限制其实只是技术上的一个强硬规定,但是考虑到微软的实际情况,恐怕其不太可能为了一些MSN机器人的新应用而有所改变。

  如此一来,就只能期望国产的IM软件能够有所行动了。目前关产的IM软件UC已经有了类似MSN上机器人小布的被动式机器人,但是主动式的似乎还没有看到。至于QQ,似乎还没有什么太大的动静。其实笔者最寄予希望的还是IMU,既然其上场就是以开放的合作平台作为重要的卖点,那么类似主动式IM机器人这样的新应用,其应该是乐于见到而且加入到现有的发展战略中去的——毕竟比起用户定制版、插件这两种开放形式来,主动式IM机器人更加简单,更加方便。

  摇旗呐喊晚了,笔者在这里也就只能拭目以待,看看第一个与WEB应用融合的主动式IM机器人服务究竟花出谁家了。


用.NET开发MSN聊天机器人 - MSN聊天机器人开发揭秘

写在前面:
我不是开发人员,不是高手,就是自己比较爱玩。在技术上,没有什么喜欢摸索的精神,而是喜欢投机取巧。在这篇文章里,你也不能"少劳而获"地通过我的机器人修改出一个自己的机器人,因为自己觉得程序写的比较臭,所以不会open source。但是,如果你对.net或者C#有点了解的话,相信从这篇文章里,你可以找到一切所需的资源,来开发一个自己的,绝对可用的msn机器人。要和我的机器人聊天,可以加 [email protected],名字叫“塔奇克马”,是从动画片攻壳机动队得名。同时,你也可以去http: //www.guanqun.com,那里有一个和这个MSN机器人一样的网页聊天机器人,可以先聊聊看,尽量用中文聊。

这不是一篇新手入门的文章,如果你不知道什么是.NET,不了解数据库方面的哪怕是一丁点东西,建议你先看看。同时,也希望真正的高手不要笑话打击我,毕竟一个普通的,非开发人员的电脑爱好者通过摸索,并告诉大家怎样做一个好玩的东西,不是一件错事。

一、为什么要做MSN聊天机器人

1 我能想到的原因

最重要的是因为很好玩。你的MSN机器人说的话,一定体现你的性格(如果你希望这样的话)。当然,这是我的理由,作这个机器人的初衷仅仅是突然有一天自己想做。也许你也希望你的机器人可以帮助你做一些事情,类似一个专家系统或是客服系统等等。

2 现在的MSN聊天机器人

现在MSN机器人有很多,如果你加过MSN机器人,我想你列表上最多的是一个叫做“小布”或者是他兄弟姐妹一大堆的家伙们(http: //www.9zi.com),可能基于负载的考虑,每次上线你都可能被他们一家子的一堆加入好友的请求包围。还有一些所谓的“免费短信”机器人,我一直就是做SP的,我直接说,为了不耽误你们赚钱,我不对这种机器人加以什么评论。可以提一下MsgerAI( [email protected])这个机器人,开发它的这位老兄非常希望做一个可以像人一样具有智能的东西,虽然可能在他有生之年都无法完成,不过我还是祝他成功。毕竟有梦想就是好的,而且这个机器人现在也可以为他完成些工作( http://www.funnyok.net/nlp)。还有一些其他的MSN机器人,比如专门提供信息查询服务的,帮你搜索google的等等。MSN进行时里面有列表( http://www.msning.com),自己去看看就好了。

二、为什么用.NET

其实理由很简单。C#和Java很像,但是Java我实在找不出一个非常好用的,符合自己使用习惯的IDE来。而C#就不同,Vs.NET(http: //msdn.microsoft.com/vstudio/) 当然最好用,C# Builder( http://www.borland.com/csharpbuilder/)也不错,连SharpDevelop(http: //www.icsharpcode.net/OpenSource/SD/)用起来都相当舒服。所以选择.NET比较好。

另外,.NET在开发上非常方便,只要你有一点点开发基础,用.NET写程序就不是很难。我是站在一个使用者而非开发者的角度,不用去钻研太多技术层面,或是优化的东西,我没那能力也不想进微软研究院。

建议你使用最新版Visual Studio.NET,可以省去很多麻烦的事儿。

同时,.NET开发可以找到的资源也有很多,我们接下来会提。

三、你要一个什么样的聊天机器人

1 开发前的设想

我这里讨论的就是“聊天机器人”这个概念,意思是,他能做的就是陪你聊天。你要有一个程序去“教”他说话,同时要让他明白话语中包含的大概意义,还能够做基本上不怎么离谱的回答。

2 还可以让他做什么

你还可以让他做很多其他的事情,比如查询ip,手机号码,注册号,航班号,或者直接让他去查google,帮你搜索。这些也都不是什么麻烦的事情,只要你想。

四、先让机器人开口说话

不管你的机器人聪不聪明,让他能在MSN上象摸象样地回答是最重要的。所以,你需要有一个MSN帐号,连接到MSN服务器,取得各种服务器的消息,同时发送消息回服务器。

当然,你可以分析MSN的协议( http://www.hypothetic.org/docs/msn/index.php),自己写通讯部分。不过我提过,我是个喜欢投机取巧的人,所以,找个能用的接口用就好了。所以,我找了些MSN的开发接口。

MSNHelper:
http://sourceforge.net/projects/msnphelper/

dotMSN:
http://members.home.nl/b.geertsema/dotMSN/

这两个都是为.NET开发的,我用dotMSN,它使用MSNP8协议。注意dotMSN不要用sourceforge上的版本,要用上面给出的地址。
接下来,下载这个例子:
http://members.home.nl/b.geertsema/dotMSN/example/Example.zip

用vs.net打开,编译,执行。

看懂了吧。登录之后随便双击列表上的某个人,会发送给这个人一句"Hello world!"。你已经可以不通过MSN原来的程序,而直接和人说话了。

这部分的代码是这样的:

private void ContactJoined(Conversation sender, ContactEventArgs e)
{
// someone joined our conversation! remember that this also occurs when you are
// only talking to 1 other person. Log this event.
Log.Text += e.Contact.Name + " joined the conversation.\r\n";

// now say something back. You can send messages using the Conversation object.
sender.SendMessage("Hello world!");
}

意思是当对方加入聊天后,你就给他发一个"Hello world!"的消息过去。这时候如果你列表上的人双击你的名字,也同样会收到一个Hello World!。

五、让机器人懂中文

1 数据库

因为我们要做中文聊天机器人,语料库的大小直接关系到你的机器人是不是聪明。由于自己的习惯,我用了mysql作为存放语料库和中文分词库的数据库。而且 mysql速度极快。当然,你要用Access或者Sql Server,完全可以,而且更容易些。.NET调用Mysql的库可以在这里找到MySQL Driver CS
http://sourceforge.net/projects/mysqldrivercs/


2 整句匹配

整句匹配这个概念很简单。聊天嘛,不认识的人一般都会要上来就说“你好”,或者“hi~~”之类的。这种话通常很简单,而且没有什么太多的变化,直接让机器人回答就行了。比如对方说“你好”,机器人看到这个“你好”,就直接回答“你好”,就可以了。或者对方说"88",你可以让机器人说“再见”,或者88什么的。。这就叫整句匹配。就是机器人拿到整个的句子,在库里面一查,啊,库里有这句话怎么回答,挑出一句回答过去,对方不会觉得这个机器人笨。

甚至如果对方说“你好笨”,你让机器人回答“我才不笨呢”,对方一定会觉得,这个机器人还行,还知道别人说他笨。

3 中文分词

一个聊天机器人当然要懂些中文。中文处理的基础就是中文分词。分词是什么?“分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。”这个定义是我抄的。请查阅这篇文章: http://www.hylanda.com/center/knowledge.htm 他们做中文分词应该有一定成绩。国内的分词系统,ICTCLAS做得也比较好。有VC的源代码,可以下来看看。
http://www.nlp.org.cn/project/project.php?proj_id=6

有人会说,这个东西我不懂,我也没研究过。其实我也不懂。只不过,如果不做中文分词,聊天机器人就只能停留在证据匹配的地步。我们可以使用最大匹配法,对聊天机器人接收到的话做简单的分词。关于算法,请参阅詹卫东先生的这个讲义,估计你一看就明白了。

课程名称:中文信息处理基础

http://ccl.pku.edu.cn/doubtfire/ ... ing/2002_2003_1.htm

下载这个PPT讲义: http://ccl.pku.edu.cn/doubtfire/ ... ts/Chapter_07_1.ppt

分词算法不需要太复杂,简单点就好。

另外分词算法需要一个中文分词库。我提供了一个mysql的,这里可以下载。导入到你的mysql里面就可以。其他数据库其实把sql语句简单改改也可以用。
中文分词库下载: http://www.guanqun.com/down/wordlist.rar

4 词语的匹配

仅仅会分词还不够,如果真的要让机器人了解人说的话,肯定需要一些人工智能的算法。我们就是做个机器人玩玩,没必要研究的那么深。人工智能走到现在,太聪明的聊天机器人也少之又少。而且,让专业的研究人员去研究就好了,我们仅仅就是玩玩。所以呢……我们就用一个最简单的办法。我们的方法是,让机器人找这一句话的关键词,这句话大概的词性搭配,再去语料库里面找到符合这样规则的回答的话。

举个简单的例子:
比如对方说:

“你真好玩”

我们先用分词算法,把这句话分成

“你 真 好玩”,

然后找出关键词“好玩”。同时把这句话的词性搭配也记录下来。 这样,当找到关键词“好玩”在语料库中的时候,我们再来找是否有类似这这句话词性搭配的回答,如果有,随机回答出一句:“哈哈。。。我就喜欢你这么说。”,这样,可以给聊天者比较好的感觉。

那么问题来了,如何找出关键词呢? 我的方法是……(比较烂,但是通常有效),找出这句话中长度最长的词作为关键词。没有为什么,因为这样速度会快些。如果一句话中所有的词都被扫描成为关键词,再去查库,会出现些匹配上的问题。(不科学,但是通常有效)。

五、让机器人再“聪明”些

1 整句匹配语料库的设计

第一步当然是做你的整句匹配语料库。语料库都要自己写的,不要偷懒。找出别人最常说的话,比如你好谢谢对不起什么的,多放些回答在里面,免得每次回答都是一样的,然后要回答的时候,先写一句sql来查询,如

select * from reply where `key` = '"+sentense+"' order by rand() limit 1

把找到的话直接回复过去就可以了。如果找不到整句匹配,再做分词处理。

2 分词匹配语料库的设计

因为我们分词算法也没有经过什么优化,同时,我们找出关键字的办法也不是那么的好,所以,你给出的回答一定要不那么清晰。说白了就是,回答的话要有些“含糊不清”才可以。目标就是,让人觉得机器人对他说的话已经理解了,回答出来的还算比较“对路”。不要求100%对路,只要有40%以上对路,聊天的人基本就可能会接受。同时,回答的语料库,最好可以引导对方再次回答的时候,可以说出你语料库里面有的,最好是可以整句匹配的句子。

举个好玩的例子:

问题:你是男的还是女的?/你是男的还是女的/你是男的还是女的? (是否有标点符号没关系,我们要记录句子的词性搭配,同时,要对标点符号做些处理)

像这样一句话,我们可以通过分词,找出关键词:“还是”,而且通过判断词性,可以知道,这是一句问句。而且问的是在两种情况之间选择。(当然,我们通过简单的算法,没法知道这句话其实是问性别)

对于这样的问题,你的机器人怎么回答?其实很简单,首先,回答要“对路”,尽量不让人觉得答非所问,至少让人觉得,你的机器人是知道对方在问什么的。所以,我的机器人这样回答:

机器人回答:都是。。。哈哈

因为回答的是聊天用语,而且带点开玩笑的意味,所以会使聊天者觉得,这个机器人还不是那么笨。

这只是一个简单的例子。很多具体的句子还得你自己去分析。当然,语料库越多,机器人懂的就越多,也就越聪明了。

3 匹配不到关键字怎么办

语料库不是很多的情况下,很可能我们的分词算法匹配不到合适的回答来应付。所以我们还要另外做一个语料库,用来在实在匹配不到关键词的时候,进行回答。这样的回答比较需要类似“算卦”的人的回答技巧,因为对方可能说任何话,而我们的机器人不明白。所以,要想办法“蒙混过关”,同时,尽量引导对方向你机器人可能回答的方面说。你可以试着跟“小布”聊聊天,会发现它回答不出来的时候,就会随便挑一句“佛经”来说。

其实最重要的一个技巧就是,学习一下算卦的人说的话,都是云里雾里的,让人摸不到头脑,还觉得可能是对的。我们就要让机器人学习这种技巧,来达到看上去“聪明”的目的。

最后的话:
其实写这样的一个机器人程序很快的,如果熟悉些的话,估计一天应该就可以写出来。我大概用了一天半,还加上准备些语料库的时间。如果你真想做个稍稍“聪明些”的机器人来玩,这篇文章应该可以为你剩下至少3-5个小时的找资料的时间。如果你懒得自己研究,也有别的公司做的只能整句匹配的程序可以下载,自己下一个玩玩也就算了。

原文最早发表于blog: http://bot.donews.net/bot

作者Blog: http://blog.csdn.net/cmoremore/


JAVA MSN机器人主要代码开发

一直想把这个JAVA机器人的主要代码公开(虽然不是什么好东西~~但偶们要坚持开源~~提供给其他刚接触JAVA MSN的人参考)~~虽然偶和WENSI写的比较初级而且方法很多都比较傻,想在完善下在发布,不过昨天我尝到了不开源的弊端~~~我的系统突然坏了,C盘数据都没保留下来,其中就包括了以前MSN机器人的代码备份,如果我要是开源了还能在网上找到~~-_-!!

说明:先将java msn里的lib导入的包里,然后使用下面的代码,这个代码完成MSN机器人的操作。

(可能有比较笨的方法,变量方法的命名也不规范,还请指正,如果哪位会设置头像或建立数据库连接池请说说怎么做,现在这个机器人不能支持很多人,谢谢)

import java.sql.*;
import java.net.*;
import rath.msnm.*;
import rath.msnm.MSNMessenger;
import rath.msnm.SwitchboardSession;
import rath.msnm.UserStatus;
import rath.msnm.entity.Group;
import rath.msnm.entity.MsnFriend;
import rath.msnm.event.MsnAdapter;
import rath.msnm.msg.MimeMessage;
import java.util.*;
import com.google.soap.search.*;
import com.google.soap.search.GoogleSearch;
import com.google.soap.search.GoogleSearchResult;
import java.io.*;

/**
* MSN消息事件处理类
* @author 瞄瞄,wensi
*/
public class MSNAdapter
    extends MsnAdapter {
  MsnIn msnin = new MsnIn();
  static String searchcon;
  static String firstfriend;
  static String friendlist;
  String rewa = "";
  String msg3;
  MSNMessenger messenger;
  String url = "jdbc:odbc:chat";  ////数据源
  String login = "";            //用户名
  String password = "";      //密码
  Connection connection = null; // 声明Connection接口对象connection
  ResultSet rSet = null; // 定义数据库查询的结果集
  ResultSet keySet = null;
  Statement statement = null; // 定义查询数据库的Statement对象

  public MSNAdapter(MSNMessenger messenger) {
    this.messenger = messenger;
  }

  public void msnmsg(String msg) {
    msg3 = msg;
    msnin.textArea2.append(msg3 + "
");
  }

  /**
   * 某人正在输入信息
   */
  public void progressTyping(
      SwitchboardSession ss,
      MsnFriend friend,
      String typingUser) {

    System.out.println(friend.getLoginName() + "正在输入信息...");
  }

  /**
   * 收到消息的时候执行该方法
   */
  public void instantMessageReceived(
      SwitchboardSession ss,
      MsnFriend friend,
      MimeMessage mime) {
    System.out.print("接收到消息:" + friend.getFriendlyName() + "->");
    System.out.println(mime.getMessage());
    msnmsg(friend.getFriendlyName() + "说:" + mime.getMessage());

    int first = 0;
    if (firstfriend != null) {
      StringTokenizer stt = new StringTokenizer(firstfriend, "$");
      String[] subStrt2 = new String[stt.countTokens()];
      int a = 0;
      for (int i = 0; i < subStrt2.length; i++) {
        subStrt2[i] = stt.nextToken();
        if (subStrt2[i].equals(friend.getLoginName())) {
          a = 1;
          break;
        }
      }
      if (a == 0) {
        firstfriend = firstfriend + "$" + friend.getLoginName();
        first = 1;
        try {
          mime.setMessage("|0_0| 我是机器人瞄,如果想问我问题请尽管问
(co) 如果你想告诉我些知识请按照: study,问题内容,答案内容 的格式告诉我
(I) 如果想使用google搜索请输入: google,搜索内容
:-D 帮助请输入: help或?
(M) 想和我的开发人员说话请加:[email protected],[email][email protected][/email]");
          messenger.sendMessage(friend.getLoginName(), mime);
          msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:帮助");

        }
        catch (IOException ex1) {}
      }
    }
    else {
      firstfriend = firstfriend + "$" + friend.getLoginName();
      first = 1;
      try {
        mime.setMessage("|0_0| 我是机器人瞄,如果想问我问题请尽管问
(co) 如果你想告诉我些知识请按照: study,问题内容,答案内容 的格式告诉我
(I) 如果想使用google搜索请输入: google,搜索内容
:-D 帮助请输入: help或?
(M) 想和我的开发人员说话请加:[email protected],[email][email protected][/email]");
        messenger.sendMessage(friend.getLoginName(), mime);
        msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:帮助");
      }
      catch (IOException ex1) {}
    }
    if (first == 0) { //对话开始
      if (mime.getMessage().equals("help") || mime.getMessage().equals("帮助") ||
          mime.getMessage().equals("?")) { //是否帮助
        mime.setMessage("|0_0| 我是机器人瞄,如果想问我问题请尽管问
(co) 如果你想告诉我些知识请按照: study,问题内容,答案内容 的格式告诉我
(I) 如果想使用google搜索请输入: google,搜索内容
:-D 帮助请输入: help或?
(M) 想和我的开发人员说话请加:[email protected],[email][email protected][/email]");
        msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:帮助");
      }
      else { //是否帮助
        if (mime.getMessage().indexOf(",") > 0) { //判断模式开始
          StringTokenizer ms = new StringTokenizer(mime.getMessage(), ",");
          int mspart;
          mspart = ms.countTokens();
          String[] message = new String[ms.countTokens()];
          for (int i = 0; i < message.length; i++) {
            message[i] = ms.nextToken();
          }
          if ( (mspart == 3) && (message[0].equals("学习")) ||
              (message[0].equals("study")) || (message[0].equals("STUDY"))) { //学习模式
            try {
             Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
              connection = DriverManager.getConnection(url);
              statement = connection.createStatement();
              String sql1 =
                  "insert into holophrase (question,answer) values ('" +
                  message[1] +
                  "','" + message[2] + "')";
              statement.executeUpdate(sql1);
              mime.setMessage("学习完毕,在问问我啊。帮助请输入:? 或 help");
              msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:学习完毕");
            }
            catch (Exception ex) {
              ex.printStackTrace();
            }
          }
          else if ( (mspart == 2) && (message[0].equals("google")) ||
                   (message[0].equals("GOOGLE"))) { //学习模式结束google搜索模式
            String titleall = "";
            if (searchcon != null) {
              String searchcon2 = searchcon;
              searchcon = "";
              StringTokenizer stt = new StringTokenizer(searchcon2, "$");
              String[] subStrt2 = new String[stt.countTokens()];
              int a = 0;
              int b = 0;
              for (int i = 0; i < subStrt2.length; i++) {
                subStrt2[i] = stt.nextToken();
                b++;
                if (subStrt2[i].equals(friend.getLoginName())) {
                  a = 1;
                  break;
                }
              }
              if (a == 0) {
                searchcon2 = searchcon2 + "$" + friend.getLoginName() + "$" +
                    message[1];
                searchcon = searchcon2;
              }
              else {
                subStrt2[b + 1] = message[1];
                for (int i = 0; i < subStrt2.length; i++) {
                  searchcon = searchcon + "$" + subStrt2[i];
                }
              }
            }
            else {
              searchcon = searchcon + "$" + friend.getLoginName() + "$" +
                  message[1];
            }
            try {
              GoogleSearch search = new GoogleSearch();
              search.setKey("AhGBLehQFHICcFbB5lYF9lDTZ4G71AzH");
              search.setQueryString(message[1]);
              GoogleSearchResult result = search.doSearch();
              int numResults = result.getEstimatedTotalResultsCount();
              int numResults2 = numResults / 10;
              GoogleSearchResultElement[] resultElements = result.
                  getResultElements();
              int startIndex = result.getStartIndex() - 1;
              int endIndex = result.getEndIndex() - 1;
              for (int i = startIndex; i <= endIndex; i++) {
                GoogleSearchResultElement resultElement = resultElements[ (i)];
                String title = resultElement.getTitle();
                StringTokenizer st2 = new StringTokenizer(title, "<b>");
                String[] subStr2 = new String[st2.countTokens()];
                String title2 = "";
                for (int i2 = 0; i2 < subStr2.length; i2++) {
                  subStr2[i2] = st2.nextToken();
                  title2 = title2 + subStr2[i2];
                }
                StringTokenizer st3 = new StringTokenizer(title2, "</b>");
                String[] subStr3 = new String[st3.countTokens()];
                String title3 = "";
                for (int i3 = 0; i3 < subStr3.length; i3++) {
                  subStr3[i3] = st3.nextToken();
                  title3 = title3 + subStr3[i3];
                }
                String url = resultElement.getURL();
                titleall = titleall + title3 + "
" + url + "
";
                System.out.println(title);
                System.out.println(url);
              }
              if (startIndex == 0) {
                mime.setMessage(titleall + "
搜索结果共" + numResults2 +
                                "页
当前页:0
看其他纪录输入:go->页数");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:google搜索成功");
              }
              else {
                mime.setMessage(titleall + "
搜索结果共" + numResults2 +
                                "页
当前页:0
看其他页纪录输入:go->页数;看第一页纪录输入:go->0");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:google搜索成功");
              }
            }
            catch (Exception gsf) {
              mime.setMessage("Google Search Fault: " + gsf); //.getMessage());
              System.out.println("Google Search Fault: " + gsf); //.getMessage());
              msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:google搜索失败");
            }
          }
         
          else { //google搜索模式结束数据库查询开始
            try {
                Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
              connection = DriverManager.getConnection(url);
              statement = connection.createStatement();
              String sqlStr1 =
                  "SELECT question,answer FROM holophrase WHERE question like '%" +
                  mime.getMessage() + "%'";
              rSet = statement.executeQuery(sqlStr1);
              if (rSet.next() == false) { //分词开始
                int br = 0; //比较有结果为1
                String key = mime.getMessage();
                for (int i = key.length(); i > 1; i--) {
                  if (br == 1) {
                    break;
                  }
                  for (int num = 0; num + i <= key.length(); num++) {
                    if (br == 1) {
                      break;
                    }
                    String subS = key.substring(num, num + i);
                    String sqlKey = "select * from pivotal order by pivotalid";
                    rSet = statement.executeQuery(sqlKey);
                    while (rSet.next()) {
                      if (br == 1) {
                        break;
                      }
                      String pivotal = rSet.getString("pivotal");

                      StringTokenizer pivotal1 = new StringTokenizer(pivotal,
                          "/");
                      String[] pivotal2 = new String[pivotal1.countTokens()];
                      for (int i3 = 0; i3 < pivotal2.length; i3++) {
                        pivotal2[i3] = pivotal1.nextToken();

                        if (pivotal2[i3].equals(subS)) {
                          String p = rSet.getString("content");
                          System.out.println("分词选择:" + p + "
");
                          mime.setMessage(p);
                          msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" +
                                 p);
                          br = 1;
                          break;
                        }
                      }
                    }
                  }
                }
                if (br == 0) { //如果比较没结果
                  int count = (int) (Math.random() * 78);
                  String sqlStr2 = "SELECT * FROM cope WHERE copeid =" + count +
                      "";
                  ResultSet rs = statement.executeQuery(sqlStr2);
                  rs.next();
                  String returnInfo1 = rs.getString(2);
                  mime.setMessage(returnInfo1 + "
" +
                                  "[如果你想告诉我正确的答案,请按照 study,问题,答案 的格式教我!-_-!]");
                  System.out.print(returnInfo1 + "
");
                  msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" +
                         returnInfo1);
                }
                statement.close();
                connection.close();
              } //分词结束
              else {
                String s = "";
                String s2 = rSet.getString(2);
                int a = 0;
                while (rSet.next()) {
                  s = s + "$" + rSet.getString(2);
                  a++;
                }
                if (a != 0) {
                  StringTokenizer stt = new StringTokenizer(s, "$");
                  String[] subStrt2 = new String[stt.countTokens()];
                  for (int i = 0; i < subStrt2.length; i++) {
                    subStrt2[i] = stt.nextToken();
                  }
                  int count;
                  count = (int) (Math.random() * a);
                  s = subStrt2[count];
                }
                else {
                  s = s2;
                }
                String returnInfo2 = "";
                if (s.indexOf("$n") > 0) { //换行
                  StringTokenizer st = new StringTokenizer(s,
                      "$n");
                  String[] subStr = new String[st.countTokens()];
                  for (int i = 0; i < subStr.length; i++) {
                    subStr[i] = st.nextToken();
                    returnInfo2 = returnInfo2 + subStr[i] + "
";
                  }
                  mime.setMessage(returnInfo2);
                  System.out.print(returnInfo2 + "
");
                  msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" +
                         returnInfo2);

                }
                else {
                  mime.setMessage(s);
                  System.out.print(s + "
");
                  msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" + s);
                }
                statement.close();
                connection.close();
              }
            }
            catch (Exception ex) {
              ex.printStackTrace();
            }
          }
        }
        else if (mime.getMessage().indexOf("->") >= 0) { //判断模式结束 google搜索换页开始
          String titleall = "";
          StringTokenizer st2 = new StringTokenizer(searchcon, "$");
          String[] subStr2 = new String[st2.countTokens()];
          for (int i = 0; i < subStr2.length; i++) {
            subStr2[i] = st2.nextToken();
          }
          int a = 0;
          while (!subStr2[a].equals(friend.getLoginName())) {
            a++;
          }
          StringTokenizer stk = new StringTokenizer(mime.getMessage(), "->");
          String[] subStrk = new String[stk.countTokens()];
          for (int i = 0; i < subStrk.length; i++) {
            subStrk[i] = stk.nextToken();
          }
          try {
            GoogleSearch search = new GoogleSearch();
            search.setKey("AhGBLehQFHICcFbB5lYF9lDTZ4G71AzH");
            search.setQueryString(subStr2[a + 1]);
            int aa = Integer.parseInt(subStrk[1]);
            if (aa * 10 < 0) {
              mime.setMessage("错误,无法翻页");
              msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:翻页错误");
            }
            else {
              int startResult = aa * 10;
              search.setStartResult(startResult);
              int maxResult = 10;
              search.setMaxResults(maxResult);
              GoogleSearchResult result = search.doSearch();
              int numResults = result.getEstimatedTotalResultsCount();
              int numResults2 = numResults / 10;
              GoogleSearchResultElement[] resultElements = result.
                  getResultElements();
              int startIndex = result.getStartIndex() - 1 - startResult;
              int endIndex = result.getEndIndex() - 1 - startResult;
              for (int i = startIndex; i <= endIndex; i++) {
                GoogleSearchResultElement resultElement = resultElements[ (i)];
                String title = resultElement.getTitle();
                StringTokenizer st3 = new StringTokenizer(title, "<b>");
                String[] subStr3 = new String[st3.countTokens()];
                String title2 = "";
                for (int i2 = 0; i2 < subStr3.length; i2++) {
                  subStr3[i2] = st3.nextToken();
                  title2 = title2 + subStr3[i2];
                }
                StringTokenizer st4 = new StringTokenizer(title2, "</b>");
                String[] subStr4 = new String[st4.countTokens()];
                String title3 = "";
                for (int i3 = 0; i3 < subStr4.length; i3++) {
                  subStr4[i3] = st4.nextToken();
                  title3 = title3 + subStr4[i3];
                }
                String url = resultElement.getURL();
                titleall = titleall + title3 + "
" + url + "
";
                System.out.println(title);
                System.out.println(url);
              }
              if (startResult == 0) {
                mime.setMessage(titleall + "
搜索结果共" + numResults2 +
                                "页
当前页:" + aa + "
看其他纪录输入:go->页数");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:google搜索成功");
              }
              else {
                mime.setMessage(titleall + "
搜索结果共" + numResults2 +
                                "页
当前页:" + aa +
                                "
看其他页纪录输入:go->页数;看第一页纪录输入:go->0");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:google搜索成功");
              }
            }
          }
          catch (Exception gsf) {
            mime.setMessage("Google Search Fault: " + gsf); //.getMessage());
            System.out.println("Google Search Fault: " + gsf); //.getMessage());
            msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:google搜索失败");
          }
        }
        else { //google搜索换页结束
          try {
             Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
            connection = DriverManager.getConnection(url);
            statement = connection.createStatement();
            String sqlStr1 =
                "SELECT question,answer FROM holophrase WHERE question like '%" +
                mime.getMessage() + "%'";
            rSet = statement.executeQuery(sqlStr1);
            if (rSet.next() == false) { //分词开始
              int br = 0; //比较有结果为1
              String key = mime.getMessage();
              for (int i = key.length(); i > 1; i--) {
                if (br == 1) {
                  break;
                }
                for (int num = 0; num + i <= key.length(); num++) {
                  if (br == 1) {
                    break;
                  }
                  String subS = key.substring(num, num + i);
                  String sqlKey = "select * from pivotal order by pivotalid";
                  rSet = statement.executeQuery(sqlKey);
                  while (rSet.next()) {
                    if (br == 1) {
                      break;
                    }
                    String pivotal = rSet.getString("pivotal");

                    StringTokenizer pivotal1 = new StringTokenizer(pivotal, "/");
                    String[] pivotal2 = new String[pivotal1.countTokens()];
                    for (int i3 = 0; i3 < pivotal2.length; i3++) {
                      pivotal2[i3] = pivotal1.nextToken();

                      if (pivotal2[i3].equals(subS)) {
                        String p = rSet.getString("content");
                        System.out.println("分词选择:" + p + "
");
                        mime.setMessage(p);
                        msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" +
                               p);
                        br = 1;
                        break;
                      }
                    }
                  }
                }
              }
              if (br == 0) { //如果比较没结果
                int count = (int) (Math.random() * 78);
                String sqlStr2 = "SELECT * FROM cope WHERE copeid =" + count +
                    "";
                ResultSet rs = statement.executeQuery(sqlStr2);
                rs.next();
                String returnInfo1 = rs.getString(2);
                mime.setMessage(returnInfo1 + "
" +
                                "[如果你想告诉我正确的答案,请按照 study,问题,答案 的格式教我!-_-!]");
                System.out.print(returnInfo1 + "
");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" +
                       returnInfo1);
              }
              statement.close();
              connection.close();
            } //分词结束
            else {
              String s = "";
              String s2 = rSet.getString(2);
              int a = 0;
              while (rSet.next()) {
                s = s + "$" + rSet.getString(2);
                a++;
              }
              if (a != 0) {
                StringTokenizer stt = new StringTokenizer(s, "$");
                String[] subStrt2 = new String[stt.countTokens()];
                for (int i = 0; i < subStrt2.length; i++) {
                  subStrt2[i] = stt.nextToken();
                }
                int count;
                count = (int) (Math.random() * a);
                s = subStrt2[count];
              }
              else {
                s = s2;
              }
              String returnInfo2 = "";
              if (s.indexOf("$n") > 0) { //换行
                StringTokenizer st = new StringTokenizer(s,
                    "$n");
                String[] subStr = new String[st.countTokens()];
                for (int i = 0; i < subStr.length; i++) {
                  subStr[i] = st.nextToken();
                  returnInfo2 = returnInfo2 + subStr[i] + "
";
                }
                mime.setMessage(returnInfo2);
                System.out.print(returnInfo2 + "
");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" +
                       returnInfo2);
              }
              else {
                mime.setMessage(s);
                System.out.print(s + "
");
                msnmsg("#机器人回答:" + friend.getFriendlyName() + ",内容:" + s);
              }
              statement.close();
              connection.close();
            }
          }
          catch (Exception ex) {
            ex.printStackTrace();
          }
        }
      }

      try { //输出
        messenger.sendMessage(friend.getLoginName(), mime);
      }
      catch (IOException ex1) {} //输出

    } //对话结束
  }

  /**
   * 登录成功后执行该方法
   */
  public void loginComplete(MsnFriend own) {
    System.out.println(own.getLoginName() + " Login OK");
    msnmsg("*系统消息:" + own.getLoginName() + "登陆成功");
  }

  /**
   * 登录失败后执行该方法
   */
  public void loginError(String header) {
    System.out.println("Login Failed: " + header);
    msnmsg("*系统消息:登陆失败" + header);
  }

  /**
   * 好友离线时执行该方法
   */
  public void userOffline(String loginName) {
    System.out.println("USER " + loginName + " Logout.");
    msnmsg("*系统消息:" + loginName + "下线");
  }

  /**
   * 好友上线时执行该方法
   */
  public void userOnline(MsnFriend friend) {
    System.out.println("USER " + friend.getFriendlyName() + " Login.");
    msnmsg("*系统消息:" + friend.getFriendlyName() + "上线");

  }

  /**
   * 有人加我为好友时执行
   */
  public void whoAddedMe(MsnFriend friend) {
    System.out.println("USER " + friend.getLoginName() + " Addme.");
    msnmsg("*系统消息:" + friend.getLoginName() + " 将我加入好友");
    try {
      messenger.addFriend(friend.getLoginName());
      msnmsg("*系统消息:" + friend.getLoginName() + " 加入我的好友");
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }


  /**
   * 有人把我从好友列表中删除时执行
   */
  public void whoRemovedMe(MsnFriend friend) {
    System.out.println("USER " + friend.getLoginName() + " Remove me.");
    msnmsg("*系统消息:" + friend.getLoginName() + " 将我删除");
    try {
      messenger.removeFriend(friend.getLoginName());
      msnmsg("*系统消息:" + friend.getLoginName() + " 被我删除");
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }

}

MSN机器人梦工厂

前几日看到网友“瞄瞄”的一个关于msn机器人的blog文章。然后在google上搜索了一下,原来,现在流行MSN机器人了!

由于MSN Messenger公开了它的通讯协议,使得很多人加入到编写自己的机器人的行列。msn协议全文共有20页,相比smtp协议来说是少得多了。但对于大多数开发者来说,根据原始协议来写机器人程序仍然是一件困难的事(更何况协议内容是英文的)。

也是在google上,我发现在Java领域,比较流行的机器人开发工具是一个韩国人写的一个jMSN开发包。它将复杂的协议封装成简单易用的Java API供使用者调用。(下载地址: http://sourceforge.net/projects/jmsn)

话虽这么说,使用起来还是有点阻碍的。整个jMSN开发包的文档全部是韩文写成,对于我们这些连英文都很吃力的人来说,这简直就是天书!

不过不要紧,欢迎大家进入机器人梦工厂,同我一起来探索MSN机器人的制造过程。

现在我们就开始实现自制MSN机器人梦想。

我们先不要去研究通篇韩文的开发文档。先跟笔者一起来研究一个有趣的机器人,看看它是怎么工作的。然后再一步步深入研究机器人的工作原理。

这是一个有趣的机器人,你对它说什么,它就应什么。我把它叫做应声虫机器人。来看看它的结构:

import java.util.Properties;

import rath.msnm.MSNMessenger;
import rath.msnm.SwitchboardSession;
import rath.msnm.UserStatus;
import rath.msnm.entity.MsnFriend;
import rath.msnm.event.MsnListener;
import rath.msnm.ftp.VolatileDownloader;
import rath.msnm.ftp.VolatileTransferServer;
import rath.msnm.msg.MimeMessage;

/**
* MSN应声虫机器人。
* @author Turbo Chen
* @create 2004-7-29
*/
public class YesmanRobot
{
    public static void main(String[] args)
    {
        MSNMessenger msn = new MSNMessenger("[email][email protected][/email]", "xxxxxxxx");
        msn.setInitialStatus(UserStatus.ONLINE);
        msn.addMsnListener(new YesmanRobotAdapter(msn));
        msn.login();
    }
   
   
}

class YesmanRobotAdapter implements MsnListener
{

    MSNMessenger msn;

    public YesmanRobotAdapter(MSNMessenger msn)
    {
        this.msn = msn;
    }

    /**
     * 收到消息事件。当收到消息时,会自动调用此方法。
     */
    public void instantMessageReceived(SwitchboardSession ss, MsnFriend friend,
            MimeMessage mmsg)
    {
        try
        {
            //发送相同的回复信息给发送者
            MimeMessage newMsg = new MimeMessage(
                    "我是MSN应声虫,你说啥我应啥:" + mmsg.getMessage());
            newMsg.setKind(MimeMessage.KIND_MESSAGE);
            System.out.println(newMsg.getMessage());
            msn.sendMessage(friend.getLoginName(), newMsg);
        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

   ....   
}

其中YesmanRobot是机器人的主类,为了让机器人工作,要先让它登入到MSN才行,相关代码如下:

        MSNMessenger msn = new MSNMessenger(" [email protected]", "xxxxxxxx");
        msn.setInitialStatus(UserStatus.ONLINE);
        msn.addMsnListener(new YesmanRobotAdapter(msn));
        msn.login();

在这里创建了一个MSNMessenger对象,传入登入帐号和密码,使用setInitialStatus方法设置它登入的的状态为'在线',最后是调用login方法登入。

为了使机器人可以达到“应声虫”的功能,在登入之前,我们为它添加了一个监听器。这个监听器是MSNListener的一个实现类。在这里我们实现了一个YesmanRobotAdapter类,它里面只实现一个instantMessageReceived方法,当有消息送给机器人时,会触发此方法,在这个方法里,我们的机器人将对方送过来的消息又送回给了对方。这样就实现的应声虫的功能。

在实际的完整例子中,你会发现MSNListener有多达28个接口,也就是说它除了可以监听收到消息的事件,还提供了许多其它的事件供我们使用。在以后的文章中,我们会慢慢的接触到这些事件。

通过这个应声虫机器人,我们知道,实现自己功能的机器人一点都不难。只需要在instantMessageReceived方法中处理收到的消息并回应,就变成你自己的机器人了。在后面的内容中,我们会更深入机器人内部,看看它的工作原理。

废话少说。接上回。

由上回的一个应声虫机器人的例子,我们初步了解到jmsn开发包的基本用法,我先画一个它的工作原理图给大家:见附件1

MSNMessenger为主程序。它包含一个NotificationProcessor,这个一个通讯事件的处理器,本质上它是一个独立运行的线程。MSNMessenger提供许多命名友好的方法来向其它联系人(透过服务器)发送通讯命令。发送通讯命令都是通过NotificationProcessor处理器在后台(独立线程)处理的。

所有的通讯命令都封装在命名友好的方法中发送,这样你就勿需了解MSNMessenger的底层通讯协议。MSNMessenger提供了许多实用的方法来调用。比如有登入和注销(login, logout),添加或删除好友(addFriend, removeFriend),建立和删除群组(addGroup, removeGroup),邀请友人聊天(doCall),或者邀请某人加入到现有的聊天中等等。有了这些现成的方法,你可以让你的机器人变成非常强大,当然,也完全可以用它做一个替代Microsoft Messenger的聊天工具。

命令通过处理器发送出去之后,并不是马上返回。它是在后台由处理器负责运行的。也就是说,你调用了登入命令之后,如果想马上添加某人为好友,这时程序会报错。登入过程可能是一个慢长的过程,它要与服务器通讯,要等待服务器返回回应信息。而处理器在后台做这些事情,做完后,它会通过你注册在MSNMessenger类中的MSNListener监听器通知你。

所以不要试者一连串的执行MSNMessenger中提供某些方法(主要是指前面提到的那些类似方法),你应该在自己的监听器中等待事件完成后的通知你后再执行相应命令。

MSNListener是一个接口,提供了多达28种事件通知方法。包括登入完成,好友上线或离线,开始会话或结束会话,收到新消息,有谁添加自己为好友等事件。

这次的说教就到这。最后我们来改进一下上次的应声虫程序,让它可以在某人上线后主动发一个消息给他打招呼(不然谁知道你是个应声虫 )。

代码别的地方都一样,只是在YesmanRobotAdapter新实现了两个方法:

    /**
     * 好友上线时开始与其对话。
     */
    public void userOnline(MsnFriend friend)
    {
        try
        {
            System.out.println("开始邀请"+friend.getLoginName()+"会话");
            msn.doCall(friend.getLoginName());

        } catch (IOException e)
        {
            // TODO 处理异常
            e.printStackTrace();
        }
    }

    /**
     * 会话开始。
     */
public void switchboardSessionStarted( SwitchboardSession ss )
{
        try
        {

            if ( ss==null ) return;
            
            String toFriend = ss.getMsnFriend().getLoginName();

            if ( msn.findSwitchboardSession(toFriend)!=null )
            {
                MimeMessage msg = new MimeMessage("你好,这是MSN应声虫机器人自动与你通话。欢迎提问");
                msg.setKind(MimeMessage.KIND_MESSAGE);
                boolean success = msn.sendMessage(toFriend, msg);
                if ( success )
                    System.out.println("打完招呼.");
            }

            
        } catch (IOException ex)
        {
            // TODO 处理异常
            ex.printStackTrace();
        }
    }

在这里,首先在用户上线的事件通知userOnline里调用了doCall方法。doCall方法是开始与友人的通讯。这时候还不能马上向其发消息,要等到会议开始之后,所以又实现了一个switchboardSessionStarted。当会话开始后,我们的机器人就可以在你上线的时候主动发出问题了。

各位,久违了。很久没一起探索MSN机器人了。

接下来的文章,我们一起来探索如果让机器人显示头像。

如要显示头像,机器人方和对方要经过一系列的通讯过程,其通讯过程如下:

1。对方发送一个INVITE信息给机器人。这种情况一发生在双方建立舒适的时候。

2。机器人收到一个信息后,将自己的BaseID发送给对方。

3。机器人接着再发出一个200 OK的信息给对方。

4。对方收到后,回应一个200 OK Ack信息。

5。收到应答后,机器人接下来就发送一个Data Preperation信息,表示数据准备好发送了。

6。对方收到后要回应一个Data Prep Ack表示应答。

7。如果没问题,机器人接下来就发送头像数据(如果头像数据大于1202bytes,要分多次发送)。

8。对方收完头像数据后,要发一个Data Ack的信息,表示收到所有数据了。

9。最后对方要再发一个BYE信息,结束通话。

通过解析网络通讯的数据包,我们发现,每次通讯的过程,发送的都是P2P信息。p2p信息格式有如下几部分构成:

MSG [TrId] D [msgLength]
MIME-Version: 1.0

Content-Type: application/x-msnmsgrp2p

P2P-Dest: [email protected]



[Binary Header]
[Message Body]
[Binary Footer]

第一行中,TrId是通信用的Transaction Id,对于每次通话,如果是回应信息的话,TrId就应该是主叫信息的TrId。如果是主叫信息,此时必须是一个唯一ID, 对方回应时其信息中会返回此ID.而msgLength是此信息内容的字节数。
接下来的三行是p2p信息固有的, 最后以一个空白行
结束.

接下来的部分我们称之为Binary Header,它是P2P消息中要发送数据的标头信息, Binary Header包含9个字段,每个字段定义如下:见附件2

1. field1. 是包含了sessionId的Dword字段,  当正在开始邀请会话时,此时的值是0. 此字段只在发送数据
时使用.
2. field2. 也是一个Dword, 此条信息的标识(也称为BaseId).
第一条信息里包含了随机生成的原始BaseID.
之后的几次消息中分别用原始BaseID-3,BID-2, BID-1, BID+1
例如,以下是一个完整的P2P通讯的过程,大家注意看它的BID变化情况:
Me: Invite (baseID random)
58 A9 18 01 (18393432)(myBID)
You: BaseID (random)
3B 99 4F 02 (38771003)(yourBID)

You: 200 OK
38 99 4F 02 (38771000)(yourBID-3)
Me: 200OK ACK
55 A9 18 01 (18393429)(myBID-3)

You: Data Prep
39 99 4F 02 (38771001)(yourBID-2)
Me: Data Prep ACK
56 A9 18 01 (18393430)(myBI-2)

You: Send Data
3A 99 4F 02 (38771002)(yourBID-1)
Me: Data ACK
57 A9 18 01 (18393431)(myBID-1)

Me: Bye
59 A9 18 01 (18393433)(myBID+1)
You: Bye ACK
3C 99 4F 02 (38771004)(yourBID+1)

BaseID的范围从4 ~ 4 250 000 000). 它是随机生成的.

3. Field3是一个QWord, 包含了你要发送的数据的偏移位置(offset),
如果数据大小超过1202, 例如 如果你送2404 bytes数据, 那么第一次的信息中这个字段为0值(因为你还没
送出任何数据),第二条信息里此字段为1202,因为你已发送了1202 bytes.
如果没有数据发磅,此字段为0.
4. field4也是一个QWord, 包含了要发送整个数据的大小。如果没数据则为0并且通常field6要设为2.
5. field5是一个DWord, 包含了本次要发送数据的大小。例如,要发送一个5000bytes的文件,
则field4的值为5000, 而此字段的值为1202.因为每个发送最大不能超过1202个字节。
6. field6是一个DWord, 一个Flag字段. 如果没有Flag则值为0。
如果此信息为回应到另一个信息(ACK),则它的值是2, 如果在接收到的信息的binaryHeader有
错误发生则值为8. 如果是发送的Display Pictures(DP)或者Custom Emoticons(CE)
这个字段为32(0x20),  
完整的参数值见下表:
    0x00 - No flags
    0x01 - Unknown
    0x02 - Acknowledgement
    0x04 - Waiting for a reply
    0x08 - Error (Possibly binary level)
    0x10 - Unknown
    0x20 - Data for DP/CE
    0x40 - Bye ack (He who got BYE)
    0x80 - Bye ack (He who sent BYE)
    0x1000030 - Data for FT

7. field7, DWord. 如果本次binary header中不包含sessionID, 也不包含在发送的数据中,
则此字段值为对方上次信息发来的BaseID. 如果sessionID已包含在信息中,则此字段为一个随机数。
8. field8, 此字段包含对方上次传来的信息中第7个字段的值.
9. field9, QWord, 如果你正在送一个ACK信息,此字段包含对方上次信息的第4个字段的值.其它情况下此字段为0.

位于Binary Header和Binary Footer之间的就是我们要发送的数据,在不同的信息响应中,Message Body包含了不同的内容,这个在后面会做解释。

Binary Footer,也是一个DWord. 此字段是跟随在发送的数据之后, 因此称之为Footer. 当没有数据(DP, 或CE等)被发送时这个字段的值为0. 当发送DP时值为1, CE时是其它值, 发送Ink(手写笔迹信息,它来自于Tablet PC)时值为3. 其它的值现还在研究。

所以根据Binary Header信息,我们就可以知道此p2p信息一些基本内容,并作出相应处理。

参考资源: http://siebe.bot2k3.com/docs/p4p/?url=home.html


图片附件: jmsn01.gif (2004-12-9 18:36, 14.82 K)



图片附件: msn_binaryheader.jpg (2004-12-9 18:36, 12.15 K)



参考网址(Reference)

你可能感兴趣的:(Blog)