分享一個C#編寫簡略的聊天法式(具體引見)。本站提示廣大學習愛好者:(分享一個C#編寫簡略的聊天法式(具體引見))文章只能為提供參考,不一定能成為您想要的結果。以下是分享一個C#編寫簡略的聊天法式(具體引見)正文
引言
這是一篇基於Socket停止收集編程的入門文章,我關於收集編程的進修其實不夠深刻,這篇文章是關於本身常識的一個穩固,同時願望能為初學的同伙供給一點參考。文章年夜體分為四個部門:法式的剖析與設計、C#收集編程基本(篇外篇)、聊天法式的完成形式、法式完成。
法式的剖析與設計
1.明白法式功效
假如年夜家如今曾經加入了任務,你的司理或許老板告知你,“小王,我須要你開辟一個聊天法式”。那末接上去該怎樣做呢?你是否是在頭腦裡有個雛形,然後就直接翻開VS2005開端設計窗體,編寫代碼了呢?在開端之前,我們起首須要停止軟件的剖析與設計。就拿本例來講,假如只要這麼一句話“一個聊天法式”,生怕如今年夜家對這個“聊天法式”的概念就很隱約,它可所以像QQ那樣的異常龐雜的一個法式,也能夠是很簡略的聊天法式;它能夠只要在對方在線的時刻才可以停止聊天,也能夠停止留言;它能夠每次將新聞只能發往一小我,也能夠許可發往多小我。它還能夠有一些高等功效,好比向對方傳送文件等。所以我們起首須要停止剖析,而不是一上手就開端做,而剖析的第一步,就是弄清晰法式的功效是甚麼,它可以或許做些甚麼。在這一步,我們的義務是懂得法式須要做甚麼,而不是若何去做。
懂得法式須要做甚麼,我們可以從兩方面動手,接上去我們分離評論辯論。
1.1要求客戶供給更具體信息
我們可以做的第一件事就是要求客戶供給加倍具體的信息。雖然你的司理或老板是你的下屬,但在這個例子中,他就是你的客戶(固然平日情形下,客戶是公司內部拜托公司開辟軟件的人或單元)。當碰到下面這類情形,我們只要少得不幸的一條信息“一個聊天法式”,起首可以做的,就是要求客戶供給加倍確實的信息。好比,你問司理“對這個法式的功效能不克不及供給一些更詳細的信息?”。他能夠會像如許答復:“哦,很簡略,可以登錄聊天法式,登錄的時刻可以或許告訴其他在線用戶,然後與在線的用戶停止對話,假如不想對話了,就刊出或許直接封閉,就這些吧。”
有了下面這段話,我們就又可以得出上面幾個需求:
1.法式可以停止登錄。
2.登錄後可以告訴其他在線用戶。
3.可以與其他用戶停止對話。
4.可以刊出或許封閉。
1.2關於用戶需求停止發問,並停止總結
常常會有如許的情形:能夠客戶給出的需求依然不敷過細,或許客戶本身自己關於需求就很隱約,此時我們須要做的就是針對用戶下面給出的信息停止發問。接上去我就看看若何對下面的需求停止發問,我們至多可以向司理提出以下成績:
NOTE:這裡我交叉一個我在見到的一個印象比擬深入的例子:客戶常常向你表達了激烈的志願他何等何等想具有一個屬於本身的網站,然則,他卻沒有告知你網站都有哪些內容、欄目,可以做甚麼。而作為開辟者,我們明顯關懷的是後者。
1.登錄時須要供給哪些內容?需不須要供給暗碼?
2.許可若干人同時在線聊天?
3.與在線用戶聊地利,可以將一條新聞發給一個用戶,照樣可以一次將新聞發給多個用戶?
4.聊地利發送的新聞包含哪些內容?
5.刊出和封閉有甚麼差別?
6.刊出和封閉對對方需不須要給對方提醒?
因為這是一個典范法式,而我在為年夜家講述,所以我只能再充任一下客戶的腳色,往返答下面的成績:
1.登錄時只須要供給用戶稱號便可以了,不須要輸出暗碼。
2.許可兩小我在線聊天。(這裡我們只講述這類簡略情形,許可多人聊天須要應用多線程)
3.由於只要兩小我,那末天然是只能發給一個用戶了。
4.聊天發送的新聞包含:用戶稱號、發送時光還有注釋。
5.刊出其實不封閉法式,只是分開了對話,可以再次停止銜接。封閉則是加入全部運用法式。
6.刊出和封閉均須要給對方提醒。
好了,有了下面這些信息我們根本上就控制了法式須要完成的功效,那末接上去做甚麼?開端編碼了麼?下面的這些屬於營業流程,除非你對它曾經異常熟習,或許法式異常的小,那末可以對它停止編碼,然則現實中,我們最好再編寫一些用例,如許會使法式的流程加倍的清晰。
1.3編寫用例
平日一個用例對應一個功效或許叫需求,它是法式的一個履行途徑或許履行流程。編寫用例的思緒是:假定你曾經有了如許一個聊天法式,那末你應當若何應用它?我們的應用步調,就是一個用例。用例的特色就每次只針對法式的一個功效編寫,最初依據用例編寫代碼,終究完成法式的開辟。我們這裡的需求只要簡略的幾個:登錄,發送新聞,吸收新聞,刊出或封閉,下面的剖析是對這幾點功效的一個明白。接上去我們起首編寫第一個用例:登錄。
在開端之前,我們先明白一個概念:客戶端,辦事端。由於這個法式只是在兩小我(機械)之間聊天,那末我們年夜致可以繪出如許一個圖來:
我們希冀用戶A和用戶B停止對話,那末我們就須要在它們之間樹立起銜接。雖然“用戶A”和“用戶B”的位置是對等的,但依照商定俗稱的說法:我們將提議銜接要求的一方稱為客戶端(或叫當地),另外一端稱為辦事端(或叫長途)。所以我們的登錄進程,就是“用戶A”銜接到“用戶B”的進程,或許說客戶端(當地)銜接到辦事端(長途)的進程。在剖析這個法式的進程中,我們老是將其分為兩部門,一部門為提議銜接、發送新聞的一方(當地),一方為接收銜接、吸收新聞的一方(長途)。
登錄和銜接(當地) 主途徑 可選途徑 1.翻開運用法式,顯示登錄窗口 2.輸出用戶名 3.點擊“登錄”按鈕,登錄勝利 3.“登錄”掉敗
假如用戶名為空,從新進入第2步。
4.顯示主窗口,顯示登錄的用戶稱號 5.點擊“銜接”,銜接至長途 6.銜接勝利5.2銜接為灰色,表現曾經銜接
5.3刊出為亮色,表現可以刊出
5.4發送為亮色,表現可以發新聞
這裡我們的用例稱號為登錄和銜接,然則前面我們又打了一個括號,寫著“當地”,它的意思是說,登錄和銜接是客戶端,也就是提議銜接的一方采用的舉措。異樣,我們須要寫下當客戶端銜接至辦事端時,辦事端采用的舉措。
登錄和銜接(長途) 主途徑 可選途徑 1-4 同客戶端 5.期待銜接 6.假如有銜接,主動在用戶界面顯示“長途主機銜接勝利”
接上去我們來看發送新聞。在發送新聞時,曾經是登錄了的,也就是“用戶A”、“用戶B”曾經做好了銜接,所以我們如今便可以只存眷發送這一進程:
發送新聞(當地) 主途徑 可選途徑 1.輸出新聞 2.點擊發送按鈕 2.沒有輸出新聞,從新回到第1步 3.在用戶界面上顯示收回的新聞 3.辦事端曾經斷開銜接或許封閉
3.1在客戶端用戶界面上顯示毛病新聞
然後我們看一下吸收新聞,此時我們只關懷吸收新聞這一部門。
吸收新聞(長途) 主途徑 可選途徑 1.偵聽到客戶端發來的新聞,主動顯示在用戶界面上。
留意到如許一點:當長途主機向當地前往新聞時,它的用例又變成了下面的用例“發送新聞(當地)”。由於它們的腳色曾經交換了。
最初看一下刊出,我們這裡研討的是當我們在當地機械點擊“刊出”後,兩邊采用的舉措:
刊出(當地自動)
主途徑
可選途徑
1.點擊刊出按鈕,斷開與長途的銜接
2.在用戶界面顯示曾經刊出
3.更改控件狀況
3.1刊出為灰色,表現曾經刊出
3.2銜接為亮色,表現可以銜接
3.3發送為灰色,表現沒法發送
與此對應,辦事端應當作出反響:
刊出(長途主動) 主途徑 可選途徑 1.主動顯示長途用戶曾經斷開銜接。
留意到一點:當長途自動刊出時,它采用的舉措為下面的“當地自動”,當地采用的舉措則為這裡的“長途主動”。
至此,運用法式的功效剖析和用例編寫就告一段落了,經由過程下面這些表格,以後再持續編寫法式變得輕易了很多。別的還須要記得,用例只能為你供給一個操作步調的指點,在完成的進程中,由於技巧等方面的緣由,能夠還會有大批的修正。假如修正量很年夜,可以從新修正用例;假如修正量不年夜,那末便可以直接編碼。這是一個迭代的進程,也沒有必定的尺度,總之是以高效和適合為尺度。
2.剖析與設計
我們曾經很清晰地曉得了法式須要做些甚麼,雖然如今還不曉得該若何去做。我們乃至可以編寫出這個法式所須要的接口,今後編寫代碼的時刻,我們只需去完成這些接口便可以了。這也相符面向接口編程的准繩。別的我們留意到,雖然這是一個聊天法式,然則卻可以明白地劃分為兩部門,一部門發送新聞,一部門吸收新聞。別的留意下面標識為主動的語句,它們暗示這個操作須要經由過程事宜的告訴機制來完成。關於拜托和事宜,可以參考這兩篇文章:
2.1新聞Message
起首我們可以界說新聞,後面我們曾經明白了新聞包括三個部門:用戶名、時光、內容,所以我們可以界說一個構造來表現這個新聞:
public struct Message { private readonly string userName; private readonly string content; private readonly DateTime postDate; public Message(string userName, string content) { this.userName = userName; this.content = content; this.postDate = DateTime.Now; } public Message(string content) : this("System", content) { } public string UserName { get { return userName; } } public string Content { get { return content; } } public DateTime PostDate { get { return postDate; } } public override string ToString() { return String.Format("{0}[{1}]:\r\n{2}\r\n", userName, postDate, content); } }
2.2新聞發送方IMessageSender
從下面我們可以看出,新聞發送方重要包括如許幾個功效:登錄、銜接、發送新聞、刊出。別的在銜接勝利或掉敗時還要告訴用戶界面,發送新聞勝利或掉敗時也須要告訴用戶界面,是以,我們可讓銜接和發送新聞前往一個布爾類型的值,當它為真時表現銜接或發送勝利,反之則為掉敗。由於登錄沒有任何的營業邏輯,僅僅是記載控件的值並停止顯示,所以我不盤算將它寫到接口中。是以我們可以得出它的接口年夜致以下:
public interface IMessageSender { bool Connect(IPAddress ip, int port); // 銜接到辦事端 bool SendMessage(Message msg); // 發送用戶 void SignOut(); // 刊出體系 }
2.3新聞吸收方IMessageReceiver
而關於新聞吸收方,從下面我們可以看出,它的操作滿是主動的:客戶端銜接時主動提醒,客戶端銜接喪失時顯示主動提醒,偵聽到新聞時主動提醒。留意到下面三個詞都用了“主動”來潤飾,在C#中,可以界說拜托和事宜,用於當法式中某種情形產生時,告訴別的一個對象。在這裡,法式等於我們的IMessageReceiver,某種情形就是下面的三種情形,而別的一個對象則為我們的用戶界面。是以,我們如今起首須要界說三個拜托:
public delegate void MessageReceivedEventHandler(string msg); public delegate void ClientConnectedEventHandler(IPEndPoint endPoint); public delegate void ConnectionLostEventHandler(string info);
接上去,我們留意到吸收方須要偵聽新聞,是以我們須要在接口中界說的辦法是StartListen()和StopListen()辦法,這兩個辦法是典范的技巧相干,而不是營業相干,所以從用例中是看不出來的,能夠年夜家如今對這兩個辦法是做甚麼的還不清晰,沒有關系,我們如今其實不寫完成,而界說接口其實不須要甚麼本錢,我們寫下IMessageReceiver的接口界說:
public interface IMessageReceiver { event MessageReceivedEventHandler MessageReceived; // 吸收到發來的新聞 event ConnectionLostEventHandler ClientLost; // 長途自動斷開銜接 event ClientConnectedEventHandler ClientConnected; // 長途銜接到了當地 void StartListen(); // 開端偵聽端口 void StopListen(); // 停滯偵聽端口 }
我記得已經看過有篇文章說過,最好不要在接口中界說事宜,然則我忘了他的來由了,所以本文照樣將事宜界說在了接口中。
2.4主法式Talker
而我們的主法式是既可以發送,又可以吸收,普通來講,假如一個類像取得其他類的才能,以采取兩種辦法:繼續和復合。由於C#中沒有多重繼續,所以我們沒法同時繼續完成了IMessageReceiver和IMessageSender的類。那末我們可以采取復合,將它們作為類成員包括在Talker外部:
public class Talker { private IMessageReceiver receiver; private IMessageSender sender; public Talker(IMessageReceiver receiver, IMessageSender sender) { this.receiver = receiver; this.sender = sender; } }
如今,我們的法式年夜體框架曾經完成,接上去要存眷的就是若何完成它,如今讓我們由設計走入完成,看看完成一個收集聊天法式,我們須要控制的技巧吧。
C#收集編程基本(篇外篇)
這部門的內容請參考 C#收集編程 系列文章,共5個部門較為具體的講述了基於Socket的收集編程的初步內容。
編寫法式代碼
假如你曾經看完了下面一節C#收集編程,那末本章完整沒有講授的需要了,所以我只列出代碼,對個體值得留意的處所略微地講述一下。起首須要懂得的就是,我們采取的是三個形式中開辟起來難度較年夜的一種,無辦事器介入的形式。還有就是我們沒有應用播送新聞,所以須要提早曉得銜接到的長途主機的地址和端標語。
1.完成IMessageSender接口
public class MessageSender : IMessageSender { TcpClient client; Stream streamToServer; // 銜接至長途 public bool Connect(IPAddress ip, int port) { try { client = new TcpClient(); client.Connect(ip, port); streamToServer = client.GetStream(); // 獲得銜接至長途的流 return true; } catch { return false; } } // 發送新聞 public bool SendMessage(Message msg) { try { lock (streamToServer) { byte[] buffer = Encoding.Unicode.GetBytes(msg.ToString()); streamToServer.Write(buffer, 0, buffer.Length); return true; } } catch { return false; } } // 刊出 public void SignOut() { if (streamToServer != null) streamToServer.Dispose(); if (client != null) client.Close(); } }
這段代碼可以用樸素無華來描述,所以我們直接看下一段。
2.完成IMessageReceiver接口
public delegate void PortNumberReadyEventHandler(int portNumber); public class MessageReceiver : IMessageReceiver { public event MessageReceivedEventHandler MessageReceived; public event ConnectionLostEventHandler ClientLost; public event ClientConnectedEventHandler ClientConnected; // 當端標語Ok的時刻挪用 -- 須要告知用戶界面應用了哪一個端標語在偵聽 // 這裡是營業上表現不出來,在完成中能力表現出來的 public event PortNumberReadyEventHandler PortNumberReady; private Thread workerThread; private TcpListener listener; public MessageReceiver() { ((IMessageReceiver)this).StartListen(); } // 開端偵聽:顯示完成接口 void IMessageReceiver.StartListen() { ThreadStart start = new ThreadStart(ListenThreadMethod); workerThread = new Thread(start); workerThread.IsBackground = true; workerThread.Start(); } // 線程進口辦法 private void ListenThreadMethod() { IPAddress localIp = IPAddress.Parse("127.0.0.1"); listener = new TcpListener(localIp, 0); listener.Start(); // 獲得端標語 IPEndPoint endPoint = listener.LocalEndpoint as IPEndPoint; int portNumber = endPoint.Port; if (PortNumberReady != null) { PortNumberReady(portNumber); // 端標語曾經OK,告訴用戶界面 } while (true) { TcpClient remoteClient; try { remoteClient = listener.AcceptTcpClient(); } catch { break; } if (ClientConnected != null) { // 銜接至本機的長途端口 endPoint = remoteClient.Client.RemoteEndPoint as IPEndPoint; ClientConnected(endPoint); // 告訴用戶界面長途客戶銜接 } Stream streamToClient = remoteClient.GetStream(); byte[] buffer = new byte[8192]; while (true) { try { int bytesRead = streamToClient.Read(buffer, 0, 8192); if (bytesRead == 0) { throw new Exception("客戶端已斷開銜接"); } string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead); if (MessageReceived != null) { MessageReceived(msg); // 曾經收到新聞 } } catch (Exception ex) { if (ClientLost != null) { ClientLost(ex.Message); // 客戶銜接喪失 break; // 加入輪回 } } } } } // 停滯偵聽端口 public void StopListen() { try { listener.Stop(); listener = null; workerThread.Abort(); } catch { } } }
這裡須要留意的有如許幾點:我們StartListen()為顯式完成接口,由於只能經由過程接談鋒能挪用此辦法,接口的完成類看不到此辦法;這平日是關於一個接口采取兩種完成方法時應用的,但這裡我只是不願望MessageReceiver類型的客戶挪用它,由於在MessageReceiver的結構函數中它曾經挪用了StartListen。意思是說,我們願望這個類型一旦創立,就立刻開端任務。我們應用了兩個嵌套的while輪回,這個它可認為多個客戶真個屢次要求辦事,然則由於是同步操作,只需有一個客戶端銜接著,我們的後台線程就會墮入第二個輪回中沒法自拔。所以成果是:假如有一個客戶端曾經銜接上了,其它客戶端即便銜接了也沒法對它應對。最初須要留意的就是四個事宜的應用,為了向用戶供給偵聽的端標語以停止銜接,我又界說了一個PortNumberReadyEventHandler拜托。
3.完成Talker類
Talker類是最平淡的一個類,它的全體功效就是將操作拜托給現實的IMessageReceiver和IMessageSender。界說這兩個接口的利益也從這裡可以看出來:假如往後想從新完成這個法式,一切Windows窗體的代碼和Talker的代碼都不須要修正,只須要針對這兩個接口編程便可以了。
public class Talker { private IMessageReceiver receiver; private IMessageSender sender; public Talker(IMessageReceiver receiver, IMessageSender sender) { this.receiver = receiver; this.sender = sender; } public Talker() { this.receiver = new MessageReceiver(); this.sender = new MessageSender(); } public event MessageReceivedEventHandler MessageReceived { add { receiver.MessageReceived += value; } remove { receiver.MessageReceived -= value; } } public event ClientConnectedEventHandler ClientConnected { add { receiver.ClientConnected += value; } remove { receiver.ClientConnected -= value; } } public event ConnectionLostEventHandler ClientLost { add { receiver.ClientLost += value; } remove { receiver.ClientLost -= value; } } // 留意這個事宜 public event PortNumberReadyEventHandler PortNumberReady { add { ((MessageReceiver)receiver).PortNumberReady += value; } remove { ((MessageReceiver)receiver).PortNumberReady -= value; } } // 銜接長途 - 應用主機名 public bool ConnectByHost(string hostName, int port) { IPAddress[] ips = Dns.GetHostAddresses(hostName); return sender.Connect(ips[0], port); } // 銜接長途 - 應用IP public bool ConnectByIp(string ip, int port) { IPAddress ipAddress; try { ipAddress = IPAddress.Parse(ip); } catch { return false; } return sender.Connect(ipAddress, port); } // 發送新聞 public bool SendMessage(Message msg) { return sender.SendMessage(msg); } // 釋放資本,停滯偵聽 public void Dispose() { try { sender.SignOut(); receiver.StopListen(); } catch { } } // 刊出 public void SignOut() { try { sender.SignOut(); } catch { } } }
4.設計窗體,編寫窗體事宜代碼
如今我們開端設計窗體,我曾經設計好了,如今可以先輩行一下預覽:
這裡須要留意的就是下面的偵聽端口,是法式吸收新聞時的偵聽端口,也就是IMessageReceiver所應用的。其他的沒有甚麼好說的,上去我們直接看一下代碼,控件的定名是自說明的,我就不多說甚麼了。獨一要略微解釋下的是txtMessage指的是上面發送新聞的文本框,txtContent指下面的新聞記載文本框:
public partial class PrimaryForm : Form { private Talker talker; private string userName; public PrimaryForm(string name) { InitializeComponent(); userName = lbName.Text = name; this.talker = new Talker(); this.Text = userName + " Talking ..."; talker.ClientLost += new ConnectionLostEventHandler(talker_ClientLost); talker.ClientConnected += new ClientConnectedEventHandler(talker_ClientConnected); talker.MessageReceived += new MessageReceivedEventHandler(talker_MessageReceived); talker.PortNumberReady += new PortNumberReadyEventHandler(PrimaryForm_PortNumberReady); } void ConnectStatus() { } void DisconnectStatus() { } // 端標語OK void PrimaryForm_PortNumberReady(int portNumber) { PortNumberReadyEventHandler del = delegate(int port) { lbPort.Text = port.ToString(); }; lbPort.Invoke(del, portNumber); } // 吸收到新聞 void talker_MessageReceived(string msg) { MessageReceivedEventHandler del = delegate(string m) { txtContent.Text += m; }; txtContent.Invoke(del, msg); } // 有客戶端銜接到本機 void talker_ClientConnected(IPEndPoint endPoint) { ClientConnectedEventHandler del = delegate(IPEndPoint end) { IPHostEntry host = Dns.GetHostEntry(end.Address); txtContent.Text += String.Format("System[{0}]:\r\n長途主機{1}銜接至當地。\r\n", DateTime.Now, end); }; txtContent.Invoke(del, endPoint); } // 客戶端銜接斷開 void talker_ClientLost(string info) { ConnectionLostEventHandler del = delegate(string information) { txtContent.Text += String.Format("System[{0}]:\r\n{1}\r\n", DateTime.Now, information); }; txtContent.Invoke(del, info); } // 發送新聞 private void btnSend_Click(object sender, EventArgs e) { if (String.IsNullOrEmpty(txtMessage.Text)) { MessageBox.Show("請輸出內容!"); txtMessage.Clear(); txtMessage.Focus(); return; } Message msg = new Message(userName, txtMessage.Text); if (talker.SendMessage(msg)) { txtContent.Text += msg.ToString(); txtMessage.Clear(); } else { txtContent.Text += String.Format("System[{0}]:\r\n長途主機已斷開銜接\r\n", DateTime.Now); DisconnectStatus(); } } // 點擊銜接 private void btnConnect_Click(object sender, EventArgs e) { string host = txtHost.Text; string ip = txtHost.Text; int port; if (String.IsNullOrEmpty(txtHost.Text)) { MessageBox.Show("主機稱號或地址不克不及為空"); } try{ port = Convert.ToInt32(txtPort.Text); }catch{ MessageBox.Show("端標語不克不及為空,且必需為數字"); return; } if (talker.ConnectByHost(host, port)) { ConnectStatus(); txtContent.Text += String.Format("System[{0}]:\r\n已勝利銜接至長途\r\n", DateTime.Now); return; } if(talker.ConnectByIp(ip, port)){ ConnectStatus(); txtContent.Text += String.Format("System[{0}]:\r\n已勝利銜接至長途\r\n", DateTime.Now); }else{ MessageBox.Show("長途主機不存在,或許謝絕銜接!"); } txtMessage.Focus(); } // 封閉按鈕點按 private void btnClose_Click(object sender, EventArgs e) { try { talker.Dispose(); Application.Exit(); } catch { } } // 直接點擊右上角的叉 private void PrimaryForm_FormClosing(object sender, FormClosingEventArgs e) { try { talker.Dispose(); Application.Exit(); } catch { } } // 點擊刊出 private void btnSignout_Click(object sender, EventArgs e) { talker.SignOut(); DisconnectStatus(); txtContent.Text += String.Format("System[{0}]:\r\n曾經刊出\r\n",DateTime.Now); } private void btnClear_Click(object sender, EventArgs e) { txtContent.Clear(); } }
在下面代碼中,分離經由過程四個辦法定閱了四個事宜,以完成主動告訴的機制。最初須要留意的就是SignOut()和Dispose()的辨別。SignOut()只是斷開銜接,Dispose()則是分開運用法式。
總結
這篇文章簡略地剖析、設計及完成了一個聊天法式。這個法式只是對無辦事器形式完成聊天的一個測驗考試。我們剖析了需求,隨後編寫了幾個用例,並對當地、長途的概念做了界說,接著編寫了法式接口並終究完成了它。這個法式還有很嚴重的缺乏:它沒法完成主動上線告訴,而必需要事前曉得端標語並停止手動銜接。為了完成一個功效壯大且開辟輕易的法式,更好的方法是應用集中型辦事器形式。
感激浏覽,願望這篇文章能對你有所贊助。