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
ExpandedBlockStart.gifContractedBlock.gif    
{
        
public event ReceiveMessageHandler ReceiveMsg;

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

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

    }

 

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

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

            
catch
ExpandedSubBlockStart.gifContractedSubBlock.gif            
{
                
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
ExpandedBlockStart.gifContractedBlock.gif    
{
        
public event ReceiveMessageHandler ReceiveMsg;

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

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

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

                
foreach (Delegate dele in ReceiveMsg.GetInvocationList())
ExpandedSubBlockStart.gifContractedSubBlock.gif                

                    
try
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
{
                        receicve 
= (ReceiveMessageHandler)dele;
                        receicve(strText 
+ "  " + datTime);
                    }

                    
catch
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
{
                        RemoveEvent(receicve);
                    }

                    ReceiveMsg 
-= receicve;
                }

            }

        }



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


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

            }

        }


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


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

        }

    }

 

     客戶端的一個窗體ClientForm:

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

        
public ClientForm()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            InitializeComponent();
            Init();
        }


        
private void Init()
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
// 通过编程设置反序列的级别
            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)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            
//与服务器创建连接
            _bdBody = (CommonLib.IMessageBody)Activator.GetObject(
                      
typeof(CommonLib.IMessageBody),
                      
"tcp://" + ServerAddress.Text.Trim() + "/MSG");

            
if (_bdBody == null)
                
return;

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

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

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

                
this.Text = "已连接"
            }

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

        }


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

            }

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

        }


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


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


        
private void ClientForm_FormClosed(object sender, FormClosedEventArgs e)
ExpandedSubBlockStart.gifContractedSubBlock.gif        
{
            _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屬性,如果發送報錯則從集合中移除此訂閱,那以后就可高枕無憂了。

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

转载于:https://www.cnblogs.com/sicily/archive/2008/10/25/1319231.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值