久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

我的服裝DRP之即時(shí)通訊——為WCF增加UDP綁定(應(yīng)用篇)

 昵稱10504424 2013-01-31

發(fā)個(gè)牢騷,,博客園發(fā)博文竟然不能寫副標(biāo)題。這篇既為我的服裝DRP系列第二篇,,也給為WCF增加UDP綁定系列收個(gè)尾,。原本我打算記錄開發(fā)過程中遇到的一些問題和個(gè)人見解,不過寫到一半發(fā)現(xiàn)要寫的東西實(shí)在太多,,有些問題甚至不好描述,,又擔(dān)心誤導(dǎo)讀者,就作罷了,。

說到即時(shí)通訊大伙都會(huì)第一時(shí)間想到QQ等聊天軟件,,似乎跟服裝DRP八竿子打不著。即時(shí)通信翻譯自Instant Messaging,,如果我把它解釋為即時(shí)消息推送,,再將其放之于企業(yè)應(yīng)用中就好理解了。舉例:上級(jí)給下級(jí)發(fā)貨,,下級(jí)能第一時(shí)間知道貨已發(fā)出,,就用不著打電話詢問或滿心期待地頻繁刷新列表;下級(jí)店鋪賣出一單,,正在為銷售淡季發(fā)愁的老板看到蹦出的提示消息,,瞬間有了信心……

這個(gè)功能對不明真相的客戶并沒有多少吸引力,因?yàn)榇蟛糠諧S軟件似乎都能做到這一點(diǎn),,只不過——或多或少延遲個(gè)幾秒或幾分鐘,,當(dāng)然客戶對延遲一無所知。但是做技術(shù)的知道這個(gè)延遲代表什么:頻繁地訪問數(shù)據(jù)源,,頻繁地將“最新”數(shù)據(jù)與本地?cái)?shù)據(jù)作比較[or直接使用獲取到的數(shù)據(jù)]刷新UI。假設(shè)對數(shù)據(jù)實(shí)時(shí)精度控制為1分鐘,,有1000個(gè)客戶端運(yùn)行,,平均每個(gè)客戶端對10種數(shù)據(jù)類型感興趣(比如數(shù)據(jù)類型(即時(shí)通信中可稱為消息類型)包括入庫、發(fā)貨、零售和調(diào)撥,,或者基礎(chǔ)資料的修改等等),,那么每分鐘就會(huì)產(chǎn)生額外的10000次的數(shù)據(jù)庫的訪問量,注意大部分訪問都沒有任何作用(除了副作用),,而且假如沒有合理地設(shè)置篩選條件[及其它改善手段],,那么訪問產(chǎn)生的數(shù)據(jù)量,大部分也可能是無用的,。另外,,合理的數(shù)據(jù)結(jié)構(gòu)和邏輯設(shè)計(jì)以滿足對各類數(shù)據(jù)類型的提示也是個(gè)不小的難點(diǎn),畢竟數(shù)據(jù)類型多種多樣,,就單個(gè)數(shù)據(jù)類來說,,也有多個(gè)屬性,假如用戶對其中的某些屬性感興趣,,如何設(shè)計(jì)一種方式使得數(shù)據(jù)庫中某條記錄的某些字段變動(dòng)時(shí)能檢索到,,嘖嘖,水很深喲,。

注意:即時(shí)通訊和BS幾乎沒關(guān)系,,BS應(yīng)用先天不足,只能采用定時(shí)讀取數(shù)據(jù)庫的方式來模擬即時(shí)通信,,同上述的大部分CS軟件,。也許用插件能行,但是插件本質(zhì)上也是CS中的C,。上回說到BS的缺點(diǎn),,這里又能加上一條,呵呵,,開個(gè)玩笑,。

