转自:http://www.cnblogs.com/Bird/archive/2007/08/14/854763.html
内容记:
MSN Robot
就是MSN机器人, 也可以喊成MSN聊天机器人.再解释就是会自动和你聊天的MSN,再再解释就是……啊, 我短路了(耳朵冒烟中)
本随笔是MSN Robot随笔系列第三篇, 内容是关于MSN Robot信息发送和接收.
前记:
香吉士:我想要找到
“ALL BLUE”
鲁飞:我要当海贼王 !!
索隆:我要成为一个最伟大的剑客 !!
娜美:我要画完全世界的地图 !!
骗人布:我 ... 我 ... 我要成为一个勇敢的海上战士 !!
鲁飞:我要当海贼王 !!
索隆:我要成为一个最伟大的剑客 !!
娜美:我要画完全世界的地图 !!
骗人布:我 ... 我 ... 我要成为一个勇敢的海上战士 !!
大家:出发吧
!
进入
“
伟大的航路
”!!
不管大家看到这部随笔的过程是怎样的
,
但咱们一定是追随着梦想的
!
不管太阳轮回东西
,
月明星暗
,
我们的旗帜都永远飘扬
!
絮絮了几句
,
咱们今天来看看
Message
吧
J
这是咱们
MSN Robot
的重头戏
Message
比较大头
,
计划分成两部分
,
一部分实现
,
一部分调用
,
今天咱们先来实现了消息发送和接收代码吧
J
这样咱们的
Robot
已经焊接到了胸部
,
明天加上几行调用
,
宇宙银河系地球中国北京
XX
路
XX
号中间一个屋子里边的一台机器中的无敌变形金刚就出现了
!
应该给它起个什么名字好呢
?
J
(
陷入沉思中
)
我的建议把
:
Switchboard_SessionClosed
Switchboard_ContactJoined
Switchboard_ContactLeft
Conversation_Closing
Switchboard_TextMessageReceived
SendInput
几个与消息亲密的函数包装成一个类. 当然, 这样做是有好处的
J
不然咱们明天在处理多用户服务的时候会遇到相当麻烦的小槛
J
只听半空
“dang”
的一声
,
一个看起来很可爱的类就掉下来了
.
啊
..
我多希望它是机器猫呀
….
using
System;
using
System.Drawing;
using
System.Collections;
using
System.ComponentModel;
using
System.Windows.Forms;
using
XihSolutions.DotMSN;
using
System.Data;
using
System.Data.SqlClient;
using
System.Text;
using
System.Net;
using
System.Net.Sockets;
class
Message
{
// conversation
private Conversation _conversation;
//
主画面
DotMSNClient.ClientForm from;
//ContactJoined
标记
//true:
成功加入
//flase:
尚未加入
private bool bRun = false;
//IP and Port
private IPEndPoint iepSeverAddress=null;
//
数据片最大长度
public static long SLICE_MAX_LENGTH = 800;
//
信息记录
private const string STR_CFG_CONTACT_LEFT = "
用户已经退出会话, 消息发送不成功/r/n"
;
private const string STR_CFG_REVERT_TIMEOVER = "*
回复超时/r/n"
;
private const string STR_CFG_QUESTION_OK = "*
回复答案/r/n"
;
private const string STR_CFG_SBPROCESSOR_NULL = "Conversation SwitchboardProcessor
为空/r/n"
;
private const string STR_CFG_MESSAGEEMPTY = "
欲发送消息为空/r/n"
;
private const string STR_CFG_OVERTIME = "
操作超时, 请稍候再试/r/n"
;
private const string STR_CFG_QUESTION_OVER = "*
回复答案太长/r/n"
;
private const string STR_CFG_QUESTION_NO = "*
回复没找到/r/n"
;
//
用户消息
string STR_USER_TIMEOVER = "
操作超时, 请稍候再试"
;
string STR_USER_ADD = "
哦, 忘记告诉你... 你教我的我看不懂... 我不太懂中文... Yeah~ 我忘记了"
;
string STR_USER_HELP = "
询问格式: ans 今天天气如何?/r/n"
+
"
寻求帮助help/r/n/r/n"
+
"
如果需要与客服聊天请加入MSN:zhangyv1234@hotmail.co.jp/r/n"
+
"
请不要和机器人吵架!不要说shit..."
;
string STR_USER_SEVERCLOSE = "
后台服务器没有开哟, 请稍候再试"
;
string STR_USER_NOANSWER = "
呵呵, 没找到"
;
string STR_USER_MESSAGELONG = "
消息太长了.. 咱缓缓吧...累死我了..."
;
///<summary>
/// The conversation object which is associated
///</summary>
public Conversation Conversation
{
get { return _conversation; }
}
public Message(Conversation conversation, ClientForm thisFrom)
{
_conversation = conversation;
from = thisFrom;
//
获得用户配置的说话
STR_USER_TIMEOVER = from.STR_USER_TIMEOVER;
STR_USER_ADD = from.STR_USER_ADD;
STR_USER_HELP = from.STR_USER_HELP;
STR_USER_SEVERCLOSE = from.STR_USER_SEVERCLOSE;
STR_USER_NOANSWER = from.STR_USER_NOANSWER;
STR_USER_MESSAGELONG = from.STR_USER_MESSAGELONG;
//
消息接收事件
Conversation.Switchboard.TextMessageReceived += new TextMessageReceivedEventHandler(Switchboard_TextMessageReceived);
//SessionCloes
事件
Conversation.Switchboard.SessionClosed += new SBChangedEventHandler(Switchboard_SessionClosed);
//
用户加入连接事件
Conversation.Switchboard.ContactJoined += new ContactChangedEventHandler(Switchboard_ContactJoined);
//
用户离开连接事件
Conversation.Switchboard.ContactLeft += new ContactChangedEventHandler(Switchboard_ContactLeft);
}
//
判断是否超时
private bool GetTimeOverTimeSpan()
{
DateTime tBeginTime = DateTime.Now;
DateTime tCurrTime;
//
等待ContactJoined事件先行触发, Contact进入Conversation后才能继续处理.
while (!bRun)
{
tCurrTime = DateTime.Now;
TimeSpan dif = tCurrTime - tBeginTime;
//
计算超时, 如果秒后Contact尚未进入, 就断定超时退出处理.
if (dif.Seconds > 10)
{
//from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return false;
}
}
return true;
}
//
消息发送
public void SendInput(string strMessage)
{
string inputTextBox = strMessage;
//
判断是否超时
if (!GetTimeOverTimeSpan())
{
from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return;
}
//
消息为空
if (inputTextBox.Length == 0)
{
from.richTextBox3.AppendText(STR_CFG_MESSAGEEMPTY);
return;
}
//SwitchboardProcessor
为null
if (Conversation.SwitchboardProcessor == null)
{
from.richTextBox1.AppendText(STR_CFG_SBPROCESSOR_NULL);
return;
}
// if there is no switchboard available, request a new switchboard session
if (Conversation.SwitchboardProcessor.Connected == false)
{
Conversation.Messenger.Nameserver.RequestSwitchboard(Conversation.Switchboard, this);
}
// Contacts
为, 用户已经left会话
if (Conversation.Switchboard.Contacts.Count == 0)
{
from.richTextBox1.AppendText(STR_CFG_CONTACT_LEFT);
return;
}
//
准备消息
TextMessage message = new TextMessage(inputTextBox);
/* You can optionally change the message's font, charset, color here.
* For example:
* message.Color = Color.Red;
* message.Decorations = TextDecorations.Bold;
*/
//
终于可以发送了
Conversation.Switchboard.SendTextMessage(message);
}
///<summary>
///
把消息显示在界面
///</summary>
///<param name="name"></param>
///<param name="text"></param>
private void PrintText(string name, string text)
{
from.richTextBox3.AppendText(name + "
说: /r/n"
+ text + "/r/n");
from.richTextBox3.ScrollToCaret();
}
///<summary>
///
接收到消息事件
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
private void Switchboard_TextMessageReceived(object sender, TextMessageEventArgs e)
{
//
得到用户命令的小写串
string cmd = e.Message.Text.ToLower();
//
用户命令存放字符串
string strShirtCmd = string.Empty;
//
显示接收到的消息到界面
PrintText(e.Sender.Name, e.Message.Text);
//
解析用户命令
if (cmd.Length > 3)
strShirtCmd = cmd.Substring(0, 3);
//
转换Sender成SBMessageHandler
SBMessageHandler SBSender = (SBMessageHandler)sender;
string strMail="";
//
发送正在编辑信息
SBSender.SendTypingMessage();
//
判断是否超时
if (!GetTimeOverTimeSpan())
{
from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return;
}
//
用户请求help时候处理
if (cmd == "help")
{
OnHelp(SBSender, e.Sender.Name, e.Message.Text);
}
//
用户询问处理
else if (strShirtCmd == "ans")
{
OnAnswerByDBSearch(SBSender, e.Sender.Name, e.Message.Text);
//OnAnswerBySocketConn(SBSender, e.Sender.Name, e.Message.Text);
}
//
用户知识库添加处理
else if (strShirtCmd == "add")
{
SBSender.SendTextMessage(new TextMessage(STR_USER_ADD));
}
//
用户骂人.... 回骂...
else if (cmd.StartsWith("shit") || cmd.StartsWith("fuck"))
{
for (int x = 0; x < 5; x++)
{
SBSender.SendTextMessage(new TextMessage("!@#!@#%$#%$^&^%$@$@%$#%#$"));
}
}
//
默认处理, Help
else
{
OnHelp(SBSender, e.Sender.Name, e.Message.Text);
}
}
///<summary>
/// Help
消息回复
///</summary>
///<param name="con"></param>
///<param name="Mail"></param>
///<param name="cmd"></param>
void OnHelp(SBMessageHandler con, string Mail, string cmd)
{
con.SendTextMessage(new TextMessage(STR_USER_HELP));
}
///<summary>
///
测试数据库处理
///</summary>
///<param name="SBSender"></param>
///<param name="name"></param>
///<param name="text"></param>
void OnAnswerByDBSearch(SBMessageHandler SBSender, string name, string text)
{
text = prepare(text);
DotMSN.DBClass db = new DotMSN.DBClass(@"Server=localhost;Integrated Security=True;" + "Database=Spider");
SqlDataReader dbReader = db.GetSqlDataReader("select title,url from page where title like '%" + text + "%'");
StringBuilder strBuilder = new StringBuilder();
string strResult = "";
while (dbReader.Read())
{
strResult += dbReader["title"].ToString().Trim() + "/r/n" + dbReader["url"].ToString().Trim() + "/r/n";
}
dbReader.Close();
db.Close();
SendAnswer(SBSender, strResult);
}
///<summary>
///
将答案传回
///</summary>
///<param name="SBSender"></param>
///<param name="text"></param>
private void SendAnswer(SBMessageHandler SBSender, string text)
{
//
没有找到答案
if (string.IsNullOrEmpty(text))
{
SBSender.SendTextMessage(new TextMessage(STR_USER_NOANSWER));
from.richTextBox3.AppendText(STR_CFG_QUESTION_NO);
}
else
{
SBSender.SendTextMessage(new TextMessage("hallo,World"));
//
记录"* 回复答案"
from.richTextBox3.AppendText(STR_CFG_QUESTION_OK);
}
}
///<summary>
///
命令准备
///</summary>
///<param name="text"></param>
///<returns></returns>
private string prepare(string text)
{
if (text.Length > 3)
{
text = text.Substring(3, text.Length - 3);
}
else
{
//SBSender.SendTextMessage(new TextMessage("
命令错误"));
return text;
}
text = text.Trim();
return text;
}
}
通览这篇文章准备呛我行的你肯定第一眼就看到了这两个函数. 乃是整个Robot核心之核心, 重点之重点.
Ok,
讲解完了. 大家自己看吧
………
.(
我是很邪恶很自私的说
L
)
Switchboard_TextMessageReceived
SendInput
我是下边讲解的例子
//消息发送
public void SendInput(string strMessage)
{
string inputTextBox = strMessage;
// 判断是否超时
// 这里是非常关键的地方, 可以看下边的解释
if (!GetTimeOverTimeSpan())
{
from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return;
}
//消息为空
if (inputTextBox.Length == 0)
{
from.richTextBox3.AppendText(STR_CFG_MESSAGEEMPTY);
return;
}
//SwitchboardProcessor为null
if (Conversation.SwitchboardProcessor == null)
{
from.richTextBox1.AppendText(STR_CFG_SBPROCESSOR_NULL);
return;
}
// if there is no switchboard available, request a new switchboard session
if (Conversation.SwitchboardProcessor.Connected == false)
{
Conversation.Messenger.Nameserver.RequestSwitchboard(Conversation.Switchboard, this);
}
// Contacts为, 用户已经left会话
if (Conversation.Switchboard.Contacts.Count == 0)
{
from.richTextBox1.AppendText(STR_CFG_CONTACT_LEFT);
return;
}
// 准备消息
TextMessage message = new TextMessage(inputTextBox);
/* You can optionally change the message's font, charset, color here.
* For example:
* message.Color = Color.Red;
* message.Decorations = TextDecorations.Bold;
*/
// 终于可以发送了
Conversation.Switchboard.SendTextMessage(message);
}
//消息发送
public void SendInput(string strMessage)
{
string inputTextBox = strMessage;
// 判断是否超时
// 这里是非常关键的地方, 可以看下边的解释
if (!GetTimeOverTimeSpan())
{
from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return;
}
//消息为空
if (inputTextBox.Length == 0)
{
from.richTextBox3.AppendText(STR_CFG_MESSAGEEMPTY);
return;
}
//SwitchboardProcessor为null
if (Conversation.SwitchboardProcessor == null)
{
from.richTextBox1.AppendText(STR_CFG_SBPROCESSOR_NULL);
return;
}
// if there is no switchboard available, request a new switchboard session
if (Conversation.SwitchboardProcessor.Connected == false)
{
Conversation.Messenger.Nameserver.RequestSwitchboard(Conversation.Switchboard, this);
}
// Contacts为, 用户已经left会话
if (Conversation.Switchboard.Contacts.Count == 0)
{
from.richTextBox1.AppendText(STR_CFG_CONTACT_LEFT);
return;
}
// 准备消息
TextMessage message = new TextMessage(inputTextBox);
/* You can optionally change the message's font, charset, color here.
* For example:
* message.Color = Color.Red;
* message.Decorations = TextDecorations.Bold;
*/
// 终于可以发送了
Conversation.Switchboard.SendTextMessage(message);
}
也许咱们已经对照着DotMSN来试着实现消息的接收和发送了, 又也许你会遇到这样一个问题, 就是发送消息和接收消息会出现失败的情况, 或者是发送出去的消息对方没有收到, 又或者是对方发来的消息自己没有收到. 有的时候这种情况会很明显, 有的时候又靠着rp问题才能重现. 再也许你会奇怪另一个问题, 就是很哲理的.. 这个消息是怎么发出去的? Ok, 它们是一个答案, 关于后一个问题咱们先来解决它的表象吧 J 好吗?
在DotMSN里, 消息发送是分为这样几步的.
一, 取到用户Contact
比如:
Contact
selectedContact = (Contact)ContactListView.SelectedItems[0].Tag;
二, 创建Conversation, 也可以叫创建会话.
比如:
Conversation
Cconversation = messenger.CreateConversation();
三, 将用户加入到会话中
Conversation
.Invite(selectedContact);
四, 发送消息
Conversation
.Switchboard.SendTextMessage(message);
看
J
是不是很简单? 行云流水丝绸一样飘过. 消息就这样发送出去了.
等等! 是不是发送成功了呢? 在这样天鹅绒光滑的水面下有没有什么杀人的石头呢?
嗯, 应该是有, 不然科幻小说的作者就没有写下去的动力了.
比如, 最不幸的就是本人, 某一天月黑风高在早晨可以像小舟一样划过水面的程序, 下午就触礁了.
在消息发送时
if
(Conversation.SwitchboardProcessor.Connected == false)
这句上出现了永远过不去的, 对象没有绑定到实例. 这时候SwitchboardProcessor为null
为什么会这样子?! 为什么会出现永恒的, 突然的, null的地狱呢? 这是诅咒还是宿命?!
路人甲: 这是rp问题
…
.
Ok,
本人是不能允许我这样风华绝代大帅哥出现rp问题的. 抄刀灭之!
这时候突然想到曾经神仙老爷爷说过的话, 发送消息之前一定要双方进入会话.
顺藤摸瓜赶紧查, yi, 为什么在Invite好友之后程序没有马上触发Switchboard_ContactJoined事件通知咱们用户进入对话呢?
是不是在Invite之后要等待服务器的通知? 嗯, 一定是这样.
再敬仰的去看
DotMSN
的程序
…
yi,
为什么在Invite好友之后打了一个很二的时间差呢?
如果
Invite
的时候有网络延迟, 运行到send的时候对方还没有加入会话呢?! o, null hell, send fail.
于是在咱们的相关消息函数中有这样一段代码:
//
判断是否超时
//
这里是非常关键的地方, 可以看下边的解释
if
(!GetTimeOverTimeSpan())
{
from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return;
}
可以看一看它的函数本体, 哗, 是一个很白痴的while循环! 它在一直等待一个名字叫bRun的白痴看不懂是什么的标记变成true
//
判断是否超时
private
bool GetTimeOverTimeSpan()
{
DateTime
tBeginTime = DateTime.Now;
DateTime
tCurrTime;
//
等待ContactJoined事件先行触发, Contact进入Conversation后才能继续处理.
while
(!bRun)
{
tCurrTime = DateTime.Now;
TimeSpan
dif = tCurrTime - tBeginTime;
//
计算超时, 如果秒后Contact尚未进入, 就断定超时退出处理.
if
(dif.Seconds > 10)
{
//from.richTextBox3.AppendText(STR_CFG_OVERTIME);
return
false;
}
}
return
true;
}
你会指着我的鼻子喊:
这个叫
bRun
的白痴标记在什么时候才能变成true呢?!
答案是 在好友已经加入对话的事件里.
private
void Switchboard_ContactJoined(object sender, ContactEventArgs e)
{
bRun = true;
}
嗯, 结论是, 世界终于和平, 人类终于美好了~~ 王子和公主幸福的生活在一起, 相知相伴, 永不分离.
流氓: 等等! 还没结束, 在程序的后台又发生了什么呢??!!
可怜的俺: 大佬.. 饶了我吧.. 今天打了一下午字( 大于等于1 ), 手都麻了~~~ 而且, 而且这个时候我是下班的可怜人呐
L
55555
…
.
转上几个字, 希望接着了解后台可以顺着读一下, 门清~
在MSN里的即时通讯是基于session的。想进行对话的两个人必须在session模式当中。除非我们同其他用户开始一个聊天session,否则我们是不能发送/接受信息的。
基本上有两种途径可以使一个用户处于一个聊天session中
.1 用户向另一个用户发送一个聊天session请求
2 用户接收从另一个用户那里发送来的聊天session请求
接下来将分别详细介绍这两种途径
用户向另一个用户发送一个聊天session请求
客户端(用户)向服务器发送一个命令,以获取接线总机(SwitchBoard)服务器的地址.所有的即时通讯交谈都必须通过接线总机服务器实现。
基本上有两种途径可以使一个用户处于一个聊天session中
.1 用户向另一个用户发送一个聊天session请求
2 用户接收从另一个用户那里发送来的聊天session请求
接下来将分别详细介绍这两种途径
用户向另一个用户发送一个聊天session请求
客户端(用户)向服务器发送一个命令,以获取接线总机(SwitchBoard)服务器的地址.所有的即时通讯交谈都必须通过接线总机服务器实现。
XFR9 SB
此接线总机服务器返回此服务器的ip地址,连接端口,和一个CKI杂列。CKI 是一个安全包,用户必须使用此CKI杂列连接上接线总机服务器。
此接线总机服务器返回此服务器的ip地址,连接端口,和一个CKI杂列。CKI 是一个安全包,用户必须使用此CKI杂列连接上接线总机服务器。
XFR9 SB 64.4.13.88:1863 CKI1989487642.2070896604
现在这次我们将向接线总机服务器进行一次新的连接。而且我们上次对MSN即时通服务器的连接必须要保持,否则我们将会登出。
现在这次我们将向接线总机服务器进行一次新的连接。而且我们上次对MSN即时通服务器的连接必须要保持,否则我们将会登出。
在我们连接上接线总机服务器之后,我们将向此接线总机服务器发送以下命令:
USR 1 venky_dude@hotmail.com 989487642.2070896604
如果我们发送的这个CKI杂列正确的话,接线服务器将会返回
USR 1 OK venky_dude@hotmail.com venkat
当以上做完之后,接下来这个用户要做的就是要把另一个用户”叫到”此聊天session中了,这可以通过发送下面的命令完成
CAL 2 deadxxx@hotmail.com
服务器将会向此用户回应一个session号,同时也会将此session号发送给另一个用户。
如果另一个用户准备好聊天,并向做出回应时,服务器将向我们发送如下命令
USR 1 venky_dude@hotmail.com 989487642.2070896604
如果我们发送的这个CKI杂列正确的话,接线服务器将会返回
USR 1 OK venky_dude@hotmail.com venkat
当以上做完之后,接下来这个用户要做的就是要把另一个用户”叫到”此聊天session中了,这可以通过发送下面的命令完成
CAL 2 deadxxx@hotmail.com
服务器将会向此用户回应一个session号,同时也会将此session号发送给另一个用户。
如果另一个用户准备好聊天,并向做出回应时,服务器将向我们发送如下命令
JOI deadlee@hotmail.com Venkatesh
这条命令表示另一个用户加入了聊天当中,我们现在可以接受和发送信息了
这条命令表示另一个用户加入了聊天当中,我们现在可以接受和发送信息了
看, 一直到这里, 咱们的
Invite
终于执行到ContactJoined了!
用户接收从另一个用户那里发送来的聊天session请求
当我们被一个用户邀请到一个聊天session中时,认证服务器将向我们发送如下的信息.
RNG 11742066 64.4.13.74:1863 CKI 989495494.750408580 deaxxxx@hotmail.com Venkatesh
上面的命令包含了session号,接线服务器的ip地址,端口,CKI杂列及向我们发出交谈请求的用户信息。
现在我们将向接线服务器进行一次新的连接。同样我们上次对MSN即时通服务器的连接必须要保持,否则我们将会登出。
我们连接上接线服务器,并且发送如下命令
ANS 1 venky_dude@hotmail.com 989495494.750408580 11742066
上面的命令包含了我们的登陆名,我们接收到的CKI杂列和session号.
服务器将回应如下信息
IRO 1 1 1 deaxxxx@hotmail.com Venkatesh 和 ANS 1 OK
我们现在就可以发送和接收信息了。
上面的命令包含了我们的登陆名,我们接收到的CKI杂列和session号.
服务器将回应如下信息
IRO 1 1 1 deaxxxx@hotmail.com Venkatesh 和 ANS 1 OK
我们现在就可以发送和接收信息了。
在我们发送和接收信息之前,让我们了解一下信息是如何创建的
我们发送信息时,将首先建立一个头信息如下所示
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-MMS-IM-Format: FN=Microsoft%20Sans%20Serif; EF=; CO=0; CS=0; PF=22
我们发送信息时,将首先建立一个头信息如下所示
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-MMS-IM-Format: FN=Microsoft%20Sans%20Serif; EF=; CO=0; CS=0; PF=22
然后使用如下的方式发送
MSG2 N137 MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-MMS-IM-Format: FN=Microsoft%20Sans%20Serif; EF=; CO=0; CS=0; PF=22
MSG2 N137 MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-MMS-IM-Format: FN=Microsoft%20Sans%20Serif; EF=; CO=0; CS=0; PF=22
hello
其中2是测试号,我们每发送一次信息,此号就会随着增加。137是指我们发送信息的长度。在上面的例子中就是头信息和我们发送的信息”hello”的长度。
其中2是测试号,我们每发送一次信息,此号就会随着增加。137是指我们发送信息的长度。在上面的例子中就是头信息和我们发送的信息”hello”的长度。
我们发送的信息和上面的例子是差不多的。
下面是我们接受到的一个信息的例子
MSG deaxxxx@hotmail.com Venkatesh 137
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-MMS-IM-Format: FN=Microsoft%20Sans%20Serif; EF=; CO=0; CS=0; PF=22
MSG deaxxxx@hotmail.com Venkatesh 137
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
X-MMS-IM-Format: FN=Microsoft%20Sans%20Serif; EF=; CO=0; CS=0; PF=22
hello
当另一用户正在输入信息时,我们会收到如下信息
MSG deaxxxx@hotmail.com Venkatesh 100
MIME-Version: 1.0
Content-Type: text/x-msmsgscontrol
TypingUser: deaxxxx@hotmail.com
MSG deaxxxx@hotmail.com Venkatesh 100
MIME-Version: 1.0
Content-Type: text/x-msmsgscontrol
TypingUser: deaxxxx@hotmail.com