软件工程 2016.7.5日报
软件工程 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(delegate 15 { 16 MessageBox.Show("[Error]发送消息失败"); 17 })); 18 } 19 else if (arrMsg[0] == INVALID_MESSAGE) 20 { 21 Application.Current.Dispatcher.Invoke(new Action(delegate 22 { 23 MessageBox.Show("[Error]通信过程出错"); 24 })); 25 } 26 else 27 { 28 Application.Current.Dispatcher.Invoke(new Action(delegate 29 { 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(delegate 13 { 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 else 10 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 }
通过上述方式,实现了文件的读写控制。