主题列表:
COMET彗星(一)SERVER PUSH介绍
COMET彗星(二)基于SERVER PUSH的消息传输
引言:
在上一篇随笔中,对COMET使用的类和作用进行了简短的介绍,从本篇随笔开始,将从实体类开始,对COMET的核心进行构建分析。
CORE框架:
图1.1 COMET核心框架
CometMessage类:
CometMessage类是COMET的通信载体,对消息的主体进行抽象,实际上这个类是最容易进行扩展的,因为从设计上看,它只是一个消息的容器。而诸如地理坐标,业务数据等,都可以通过这个类来进行直接扩充。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Runtime.Serialization;
namespace
MethodWorx.AspNetComet.Core {
///
<summary>
///
CometMessage Class
///
///
This is a CometMessage that has been sent to the client, the DataContract names have been
///
shortened to remove any bytes we dont need from the message (ok, did'nt save much, but we can do it!)
///
</summary>
[DataContract(Name
=
"
cm
"
)]
public
class
CometMessage { [DataMember(Name
=
"
mid
"
)]
private
long
messageId; [DataMember(Name
=
"
n
"
)]
private
string
name; [DataMember(Name
=
"
c
"
)]
private
object
contents;
///
<summary>
///
Gets or Sets the MessageId, used to track which message the Client last received
///
</summary>
public
long
MessageId {
get
{
return
this
.messageId; }
set
{
this
.messageId
=
value; } }
///
<summary>
///
Gets or Sets the Content of the Message
///
</summary>
public
object
Contents {
get
{
return
this
.contents; }
set
{
this
.contents
=
value; } }
///
<summary>
///
Gets or Sets the error message if this is a failure
///
</summary>
public
string
Name {
get
{
return
this
.name; }
set
{
this
.name
=
value; } } } }
类的设计简单明了,这里有必要解释下使用System.Runtime.Serialization命名空间的意义。
“System.Runtime.Serialization 命名空间包含可用于将对象序列化和反序列化的类。序列化是将对象或对象图形转换为线性字节序列,以存储或传输到另一个位置的过程。反序列化是接受存储的信息并利用它重新创建对象的过程。”
这是MSDN给我们的解释,将对象转变为线性字节,然后方便传输与调用。当然这个例子中的数据类型并不复杂,但也包含了LONG,OBJECT,STRING这样的数据类型。其中Contents成员为object对象,这给我们留下了非常大的想像空间。(图片?复杂对象类型?自定义对象类型?……)
CometClient类:
CometClient类是对客户端信息的抽象类,同时包含了两个关键属性ConnectionIdleSeconds和ConnectionTimeoutSeconds。由于考虑到不同客户端间传递属性,仍然使用System.Runtime.Serialization来序列化信息。
关于JSON的应用,这个框架其实也提供了相应支持。后面的随笔中我会介绍。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Runtime.Serialization;
namespace
MethodWorx.AspNetComet.Core {
///
<summary>
///
CometClient Class
///
///
This represents a logged in client within the COMET application. This marked as a DataContract becuase
///
it can be seralized to the client using JSON
///
</summary>
[DataContract]
public
class
CometClient { [DataMember]
private
string
privateToken; [DataMember]
private
string
publicToken; [DataMember]
private
string
displayName; [DataMember]
private
DateTime lastActivity; [DataMember]
private
int
connectionIdleSeconds; [DataMember]
private
int
connectionTimeoutSeconds;
///
<summary>
///
Gets or Sets the token used to identify the client to themselves
///
</summary>
public
string
PrivateToken {
get
{
return
this
.privateToken; }
set
{
this
.privateToken
=
value; } }
///
<summary>
///
Gets or Sets the token used to identify the client to other clients
///
</summary>
public
string
PublicToken {
get
{
return
this
.publicToken; }
set
{
this
.publicToken
=
value; } }
///
<summary>
///
Gets or Sets the display name of the client
///
</summary>
public
string
DisplayName {
get
{
return
this
.displayName; }
set
{
this
.displayName
=
value; } }
///
<summary>
///
Gets or Sets the last activity of the client
///
</summary>
public
DateTime LastActivity {
get
{
return
this
.lastActivity; }
set
{
this
.lastActivity
=
value; } }
///
<summary>
///
Gets or Sets the ConnectionIdleSections property which is the number of seconds a connection will remain
///
alive for without being connected to a client, after this time has expired the client will
///
be removed from the state manager
///
</summary>
public
int
ConnectionIdleSeconds {
get
{
return
this
.connectionIdleSeconds; }
set
{
this
.connectionIdleSeconds
=
value; } }
///
<summary>
///
Gets or Sets the ConnectionTimeOutSections property which is the number of seconds a connection will remain
///
alive for whilst being connected to a client, but without receiving any messages. After a timeout has expired
///
A client should restablish a connection to the server
///
</summary>
public
int
ConnectionTimeoutSeconds {
get
{
return
this
.connectionTimeoutSeconds; }
set
{
this
.connectionTimeoutSeconds
=
value; } } } }
ConnectionIdleSeconds:用来设置连接线程,当connection断线后,后台Thread的存活时间。
ConnectionTimeoutSeconds:客户端的超时时间,当超过时间后,客户端重新连接服务器。
ps:有这两个属性后,基本上完成了客户端连接的控制,超时重连接,无连接时杀死后台线程。
ICometStateProvider接口:
ICometStateProvider接口直接被CometStateMessager建立,这样的好处是实例化CometStateMessager对象后,CometStateMessager对象可以直接调用ICometStateProvider接口的实现,实际上实现了Adapter的方式,我们可以定制不同的InProcCometStateProvider类(在下面会提到)来定制自己的接口。
ICometStateProvider接口类提供了如下几个接口。
InitializeClient:提供ComentClient的初始化操作。
GetMessages:得到一个Messages的消息队列。
SendMessage:发送Message数据。
SendMessage:可以针对Client name来进行消息发布(私人会话)。
GetCometClient:返回一个Client。
KillIdleCometClient:杀掉一个无效的Client对象。
ps:当然这个接口是可以被拓展的,而且实现起来非常简单。得到Client队列信息,得到Client状态等等。甚至你可以想象一些更复杂的应用(比如加入一个流媒体消息……)。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
MethodWorx.AspNetComet.Core {
///
<summary>
///
This interface can be implemented to provide a custom state provider
///
for the CometStateManager class. Typical examples may be using SqlServer
///
to enable the operation over a server farm
///
</summary>
public
interface
ICometStateProvider {
///
<summary>
///
Implementation of this method should store the cometClient instance in some sort
///
of cache (eg Memory, Db etc..)
///
</summary>
///
<param name="cometClient"></param>
void
InitializeClient(CometClient cometClient);
///
<summary>
///
Imeplementation of this method should return all the messages that are queued
///
for a specific client, it is only interested in messages that have a greater id than
///
lastMessageId
///
</summary>
///
<param name="clientPrivateToken"></param>
///
<param name="lastMessageId"></param>
///
<returns></returns>
CometMessage[] GetMessages(
string
clientPrivateToken,
long
lastMessageId);
///
<summary>
///
Implementation of this method should queue a message for the specific client
///
</summary>
///
<param name="clientPublicToken"></param>
///
<param name="name"></param>
///
<param name="contents"></param>
void
SendMessage(
string
clientPublicToken,
string
name,
object
contents);
///
<summary>
///
Implementation of this method should queue a message for all the clients
///
</summary>
///
<param name="name"></param>
///
<param name="contents"></param>
void
SendMessage(
string
name,
object
contents);
///
<summary>
///
Implementation of this method should return a specific comet client
///
</summary>
///
<param name="clientPrivateToken"></param>
///
<returns></returns>
CometClient GetCometClient(
string
clientPrivateToken);
///
<summary>
///
Implementation of this method should remove a client from the cache
///
</summary>
///
<param name="clientPrivateToken"></param>
void
KillIdleCometClient(
string
clientPrivateToken); } }
InProcCometStateProvider类:
InProcCometStateProvider类实现了ICometStateProvider接口,并且提供了一个很好的范例,针对这个类,我们可以想象很多很好的拓展,诸如调用AO组件,封装报警信息等等。
InProcCometStateProvider类包含类一个私有的内部类InProcCometStateProvider,实际上可以理解为一种对消息的简单封装,设计的时候考虑到需要关联CometClient和Message,其实也可以单独作为一个外部类来设计。不过Adapter本身不应该太多关联类,这样做也是权衡了一些拓展上的需求。
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
namespace
MethodWorx.AspNetComet.Core {
///
<summary>
///
Class InProcCometStateProvider
///
///
This class provides an implementation of ICometStateProvider that keeps the
///
information in memory. This provider is not scalable as it will not run on a server
///
farm but demonstrates how you should implemement the provider.
///
</summary>
public
class
InProcCometStateProvider : ICometStateProvider {
///
<summary>
///
Private class which holds the state of each connected client
///
</summary>
private
class
InProcCometClient {
public
CometClient CometClient;
public
Dictionary
<
long
, CometMessage
>
Messages
=
new
Dictionary
<
long
, CometMessage
>
();
public
long
NextMessageId
=
1
; }
///
<summary>
///
Cache of clients
///
</summary>
private
Dictionary
<
string
, InProcCometClient
>
publicClients
=
new
Dictionary
<
string
, InProcCometClient
>
();
private
Dictionary
<
string
, InProcCometClient
>
privateClients
=
new
Dictionary
<
string
, InProcCometClient
>
();
private
static
object
state
=
new
object
();
#region
ICometStateProvider Members
///
<summary>
///
Store the new client in memory
///
</summary>
///
<param name="cometClient"></param>
public
void
InitializeClient(CometClient cometClient) {
if
(cometClient
==
null
)
throw
new
ArgumentNullException(
"
cometClient
"
);
lock
(state) {
//
ok, ensure we dont already exist
if
(publicClients.ContainsKey(cometClient.PublicToken)
||
privateClients.ContainsKey(cometClient.PrivateToken))
throw
CometException.CometClientAlreadyExistsException(); InProcCometClient inProcCometClient
=
new
InProcCometClient() { CometClient
=
cometClient };
//
stick the client int he arrays
//
ready to be used
publicClients.Add(cometClient.PublicToken, inProcCometClient); privateClients.Add(cometClient.PrivateToken, inProcCometClient); }
//
ok, they are in there ready to be used
}
///
<summary>
///
Get the messages for a specific client
///
</summary>
///
<param name="clientPrivateToken"></param>
///
<param name="lastMessageId"></param>
///
<returns></returns>
public
CometMessage[] GetMessages(
string
clientPrivateToken,
long
lastMessageId) {
if
(
string
.IsNullOrEmpty(clientPrivateToken))
throw
new
ArgumentNullException(
"
clientPrivateToken
"
);
lock
(state) {
if
(
!
privateClients.ContainsKey(clientPrivateToken))
throw
CometException.CometClientDoesNotExistException();
//
//
ok, get the client
InProcCometClient cometClient
=
privateClients[clientPrivateToken]; List
<
long
>
toDelete
=
new
List
<
long
>
(); List
<
long
>
toReturn
=
new
List
<
long
>
();
//
wicked, we have the client, so we can get its messages from our list
//
we delete any before the last messageId becuase we dont want them
foreach
(
long
key
in
cometClient.Messages.Keys) {
if
(key
<=
lastMessageId) toDelete.Add(key);
else
toReturn.Add(key); }
//
delete the ones from the messages
foreach
(
long
key
in
toDelete) { cometClient.Messages.Remove(key); }
//
and return the ones in the toReturn array
List
<
CometMessage
>
cometMessages
=
new
List
<
CometMessage
>
();
foreach
(
long
key
in
toReturn) { cometMessages.Add(cometClient.Messages[key]); }
return
cometMessages.ToArray(); } }
///
<summary>
///
Send a message to a specific client
///
</summary>
///
<param name="clientPublicToken"></param>
///
<param name="name"></param>
///
<param name="contents"></param>
public
void
SendMessage(
string
clientPublicToken,
string
name,
object
contents) {
if
(
string
.IsNullOrEmpty(clientPublicToken))
throw
new
ArgumentNullException(
"
clientPublicToken
"
);
if
(contents
==
null
)
throw
new
ArgumentNullException(
"
contents
"
);
lock
(state) {
if
(
!
publicClients.ContainsKey(clientPublicToken))
throw
CometException.CometClientDoesNotExistException();
//
//
ok, get the client
InProcCometClient cometClient
=
publicClients[clientPublicToken];
//
ok, stick the message in the array
CometMessage message
=
new
CometMessage(); message.Contents
=
contents; message.Name
=
name; message.MessageId
=
cometClient.NextMessageId;
//
increment
cometClient.NextMessageId
++
; cometClient.Messages.Add(message.MessageId, message); } }
///
<summary>
///
Send a message to all the clients
///
</summary>
///
<param name="name"></param>
///
<param name="contents"></param>
public
void
SendMessage(
string
name,
object
contents) {
if
(contents
==
null
)
throw
new
ArgumentNullException(
"
contents
"
);
lock
(state) {
foreach
(InProcCometClient cometClient
in
publicClients.Values) {
//
ok, stick the message in the array
CometMessage message
=
new
CometMessage(); message.Contents
=
contents; message.Name
=
name; message.MessageId
=
cometClient.NextMessageId;
//
increment
cometClient.NextMessageId
++
; cometClient.Messages.Add(message.MessageId, message); } } }
///
<summary>
///
Get the client from the state provider
///
</summary>
///
<param name="clientPrivateToken"></param>
///
<returns></returns>
public
CometClient GetCometClient(
string
clientPrivateToken) {
if
(
!
this
.privateClients.ContainsKey(clientPrivateToken))
throw
CometException.CometClientDoesNotExistException();
//
return the client private token
return
this
.privateClients[clientPrivateToken].CometClient; }
///
<summary>
///
Remove an idle client from the memory
///
</summary>
///
<param name="clientPrivateToken"></param>
public
void
KillIdleCometClient(
string
clientPrivateToken) {
if
(
!
this
.privateClients.ContainsKey(clientPrivateToken))
throw
CometException.CometClientDoesNotExistException();
//
get the client
InProcCometClient ipCometClient
=
this
.privateClients[clientPrivateToken];
//
and remove the dictionarys
this
.privateClients.Remove(ipCometClient.CometClient.PrivateToken);
this
.publicClients.Remove(ipCometClient.CometClient.PublicToken); }
#endregion
} }
PS:可以说这篇随笔中将COMET IM的几个最容易拓展的类进行了介绍,核心部分将在下一篇随笔中进行讲解,欢迎大家讨论并拍砖。同时预祝大家春节愉快!