標籤:
軟體工程 2016.7.5日報
今天我的主要工作是晚場了用戶端功能的搭建、串連了用戶端UI與用戶端Socket部分的功能,為服務端增加了檔案鎖避免多個線程對同一檔案同時操作。
具體實現的工作有:
用戶端功能搭建:
在用戶端完成了通訊功能的實現:
補全了昨天空缺的代碼,在收到訊息時進行相應的處理:
1 if (arrMsg[0] == SEND_MSG) 2 { 3 ReceiveMsgFromServer(msgReceive); 4 } 5 else if (arrMsg[0] == IS_RECEIVE_MSG) 6 { 7 Application.Current.Dispatcher.Invoke(new Action(delegate 8 { 9 MessageBox.Show("發送訊息成功");10 }));11 }12 else if (arrMsg[0] == IS_NOT_RECEIVE_MSG)13 {14 Application.Current.Dispatcher.Invoke(new Action(delegate15 {16 MessageBox.Show("[Error]發送訊息失敗");17 }));18 }19 else if (arrMsg[0] == INVALID_MESSAGE)20 {21 Application.Current.Dispatcher.Invoke(new Action(delegate22 {23 MessageBox.Show("[Error]通訊過程出錯");24 }));25 }26 else27 {28 Application.Current.Dispatcher.Invoke(new Action(delegate29 {30 MessageBox.Show("[Error]通訊過程出錯");31 }));32 }
其中,ReceiveMsgFromServer(string str);的具體實實現如下:
1 #region --- Receive Room History Message --- 2 /// <summary> 3 /// Receive Message 4 /// </summary> 5 /// <param name="msgReceive"></param> 6 private void ReceiveMsgFromServer(string msgReceive) 7 { 8 MsgHandler msgHandler = (MsgHandler)JsonConvert.DeserializeObject(msgReceive, typeof(MsgHandler)); 9 string roomId = msgHandler.roomId;10 List<string> msgList = msgHandler.msgList;11 12 Application.Current.Dispatcher.Invoke(new Action(delegate13 {14 tucaoWall.Document.Blocks.Clear();15 string room = (string)courseList.SelectedItem;16 if (room.Equals(roomId))17 foreach (string msg in msgList)18 {19 // TODO : 將訊息逐一添加到顯示框中20 Paragraph newParagraph = new Paragraph();21 22 InlineUIContainer inlineUIContainer = new InlineUIContainer()23 {24 Child = new TextBlock()25 {26 Foreground = new SolidColorBrush(Colors.Black),27 TextWrapping = TextWrapping.Wrap,28 Text = msg + "\r\n"29 }30 };31 newParagraph.Inlines.Add(inlineUIContainer);32 33 tucaoWall.Document.Blocks.Add(newParagraph);34 }35 36 }));37 }38 #endregion
用戶端UI與用戶端Socket的串連
添加了響應控制項的響應,使用者點擊不同的按鈕會調用不同的方法與伺服器進行互動。由於代碼過於瑣碎,在此不做列舉。
伺服器端修改了部分邏輯,同時對檔案增加了讀寫鎖。
由於原來在服務端對每個使用者都建立了一個線程,保證了在接受訊息端是同步的。但是卻沒有保證發送訊息、處理檔案等的並發性(可以同時操縱多個檔案)。所以將相關的方法封裝為一個類,對於每一個新連結的使用者建立一個該類對象,這樣每個使用者就可以通過自己儲存的類對象進行相關的操作,而互不影響。此部分功能實現代碼如下:
添加從使用者ip索引處理對象的字典
public static Dictionary<string, Handler> dictHandler = new Dictionary<string, Handler>();
處理對象Handler封裝的方法包括
public void CheckRoomList(string s_roomList); public void AddMsgToFile(string clientIP, string msg); public void InvalidMsg(string clientIP); public void SendMessage(string clientIP, byte flag, string msg);
使用特定ip對應的Handler對象的方法為
1 if (msgReceiver[0] == CHECK_ROOM_LIST) 2 dictHandler[socketKey].CheckRoomList(msg); 3 else if (msgReceiver[0] == REQUEST_ROOM_MSG) 4 dictHandler[socketKey].GetRoomMsg(socketKey, msg); 5 else if (msgReceiver[0] == SEND_MSG) 6 dictHandler[socketKey].AddMsgToFile(socketKey, msg); 7 else if (msgReceiver[0] == DISCONNECT) 8 RemoveOfflineUser(socketKey); 9 else10 dictHandler[socketKey].InvalidMsg(socketKey);
實現上述功能之後,還需要處理檔案讀寫同步的問題,同一檔案在同一時間不能被多個線程操作,所以為解決此問題需要為每個檔案添加一個讀寫鎖,來控制檔案讀寫同步的問題。
在程式中建立由檔案名稱索引到讀寫鎖的字典
public static Dictionary<string, Object> dictLocker = new Dictionary<string, object>();
在每次操作檔案時都需要進行並發控制,檢測相應讀寫鎖的使用方式,使用例子如下:
1 lock(Server.dictLocker[room]) 2 { 3 FileStream fs = new FileStream(roomFile, FileMode.Create); 4 fs.Close(); 5 6 lock (Server.dictLocker["room"]) 7 { 8 string romFile = "room.txt"; 9 10 FileStream f = File.OpenWrite(romFile);11 12 f.Position = f.Length;13 14 byte[] writeMsg = Encoding.UTF8.GetBytes(room+"\r\n");15 16 f.Write(writeMsg, 0, writeMsg.Length);17 18 f.Close();19 }20 }
通過上述方式,實現了檔案的讀寫控制。
軟體工程 2016.7.5日報