Remoting實現系統廣播和即時聊天

     公司的系統需要有系統消息提醒,即別的用戶發的在線短信或者手機短信的提醒。按照以往的做法,一般是客戶端發請求,去服務器查詢數據庫,然后返回信息,有消息則提醒。這么做的弊端就是,不管用戶是否有新消息,客戶端每隔一段時間總要發送請求,這樣做的話既消耗帶寬又占用服務器資源,增加數據庫負擔,而且大多數情況用戶是沒有任何消息,都是無用功,并且消息提醒也不即時,只有客戶端去查詢才能獲知,所以這種做法并不好。而Remoting允許客戶端訂閱,服務器監聽客戶端的,如果服務器端有消息即刻通知客戶端,既不會浪費帶寬增加負擔也達到了即時提醒的效果。
     說了這么多了,現在開始說如何實現的。我的實驗例子創建了3個工程,CommonLib、MessageClient、MessageServer。MessageClient就是客戶端,用來發布和接收消息,MessageServer是服務端,用來派發收到的消息,CommonLib是一個公用的類庫,被客戶端和服務端分別引用的。

     CommonLib里包含了一個接口 IMessageBody 和一個事件類 MessageEvent。
     IMessageBody:

public interface IMessageBody
    {
        [System.Runtime.Remoting.Messaging.OneWay]
        
void SendMsg(string strText, DateTime datTime);

        
void AddEvent(string strKey, MessageEvent cEvent);

        
void RemoveEvent(string strKey, MessageEvent cEvent);
    }

     MessageEvent:
    public delegate void ReceiveMessageHandler(string strMsg);

    
public class MessageEvent : MarshalByRefObject
    
{
        
public event ReceiveMessageHandler ReceiveMsg;

        
//[System.Runtime.Remoting.Messaging.OneWay]
        public void OnReceive(string strMsg)
        
{
            
if (ReceiveMsg != null)
            
{
                ReceiveMsg(strMsg);
            }

            
//如果消息到达太频繁(每秒上万条),应该做缓存(Buffer)处理。
        }

    }

 

     MessageServer里很簡單,啟動時加載了一個配置文件,還有一個接口IMessageBody的實現類MessageBody。

        private void Init()
        
{
            
try
            
{
                RemotingConfiguration.Configure(
"ServerCfg.config"false);
                
this.Text = "已启动";
            }

            
catch
            
{
                
this.Text = "启动失败";
            }

        }
     配置文件ServerCfg.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.runtime.remoting>
    
<application>
      
<service>
        
<wellknown
                    mode
="Singleton"
                    type
="MessageServer.MessageBody, MessageServer"
                    objectUri
="MSG" />
      
</service>
      
<channels>
        
<channel port="8888" ref="tcp" name ="tcpServer">
          
<serverProviders>
            
<formatter ref="soap" typeFilterLevel="Full"/>
            
<formatter ref="binary" typeFilterLevel="Full"/>
          
</serverProviders>
        
</channel>
      
</channels>
    
</application>
  
</system.runtime.remoting>
</configuration>

     MessageBody:

    class MessageBody : MarshalByRefObject, CommonLib.IMessageBody
    
{
        
public event ReceiveMessageHandler ReceiveMsg;

        
private IDictionary<string, ReceiveMessageHandler> _EventList = new Dictionary<string, ReceiveMessageHandler>();

        
public void SendMsg(string strText, DateTime datTime)
        
{
            Subscribe();
            
//激发客户端的消息到达事件。
            if (ReceiveMsg != null)
            
{
                ReceiveMessageHandler receicve 
= null;

                
//ReceiveMsg(strText + "  " + datTime);

                
foreach (Delegate dele in ReceiveMsg.GetInvocationList())
                

                    
try
                    
{
                        receicve 
= (ReceiveMessageHandler)dele;
                        receicve(strText 
+ "  " + datTime);
                    }

                    
catch
                    
{
                        RemoveEvent(receicve);
                    }

                    ReceiveMsg 
-= receicve;
                }

            }

        }



        
public void AddEvent(string strKey, CommonLib.MessageEvent cEvent)
        
{
            _EventList.Add(strKey, 
new ReceiveMessageHandler(cEvent.OnReceive));
        }


        
public void RemoveEvent(ReceiveMessageHandler handler)
        
{
            
foreach (string key in _EventList.Keys)
            
{
                
if (_EventList[key].Equals(handler))
                
{
                    _EventList.Remove(key);
                    
return;
                }

            }

        }


        
public void RemoveEvent(string strKey, CommonLib.MessageEvent cEvent)
        
{
            _EventList.Remove(strKey);
        }


        
private void Subscribe()
        
{
            
foreach (string key in _EventList.Keys)
            
{
                
this.ReceiveMsg += _EventList[key];
            }

        }

    }

 

     客戶端的一個窗體ClientForm:

    public partial class ClientForm : Form
    
