一個基於TCP/IP的小項目,實現廣播消息的功能。(超詳細版)


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,即可完成本地測試。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM