1.結合現狀 功能分析
該功能基於上個項目的改進,主要是通過對服務器端代碼的修改,以及對客戶端作少許修改,實現開啟多客戶端時,一個客戶端發送消息,達到對所有客戶端廣播的效果。可參考網吧里的點歌系統,比如某某用戶在網吧點了一首歌,其他用戶電腦的左下角都會彈出一個某某用戶點了一首七里香,或者游戲里面的頻道聊天,每個人發完消息后,聊天室里的人都知道你發的消息了,就像下圖一樣,這也正是做這個功能的初衷吧。
2.圖說代碼
代碼細說:
服務器里面定義了兩個字段,一個用於服務器與客戶端的連接,另一個目的在於做一個接受廣播的客戶端表單。
服務器里有四個函數,分別對應着客戶端監聽、客戶端連接、接受客戶端消息和發報廣播。
客戶端定義了一個字段
客戶端包含4個函數,分別為建立連接,接受廣播,非后台的發送消息線程、發送消息四部分
操作流程:
1)開啟服務器,即黑線①的過程,啟動監聽。
2)開啟客戶端,自動根據IP連接服務器,即綠線②。
3)客戶端1發送消息至服務器,服務器廣播消息至客戶端2,即紅線③。
3.代碼實現
服務器端包含一個主函數和一個ServerControl類
主函數:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ServerTest { class Program { static void Main(string[] args) { // 調用構造函數,使用Start方法 ServerControl server = new ServerControl(); server.Start(); Console.ReadKey(); } } }
ServerControl類:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ServerTest { public class ServerControl { // 聲明變量(使用Socket需using System.Net.Sockets;) private Socket serverSocket; // 聲明一個集合 private List<Socket> clientList; // 自定義有參構造函數,包含兩個方法(IP地址,流程傳輸方式,TCP協議) public ServerControl() { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); clientList = new List<Socket>(); } // 創建啟動方法(IPEndPoint用於指定地址及端口初始化,需using System.Net;) public void Start() { // 服務器啟動 // 綁定IP地址(為任意IP)與端口(設置為12345) serverSocket.Bind(new IPEndPoint(IPAddress.Any,12345)); serverSocket.Listen(10); Console.WriteLine("服務器啟動成功"); // 開啟線程:目的實現服務器和客戶端一對多連接 Thread threadAccept = new Thread(Accept); threadAccept.IsBackground = true; threadAccept.Start(); } // Accept方法測試:接收客戶端連接 private void Accept() { // 接收客戶端方法,會掛起當前線程(.RemoteEndPoint表示遠程地址) Socket client = serverSocket.Accept(); IPEndPoint point = client.RemoteEndPoint as IPEndPoint; Console.WriteLine(point.Address + "[" + point.Port + "] 連接成功!"); clientList.Add(client); // 開啟一個新線程線程,實現消息多次接收 Thread threadReceive = new Thread(Receive); threadReceive.IsBackground = true; threadReceive.Start(client); // 尾遞歸 Accept(); } // Receive方法的使用測試 // 接收客戶端發送過來的消息,以字節為單位進行操作 // 該方法會阻塞當前線程,所以適合開啟新的線程使用該方法 // Accept()中將Receive作為線程傳遞對象,所以要注意一點,使用線程傳遞對象只能是object類型的!! private void Receive(object obj) { // 將object類型強行轉換成socket Socket client = obj as Socket; IPEndPoint point = client.RemoteEndPoint as IPEndPoint; // 此處的異常拋出主要針對客戶端異常的問題 // 比如,客戶端關閉或者連接中斷 // 程序會停留在int msgLen = client.Receive(msg);這段代碼,而導致無法繼續往下走 try { byte[] msg = new byte[1024]; // 實際接收到字節數組長度,該方法會阻塞當前線程,即(client.Receive(msg)開始掛起) // 同時,這里還是尾遞歸掛起處 int msgLen = client.Receive(msg); // 將msg裝換成字符串 string msgStr = point.Address + "[" + point.Port + "]:" + Encoding.UTF8.GetString(msg, 0, msgLen); Console.WriteLine(msgStr); // 調用廣播函數 Broadcast(client,msgStr); // 尾遞歸實現多條消息的接收;和while同理。 Receive(client); } catch { Console.WriteLine(point.Address + "[" + point.Port + "]積極斷開"); // 若客戶端中斷,則將他在集合中刪除 clientList.Remove(client); } } private void Broadcast(Socket clientOther,string msg) { foreach(var client in clientList) { if(client == clientOther) { // 不做任何響應 } else { client.Send(Encoding.UTF8.GetBytes(msg)); } } } } }
客戶端包含一個主函數和一個ClientControl類
主函數:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ClientTest { class Program { static void Main(string[] args) { // 調用構造函數 ClientControl client = new ClientControl(); // 輸入本機IP與端口號 client.Connect("129.211.7.135", 12345); // 啟動send方法 client.Send(); Console.ReadKey(); } } }
ClientControl類:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ClientTest { public class ClientControl { // 聲明變量 private Socket clientSocket; // 自定義有參構造方法((IP地址,流程傳輸方式,TCP協議)) public ClientControl() { clientSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp); } // 創建通過IP與端口號連接的方法 public void Connect(string ip,int port) { clientSocket.Connect(ip, port); Console.WriteLine("連接服務器成功"); // 客戶端接收服務器消息的線程 Thread threadReceive = new Thread(Receive); threadReceive.IsBackground = true; threadReceive.Start(); } // 用於測試服務器向客戶端返回一條消息 private void Receive() { while(true) { try { // 用於接收服務器的回復信息 byte[] msg = new byte[1024]; int msgLen = clientSocket.Receive(msg); Console.WriteLine("服務器:"+Encoding.UTF8.GetString(msg,0,msgLen)); } // 異常處理方法 catch { Console.WriteLine("服務器積極拒絕!!"); // 退出while循環 break; } } } // Send方法測試:即發送消息,以字節為單位 public void Send() { Thread threadSend = new Thread(ReadAndSend); // 將該線程設為非后台線程。 // threadSend.IsBackground = true; threadSend.Start(); } private void ReadAndSend() { // 提示操作方法 Console.WriteLine("請輸入發送至服務器的內容或者輸入quit退出"); // 輸入內容 string msg = Console.ReadLine(); // 非退出情況下操作方式,使用while可以持續不斷的接收用戶輸入 while (msg != "quit") { clientSocket.Send(Encoding.UTF8.GetBytes(msg)); msg = Console.ReadLine(); } } } }
4.實現過程
該過程將服務器部署至騰訊雲服務器,分別在騰訊雲服務器和本地PC上各開啟2個客戶端演示廣播過程。
若沒有多余的電腦或者雲服務器,可將客戶端主函數里面的IP地址代碼改為127.0.0.1,即可完成本地測試。