請時(shí)刻注意本文所說的IM并非單純的聊天軟件,而是為企業(yè)應(yīng)用系統(tǒng)服務(wù)的輔助類工具,。它應(yīng)該具有相對獨(dú)立性,、良好的擴(kuò)展性和簡便的應(yīng)用性(應(yīng)用是對用戶和開發(fā)人員兩者來說的,用戶能方便的使用它,,開發(fā)人員能方便地將它接入系統(tǒng)),。按照本系列慣例,列客戶關(guān)注的幾個(gè)功能需求:

  1. 在線用戶管理(這在大中型服裝企業(yè)比較有用,,能有效跟進(jìn)各個(gè)分支機(jī)構(gòu)的分銷系統(tǒng)使用情況),;
  2. 系統(tǒng)消息廣播;
  3. 單點(diǎn)登錄(當(dāng)已有相同賬號(hào)在線時(shí),,兩種處理方式,,一是登錄失敗,,一是仿QQ,將原在線用戶踢下線,;用數(shù)據(jù)庫方式能實(shí)現(xiàn)前一種,。); 
  4. 業(yè)務(wù)事件成功后可自動(dòng)[對N個(gè)目標(biāo)客戶端]發(fā)送消息,;
  5. 用戶接收消息權(quán)限管理(是否能接收某個(gè)類型的消息),;
  6. 消息提示;
  7. 消息查詢(目前并未提供往期歷史消息查詢
  8. 企業(yè)通訊工具(重點(diǎn)是美工,,推后)
  9. ……

需求看似挺多,,其實(shí)技術(shù)實(shí)現(xiàn)起來難點(diǎn)就一個(gè):UDP打洞。單純打洞而言,,直接用Socket編碼相當(dāng)簡單,。不過為了提升自己對WCF的理解,我決定使用WCF來完成,,后來發(fā)現(xiàn)這真是自討苦吃(一些知識(shí)要點(diǎn)記錄在為WCF增加UDP綁定(儲(chǔ)備篇))中,。依托WCF框架進(jìn)行UDP通信與直接使用Socket相比,也有很多好處,,比如消息的傳遞被封裝為方法的調(diào)用,,更符合咱“高層開發(fā)者”的口味。WCF原生支持的綁定類型并沒有給實(shí)現(xiàn)打洞提供太多可用信息(TCP等若干綁定能獲取發(fā)送端IPEndPoint信息),,因此我使用微軟后來提供的UDP綁定封裝示例,,并增加了設(shè)置通信端口和獲取發(fā)送端IPEndPoint的功能,這兩者是實(shí)現(xiàn)打洞的前提,,此處不予贅述,。下面關(guān)注業(yè)務(wù)代碼。

復(fù)制代碼
 1 /// <summary>
 2 /// 用戶終端
 3 /// </summary>
 4 [DataContract]
 5 public class UserPoint
 6 {
 7     /// <summary>
 8     /// 用戶標(biāo)識(shí)
 9     /// </summary>
10     [DataMember]
11     public string UserGuid { get; set; }
12
13     [DataMember]
14     public int UserID { get; set; }
15     [DataMember]
16     public string UserName { get; set; }
17     [DataMember]
18     public int OrganizationID { get; set; }
19     [DataMember]
20     public string OrganizationName { get; set; }
21
22     /// <summary>
23     /// 用戶主機(jī)用于偵聽和發(fā)送消息的網(wǎng)絡(luò)地址(和端口)
24     /// </summary>
25     [DataMember]
26     public string NetPointAddress { get; set; }
27
28     public string UDPIMIPPort
29     {
30         get
31         {
32             if (string.IsNullOrEmpty(NetPointAddress))
33                 return "";
34             else
35                 return "soap.udp://" + NetPointAddress;
36         }
37     }
38
39     //給子類使用
40     //WCF不支持繼承,,可以使用KnowType,子類并非定義在當(dāng)前程序集,此處用顯式轉(zhuǎn)換
41     public UserPoint ConvertToBase()
42     {
43         return new UserPoint
44         {
45             OrganizationID = this.OrganizationID,
46             OrganizationName = this.OrganizationName,
47             UserID = this.UserID,
48             UserName = this.UserName,
49             NetPointAddress = this.NetPointAddress,
50             UserGuid = this.UserGuid
51         };
52     }
53 }
復(fù)制代碼

接著定義服務(wù)契約,,由于客戶端會(huì)相互通信,在打洞時(shí)服務(wù)端也會(huì)調(diào)用客戶端方法,,因此所有客戶端在運(yùn)行時(shí)也要寄宿服務(wù),。

服務(wù)端: 

復(fù)制代碼
 1 [ServiceContract(Namespace = "http://www./erp/")]
 2 public interface IServerService
 3 {
 4     /// <summary>
 5     /// 用戶登入[到服務(wù)器端用戶列表]
 6     /// </summary>
 7     [OperationContract(IsOneWay = true)]
 8     void UserLogin(UserPoint user);
 9
10     /// <summary>
11     /// 用戶登出[移出服務(wù)器端用戶列表]
12     /// </summary>
13     [OperationContract(IsOneWay = true)]
14     void UserLogout(UserPoint user);
15
16     /// <summary>
17     /// 叫用戶A給用戶B方向發(fā)一條消息(打洞)
18     /// </summary>
19     /// <param name="callingUser">打洞方</param>
20     /// <param name="waitingUserID">等待方標(biāo)識(shí)</param>
21     [OperationContract(IsOneWay = true)]
22     void CallUserToPunchHole(UserPoint callingUser, string waitingUserGuid);
23
24     /// <summary>
25     /// 維持映射端口
26     /// </summary>
27     [OperationContract(IsOneWay = true)]
28     void HoldMyPort();
29 }
復(fù)制代碼

注意已映射端口在一段時(shí)間不使用后會(huì)自動(dòng)失效。我在本地測試時(shí),,100秒端口還能用,,能相互通信,120秒后失效,,服務(wù)器再通過原先端口給客戶端發(fā)送訊息,,客戶端不再接收到。為了維持有效性,,需要客戶端定時(shí)給服務(wù)器發(fā)送消息(反之應(yīng)該也可以,?),。HoldMyPort就是這個(gè)作用,,一般實(shí)現(xiàn)為空方法,。

客戶端[服務(wù)]: 

復(fù)制代碼
 1 /// <summary>
 2 /// 客戶端服務(wù),主要用來接收各種消息
 3 /// </summary>
 4 [ServiceContract(Namespace = "http://www./erp/")]
 5 public interface IClientService
 6 {
 7     /// <summary>
 8     /// 用戶上線通知
 9     /// </summary>
10     [OperationContract(IsOneWay = true)]
11     void NotifyWhenUserLogin(UserPoint user);
12
13     /// <summary>
14     /// 用戶下線通知
15     /// </summary>
16     [OperationContract(IsOneWay = true)]
17     void NotifyWhenUserLogout(UserPoint user);
18
19     /// <summary>
20     /// 消息通知
21     /// </summary>
22     [OperationContract(IsOneWay = true)]
23     void NotifyMessage(IMessage message);
24
25     /// <summary>
26     /// 打洞
27     /// </summary>
28     [OperationContract(IsOneWay = true)]
29     void NotifyPunchHole(UserPoint waitingUser);
30
31     /// <summary>
32     /// sbody say "hi" to me
33     /// <remarks>屬于打洞過程</remarks>
34     /// </summary>
35     [OperationContract(IsOneWay = true)]
36     void SayHi(UserPoint callingUser);
37
38     /// <summary>
39     /// 踢我下線
40     /// </summary>
41     [OperationContract(IsOneWay = true)]
42     void KickOff(UserPoint user);
43 }
復(fù)制代碼

當(dāng)用戶登錄系統(tǒng)時(shí),,發(fā)送訊息給服務(wù)器,,服務(wù)端將執(zhí)行下述方法:

復(fù)制代碼
 1 public void UserLogin(UserPoint user)
 2 {
 3     var users = MainWindowVM.OnlineUsers.Where(o => o.UserID == user.UserID).ToArray();
 4     lock (((ICollection)MainWindowVM.OnlineUsers).SyncRoot)
 5     {
 6         if (users.Count() > 0)
 7         {
 8             Parallel.ForEach(users, u =>
 9             {
10                 MainWindowVM.OnlineUsers.Remove(u);
11                 ServerService.InvokeClientService(u, service => service.KickOff(u.ConvertToBase()));
12             });
13         }
14     }
15     OperationContext context = OperationContext.Current;
16     //獲取傳進(jìn)的消息屬性
17     MessageProperties properties = context.IncomingMessageProperties;
18     //獲取消息發(fā)送的遠(yuǎn)程終結(jié)點(diǎn)IP和端口
19     IPEndPoint endpoint = properties[RemoteEndpointMessageProperty.Name] as IPEndPoint;
20     user.NetPointAddress = endpoint.ToString();
21     lock (((ICollection)MainWindowVM.OnlineUsers).SyncRoot)
22     {
23         MainWindowVM.OnlineUsers.Add(new ServerUserPoint(user) { LoginTime = DateTime.Now });
24     }
25     NotifyWhenUserLogin(user);
26 }
27
28 /// <summary>
29 /// 通知所有在線用戶有新用戶上線了
30 /// </summary>
31 /// <param name="user">上線用戶</param>
32 private void NotifyWhenUserLogin(UserPoint user)
33 {
34     lock (((ICollection)MainWindowVM.OnlineUsers).SyncRoot)//避免在循環(huán)過程中集合被修改
35     {
36         for (int i = 0; i < MainWindowVM.OnlineUsers.Count; i++)
37         {
38             var u = MainWindowVM.OnlineUsers.ElementAtOrDefault(i);
39             if (u != null && u.UserID != user.UserID)
40                 InvokeClientService(u, service => service.NotifyWhenUserLogin(user));
41         }
42     }
43 }
復(fù)制代碼

測試該方法需要三臺(tái)最好處于不同局域網(wǎng)內(nèi)的機(jī)子,其中一臺(tái)通過NAT映射為公網(wǎng)服務(wù)器,。
單點(diǎn)登錄:當(dāng)有相同賬號(hào)用戶在線或系統(tǒng)管理員在服務(wù)端使用了踢TA下線的功能后,,該賬號(hào)已在線用戶將被強(qiáng)制退出系統(tǒng)。原本面對這樣的需求,,我們常常在用戶數(shù)據(jù)表中增加一個(gè)標(biāo)識(shí)用戶是否在線的字段,,當(dāng)用戶登錄成功置為1,退出則置為0,。但這只能實(shí)現(xiàn)后續(xù)用戶登錄失敗,,而不會(huì)給已在線用戶帶來任何影響,另外會(huì)帶來一個(gè)發(fā)生率較高的問題:系統(tǒng)異常退出,,極端的情況諸如斷電,,那么用戶以后就再也登錄不了了,除非增加一個(gè)重置狀態(tài)的功能,,假如用戶數(shù)多的話,,那系統(tǒng)管理員就有的忙了。無論如何,,這不是一個(gè)好的方法,。假如有一天,客戶希望取消同時(shí)在線數(shù)的限制,,或者,,取消部分用戶的同時(shí)在線數(shù)限制,那么開發(fā)人員就有的忙了,。有了IM,,一切都變得相當(dāng)輕松。我們只要在用戶登入IM時(shí)進(jìn)行相應(yīng)的處理即可,,我們甚至可以決定哪些用戶不能重復(fù)登入,,哪些可以重復(fù)登入。由于IM相對獨(dú)立,,改動(dòng)起來比較方便,,而且IM服務(wù)端只運(yùn)行在服務(wù)器上,也不存在部署問題,。強(qiáng)制用戶退出只需要請求相應(yīng)客戶端的KickOff操作,,此時(shí)客戶端扮演服務(wù)端的角色,。

接下來到了重點(diǎn):打洞。少年們兩眼綻放出異樣的光芒,,卻不知道當(dāng)事者的辛苦,。其實(shí)關(guān)鍵代碼相當(dāng)簡單。

復(fù)制代碼
 1 public static void SendMessageTo(ClientUserPoint user, IMessage message)
 2 {
 3     Action invokeAction = () =>
 4     {
 5         InvokeClientService(user, service => service.NotifyMessage(message));
 6     };
 7     if (user.IsTrustMe)//信任用戶(已經(jīng)建立信任連接)不需要打洞
 8     {
 9         invokeAction();
10     }
11     else
12     {
13         Action action = () =>
14         {
15             int maxTryCount = 3;//最大嘗試次數(shù)
16             for (int i = 0; i < maxTryCount && !user.IsTrustMe; i++)
17             {
18                 InvokeClientService(user, service => service.SayHi(CurrentUser));//我先打招呼
19                 InvokeServerService(service => service.CallUserToPunchHole(user.ConvertToBase(), CurrentUser.UserGuid));//服務(wù)器叫對方給我打招呼
20                Thread.Sleep(500);
21             }
22             if (user.IsTrustMe)
23             {
24                 invokeAction();
25             }
26         };
27         action.BeginInvoke(null, null);
28     }
29 }
復(fù)制代碼

這里有個(gè)問題,,當(dāng)通信雙方處于相同局域網(wǎng),,應(yīng)該期望它們直接通信,省略打洞步驟,。方法是在用戶登錄時(shí)將本機(jī)IP和端口號(hào)(未映射)同時(shí)發(fā)送到服務(wù)端,,當(dāng)客戶端A和客戶端B的映射IP相同則說明他們處于同一內(nèi)網(wǎng),然后根據(jù)本機(jī)地址直連通信,。不過這應(yīng)該有兩個(gè)問題需要解決:當(dāng)局域網(wǎng)內(nèi)存在多級(jí)子網(wǎng)NAT,,A、B分屬不同層,,那么它們還要進(jìn)行內(nèi)部局域網(wǎng)打洞,;本機(jī)IP有時(shí)候并不能準(zhǔn)確獲取,特別有些軟件能生成虛擬IP,。

在打洞成功后我們將對方的IsTrustMe設(shè)置成true,。

復(fù)制代碼
1 public void SayHi(UserPoint callingUser)
2 {
3     if(VMGlobal.CurrentUser != null)
4     {
5         var user = IMHelper.OnlineUsers.Find(o => o.UserGuid == callingUser.UserGuid);
6         if (user != null)
7             user.IsTrustMe = true;
8     }
9 }
復(fù)制代碼

現(xiàn)在就可以直連通信咯。

經(jīng)測試,,打洞過程一般嘗試1次就能連接成功,,此處每次等待500毫秒。

關(guān)于組播,。原本打算采用組播的方式群發(fā)消息(包括所有終端用戶其它用戶上下線的提示消息),,不成想,路由器默認(rèn)情況下是不會(huì)轉(zhuǎn)發(fā)組播包的,,必須在路由器上進(jìn)行配置才行,,解決該問題需要網(wǎng)管進(jìn)行配合,不是編程就能解決的,。而且一般的路由器都不支持組播,,也就是說,目前很多路由器不支持組播協(xié)議,,所以,,局域網(wǎng)的路由器不會(huì)將這個(gè)組播信息傳輸出去,so,,外面的電腦以及路由根本就不知道你這個(gè)組播的信息,。有專門支持組播的路由,不過貌似價(jià)格不菲,。如果路由器不支持組播的話,,那么你的交換機(jī)就把你的組播數(shù)據(jù)當(dāng)成廣播數(shù)據(jù)了,,廣播只能在局域網(wǎng)里面。(該段話來自網(wǎng)絡(luò)),。按照這個(gè)說法,,外部組播數(shù)據(jù)想要進(jìn)入內(nèi)網(wǎng)也困難重重(對or錯(cuò)?),。因此我改用循環(huán)發(fā)送方式,。

最后截個(gè)消息查詢和消息接收權(quán)限的圖,,消息接收權(quán)限設(shè)置我目前將之放入角色管理中,。

至此,IM核心功能基本實(shí)現(xiàn)完畢,,能滿足目前系統(tǒng)的需求(還有大數(shù)據(jù)傳輸?shù)葐栴}暫時(shí)未涉及到就不考慮了),。所謂企業(yè)通訊工具不過是在此基礎(chǔ)上功能的累加,以后再加入吧,。:)

后記:竊以為消息提示只是IM基本輔助功能,,IM還能幫助系統(tǒng)即時(shí)刷新。舉例:當(dāng)權(quán)限管理員為我新增了幾個(gè)模塊權(quán)限,,按照平常的做法,,需要我注銷后重新登錄才能看到,現(xiàn)在只要將新增的模塊信息發(fā)送給我,,我這邊系統(tǒng)自動(dòng)將它們構(gòu)造進(jìn)左側(cè)菜單樹中即可,;我正在下拉框中選擇下級(jí)機(jī)構(gòu)準(zhǔn)備為他發(fā)貨,下拉框中的數(shù)據(jù)項(xiàng)突然增加了一個(gè),,原因是機(jī)構(gòu)管理員錄入了一個(gè)新機(jī)構(gòu),;……

轉(zhuǎn)載本文請注明出處:http://www.cnblogs.com/newton/archive/2013/01/26/2877500.html

標(biāo)簽: WCF, UDP, P2P, 服裝DRP

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多