{
        
private CommonLib.IMessageBody _bdBody;
        
private CommonLib.MessageEvent _cEvent;
        
private string _strKey = Guid.NewGuid().ToString();

        
public ClientForm()
        
{
            InitializeComponent();
            Init();
        }


        
private void Init()
        
{
            
// 通过编程设置反序列的级别
            BinaryServerFormatterSinkProvider provider = new BinaryServerFormatterSinkProvider();
            provider.TypeFilterLevel 
= System.Runtime.Serialization.Formatters.TypeFilterLevel.Full;
            BinaryClientFormatterSinkProvider Clientprovider 
= new BinaryClientFormatterSinkProvider();
            
// 设置通道属性
            System.Collections.IDictionary props = new System.Collections.Hashtable();
            props[
"port"= 0;
            
//注册TCP通道,用于连接自己或者他人的服务器。
            TcpChannel tcpChl = new TcpChannel(props, Clientprovider, provider);

            ChannelServices.RegisterChannel(tcpChl, 
false);
        }


        
private void btnConnect_Click(object sender, EventArgs e)
        
{
            
//与服务器创建连接
            _bdBody = (CommonLib.IMessageBody)Activator.GetObject(
                      
typeof(CommonLib.IMessageBody),
                      
"tcp://" + ServerAddress.Text.Trim() + "/MSG");

            
if (_bdBody == null)
                
return;

            
try
            
{
                _cEvent 
= new CommonLib.MessageEvent();
                _bdBody.AddEvent(_strKey, _cEvent);

                _cEvent.ReceiveMsg 
+= new CommonLib.ReceiveMessageHandler(receive);

                ServerAddress.Enabled 
= false;
                btnConnect.Enabled 
= false;

                
this.Text = "已连接"
            }

            
catch
            
{
                
this.Text = "无连接"
                
return;
            }

        }


        
private void receive(string s)
        
{
            
try
            
{
                
//转到主线程
                if (this.InvokeRequired)
                
{
                    CommonLib.ReceiveMessageHandler handler 
= new CommonLib.ReceiveMessageHandler(Show);
                    Invoke(handler, s);
                }

            }

            
catch (Exception Ex)
            
{
                MessageBox.Show(Ex.Message);
            }

        }


        
private void Show(string str)
        
{
            txtReceived.Text 
= str + Environment.NewLine + txtReceived.Text;
        }


        
private void btnSend_Click(object sender, EventArgs e)
        
{
            
string str = SendMsg.Text.Trim();
            
if (_bdBody != null)
                _bdBody.SendMsg(str, DateTime.Now);
        }


        
private void ClientForm_FormClosed(object sender, FormClosedEventArgs e)
        
{
            _bdBody.RemoveEvent(_strKey, _cEvent);
        }

    }

 

     以上就是示例的所有代碼,其他沒太多解釋的地方,說一下那個MessageBody類吧,這個類浪費了我不少時間來實現。

     大家可以看到在AddEvent和RemoveEvent這兩個方法中,我并沒有像一般的做法那樣直接綁定事件(ReceiveMsg += new ReceiveMessageHandler(cEvent.OnReceive); 和 ReceiveMsg -= cEvent.OnReceive),而是用了一個 IDictionary<string, ReceiveMessageHandler> _EventList 的集合來存放客戶端的訂閱事件,然后只是在發送消息的時候才去綁定,也就是Subscribe()的方法,當消息發送了之后即刻取消綁定(ReceiveMsg -= receicve; )。這樣做的好處在于,改進一下 Subscribe() 方法即可對特定的人發送消息(只觸發特定的人的訂閱事件),而不用像廣播一樣全體發送。

     大家還可以看到在SendMsg的方法中有一句被注釋的語句,ReceiveMsg(strText + "  " + datTime);。這句話其實是系統自動做遍歷把消息發送到每個訂閱的客戶端。正常情況下如果客戶端正常退出取消訂閱則系統不會出問題,但如果客戶端非正常退出,沒有取消訂閱,那服務端在遍歷到這里的時候就會報錯,導致后面的消息無法發送。后來我在MessageEvent的OnReceive方法上,加上了[System.Runtime.Remoting.Messaging.OneWay]這個屬性,他的作用是標明此方法是單向的,執行后不用等回復,并摒棄錯誤信息。這樣做了之后,確實可以運行了,但是每當到了這里的時候都會卡一下,而且如果類似情況多了,系統會變得很慢,如果發現出錯能取消此客戶端訂閱就好了。就是這個問題話了我不少時間,總算找到了解決方法,就是不用原先的ReceiveMsg(strText + "  " + datTime);,自己遍歷所有的訂閱客戶端,并發送消息,取消OnReceive的OneWay屬性,如果發送報錯則從集合中移除此訂閱,那以后就可高枕無憂了。

     有人要問,我的題目是系統廣播和即時聊天,為何只有一個實例?其實,這個實例就是個即時聊天,對于系統廣播,客戶端只接收不發送,服務端只發送不接收即可實現了。呵呵。

posted @ 2008-10-25 09:34  變色龍  阅读(682)  评论(0编辑  收藏  举报