聊天室:服務器端-------------客戶端數組
最終演示展現圖:緩存
一. 服務器端服務器
對服務端爲了讓主窗體後臺不處理具體業務邏輯,所以對服務端進行了封裝,專門用來處理某個客戶端通訊的過程。網絡
而因爲通訊管理類中須要處理具體與某個客戶端的通訊業務,因此在構造函數中傳入了具體的套接字對象。tcp
針對消息提醒:因爲須要再通訊管理類中進行消息提示,而須要調用主窗體的ShowMsg方法。所以將打印消息的方法經過委託傳給了通訊管理類的構造函數ide
同理針對意外關閉的客戶端鏈接也一樣經過委託將移除客戶端的方法傳給了通訊管理類。函數
所以,再通訊管理類的全局變量裏就有了以下的定義:學習
//與某個客戶端通訊套接字 Socket sokMsg = null; //通訊線程 Thread thrMsg = null; //建立一個委託對象, 在窗體顯示消息的方法 DGShowMsg dgShow = null; //建立一個關閉鏈接的方法 DGCloseConn dgCloseConn = null;
//通訊管理類的構造函數
public MsgConnection(Socket sokMsg, DGShowMsg dgShow, DGCloseConn dgCloseConn).........
1.0 開始監聽this
1.1.1 建立監聽套接字 spa
//建立監聽套接字,使用ip4協議,流式傳輸,tcp連接
sokWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
1.1.2 綁定端口
//獲取網絡節點對象 IPAddress address = IPAddress.Parse(txbIp.Text); IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txbPort.Text)); // 綁定端口(其實內部 就向系統的端口表中註冊了一個端口,並指定了當前程序句柄) sokWatch.Bind(endPoint);
1.1.3 設置監聽隊列,指限制同時處理的鏈接請求數,即同時處理的客戶端鏈接請求。
sokWatch.Listen(10);
1.1.4 開始監聽,調用監聽線程 執行 監聽套接字的監聽方法。
thrWatch = new Thread(WatchConncting); thrWatch.IsBackground = true; thrWatch.Start(); ShowMsg("服務器啓動!");
2.0 服務器監聽方法 + void WatchConncting()
void WatchConncting() { try { //循環監聽客戶端的鏈接請求。 while (isWatch) { //2.4開始監聽,返回了一個通訊套接字 Socket sockMsg = sokWatch.Accept(); //2.5 建立通訊管理類 MsgConnection conn = new MsgConnection(sockMsg, ShowMsg, RemoveClient); //將當前鏈接成功的【與客戶端通訊的套接字】的標識保存起來,並顯示到列表中 //將遠程客戶端的 ip 和 端口 字符串 存入列表 listOnline.Items.Add(sockMsg.RemoteEndPoint.ToString()); //將服務器端的通訊套接字存入字典集合。 dictConn.Add(sockMsg.RemoteEndPoint.ToString(), conn); ShowMsg("有客戶端鏈接了!"); } } catch (Exception ex) { ShowMsg("異常" + ex); } }
3.0 服務端向指定的客戶端發送消息
string strClient = listOnline.Text; if (dictConn.ContainsKey(strClient)) { string strMsg = txtInput.Text.Trim(); ShowMsg("向客戶端【" + strClient + "】說:" + strMsg); //經過指定的套接字將字符串發送到指定的客戶端 try { dictConn[strClient].Send(strMsg); } catch (Exception ex) { ShowMsg("異常" + ex.Message); } }
4.0 根據要中斷的客戶端ipport關閉鏈接 + void RemoveClient(string clientIpPort)
//1.0 移除列表中的項 listOnline.Items.Remove(clientIpPort); //2.0 關閉通訊管理類 dictConn[clientIpPort].Close(); //3.0 從字典中移除對應的通訊管理類的項 dictConn.Remove(clientIpPort);
5.0 選擇要發送的文件
OpenFileDialog ofd = new OpenFileDialog(); if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { //將選中的要發送的文件路徑,顯示到文本框中。 txtFilePath.Text = ofd.FileName; }
6.0 發送文件
string strClient = listOnline.Text; if (dictConn.ContainsKey(strClient)) { string strMsg = txtInput.Text.Trim(); //經過指定的套接字將字符串發送到指定的客戶端 try { dictConn[strClient].SendFile(txtFilePath.Text.Trim()); } catch (Exception ex) { ShowMsg("異常" + ex.Message); } }
7.0 向指定的客戶端發送抖屏
string strClient = listOnline.Text; if (dictConn.ContainsKey(strClient)) { string strMsg = txtInput.Text.Trim(); //經過指定的套接字將字符串發送到指定的客戶端 try { dictConn[strClient].SendShake(); } catch (Exception ex) { ShowMsg("異常" + ex.Message); } }
8.0 打印消息 + ShowMsg(string strmsg)
this.txtShow.AppendText(strmsg + "\r\n");
服務器端通訊管理類,負責處理與某個客戶端通訊的過程
public class MsgConnection { //與某個客戶端通訊套接字 Socket sokMsg = null; //通訊線程 Thread thrMsg = null; //建立一個委託對象, 在窗體顯示消息的方法 DGShowMsg dgShow = null; //建立一個關閉鏈接的方法 DGCloseConn dgCloseConn = null; #region 1.0 構造函數 public MsgConnection(Socket sokMsg, DGShowMsg dgShow, DGCloseConn dgCloseConn) { this.sokMsg = sokMsg; this.dgShow = dgShow; this.dgCloseConn = dgCloseConn; //建立通訊線程,負責調用通訊套接字,來接收客戶端消息。 thrMsg = new Thread(ReceiveMsg); thrMsg.IsBackground = true; thrMsg.Start(this.sokMsg); } #endregion bool isReceive = true; #region 2.0 接收客戶端發送的消息 void ReceiveMsg(object obj) { Socket sockMsg = obj as Socket; //3 通訊套接字 監聽客戶端的消息,傳輸的是byte格式。 //3.1 開闢了一個 1M 的空間,建立的消息緩存區,接收客戶端的消息。 byte[] arrMsg = new byte[1024 * 1024 * 1]; try { while (isReceive) { //注意:Receive也會阻斷當前的線程。 //3.2 接收客戶端的消息,並存入消息緩存區。 //並 返回 真實接收到的客戶端數據的字節長度。 int realLength = sockMsg.Receive(arrMsg); //3.3 將接收的消息轉成字符串 string strMsg = System.Text.Encoding.UTF8.GetString(arrMsg, 0, realLength); //3.4 將消息顯示到文本框 dgShow(strMsg); } } catch (Exception ex) { //調用窗體類的關閉移除方法 dgCloseConn(sokMsg.RemoteEndPoint.ToString()); //顯示消息 dgShow("客戶端斷開鏈接!"); } } #endregion #region 3.0 向客戶端發送文本消息 + void Send(string msg) /// <summary> /// 3.0 向客戶端發送文本消息 /// </summary> /// <param name="msg"></param> public void Send(string msg) { byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(msg); //經過指定的套接字將字符串發送到指定的客戶端 try { sokMsg.Send(MakeNewByte("str",arrMsg)); } catch (Exception ex) { dgShow("異常" + ex.Message); } } #endregion #region 4.0 向客戶端發送文件 + void SendFile(string strPath) /// <summary> /// 4.0 向客戶端發送文件 /// </summary> /// <param name="strFilePath"></param> public void SendFile(string strFilePath) { //4.1 讀取要發送的文件 byte[] arrFile = System.IO.File.ReadAllBytes(strFilePath); //4.2 向客戶端發送文件 sokMsg.Send(MakeNewByte("file", arrFile)); } #endregion #region 4.1 向客戶端發送抖屏命令 + void SendShake() /// <summary> /// 4.1 向客戶端發送抖屏命令 /// </summary> public void SendShake() { sokMsg.Send(new byte[1] { 2 }); } #endregion #region 5.0 返回帶標識的新數組 + byte[] MakeNew(string type, byte[] oldArr) /// <summary> /// 返回帶標識的新數組 /// </summary> /// <param name="type"></param> /// <param name="oldArr"></param> /// <returns></returns> public byte[] MakeNewByte(string type, byte[] oldArr) { //5.1 建立一個新數組(是原數組長度 +1) byte[] newArrFile = new byte[oldArr.Length + 1]; //5.2 將原數組數據複製到新數組中(重新數組下標爲1的位置開始) oldArr.CopyTo(newArrFile, 1); //5.3 根據內容類型爲新數組第一個元素設置標識符號 switch (type.ToLower()) { case "str": newArrFile[0] = 0; //只能存0-255之間的數值 break; case "file": newArrFile[0] = 1; break; default: newArrFile[0] = 2; break; } return newArrFile; } #endregion #region 6.0 關閉通訊 /// <summary> /// 關閉通訊 /// </summary> public void Close() { isReceive = false; sokMsg.Close(); sokMsg = null; } #endregion }
二 . 客戶端
1.0 發送鏈接服務端請求
try { //1.0建立鏈接套接字,使用ip4協議,流式傳輸,tcp連接 sokMsg = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //2.0獲取要連接的服務端節點 //2.1獲取網絡節點對象 IPAddress address = IPAddress.Parse(txtIp.Text); IPEndPoint endPoint = new IPEndPoint(address, int.Parse(txtPort.Text)); //3 向服務端發送連接請求。 sokMsg.Connect(endPoint); ShowMsg("鏈接服務器成功!"); //4 開啓通訊線程 thrMsg = new Thread(ReceiveMsg); thrMsg.IsBackground = true; //win7, win8 須要設置客戶端通訊線程同步設置,才能在接收文件時打開文件選擇框 thrMsg.SetApartmentState(ApartmentState.STA); thrMsg.Start(); } catch (Exception ex) { ShowMsg("鏈接服務器失敗!" + ex.Message); }
2.0 接收服務端消息
//準備一個消息緩衝區域 byte[] arrMsg = new byte[1024 * 1024 * 1]; try { while (isReceive) { //接收 服務器發來的數據,由於包含了一個標示符,因此內容的真實長度應該-1 int realLength = sokMsg.Receive(arrMsg)-1; switch (arrMsg[0]) { //文本 case 0: GetMsg(arrMsg,realLength); break; //文件 case 1: GetFile(arrMsg,realLength); break; default: ShakeWindow(); break; } } } catch (Exception ex) { sokMsg.Close(); sokMsg = null; ShowMsg("服務器斷開鏈接!"); }
2.1 接收服務端文本消息 + GetMsg(byte[] arrContent, int realLength)
//獲取接收的內容,去掉第一個標示符。 string strMsg = System.Text.Encoding.UTF8.GetString(arrContent, 1, realLength); ShowMsg("服務器說:" + strMsg);
2.2 保存文件 + GetFile(byte[] arrContent, int realLength)
SaveFileDialog sfd = new SaveFileDialog(); if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK) { string savaPath = sfd.FileName; //使用文件流,保存文件 using (System.IO.FileStream fs = new System.IO.FileStream(savaPath, System.IO.FileMode.OpenOrCreate)) { //將收到的文件數據數組,寫入硬盤。 fs.Write(arrContent, 1, realLength); } ShowMsg("保存文件到 【" + savaPath + "】成功!"); }
2.3 抖屏 + void ShakeWindow()
// 保存當前窗體位置 Point oldPoint = this.Location; for (int i = 0; i < 15; i++) { //隨機生成新位置 Point newPoint = new Point(oldPoint.X + ran.Next(8), oldPoint.Y + ran.Next(8)); //將新位置設置給窗體 this.Location = newPoint; System.Threading.Thread.Sleep(25); this.Location = oldPoint; //休息15毫秒 System.Threading.Thread.Sleep(25); }
3.0 客戶端發送消息到服務端
string strMsg = txtInput.Text.Trim(); if (strMsg != "") { byte[] arrMsg = System.Text.Encoding.UTF8.GetBytes(strMsg); try { sokMsg.Send(arrMsg); } catch (Exception ex) { ShowMsg("發送消息失敗!" + ex.Message); } } else { MessageBox.Show("未輸入任何信息!"); }
4.0 展現消息方法 + ShowMsg(string strmsg)
this.txtShow.AppendText(strmsg + "\r\n");
注:小弟不斷學習中,還但願園友指導交流。同時也做爲我的學習的一個小記錄。【好記性不如爛筆頭】(*^__^*)
源碼分享: http://download.csdn.net/detail/huayuqiang/8270749