亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > C# > 正文

分享一個C#編寫簡單的聊天程序(詳細介紹)

2020-01-24 01:20:08
字體:
來源:轉載
供稿:網友

引言

這是一篇基于Socket進行網絡編程的入門文章,我對于網絡編程的學習并不夠深入,這篇文章是對于自己知識的一個鞏固,同時希望能為初學的朋友提供一點參考。文章大體分為四個部分:程序的分析與設計、C#網絡編程基礎(篇外篇)、聊天程序的實現模式、程序實現。

程序的分析與設計

1.明確程序功能

如果大家現在已經參加了工作,你的經理或者老板告訴你,“小王,我需要你開發一個聊天程序”。那么接下來該怎么做呢?你是不是在腦子里有個雛形,然后就直接打開VS2005開始設計窗體,編寫代碼了呢?在開始之前,我們首先需要進行軟件的分析與設計。就拿本例來說,如果只有這么一句話“一個聊天程序”,恐怕現在大家對這個“聊天程序”的概念就很模糊,它可以是像QQ那樣的非常復雜的一個程序,也可以是很簡單的聊天程序;它可能只有在對方在線的時候才可以進行聊天,也可能進行留言;它可能每次將消息只能發往一個人,也可能允許發往多個人。它還可能有一些高級功能,比如向對方傳送文件等。所以我們首先需要進行分析,而不是一上手就開始做,而分析的第一步,就是搞清楚程序的功能是什么,它能夠做些什么。在這一步,我們的任務是了解程序需要做什么,而不是如何去做。

了解程序需要做什么,我們可以從兩方面入手,接下來我們分別討論。

1.1請求客戶提供更詳細信息

我們可以做的第一件事就是請求客戶提供更加詳細的信息。盡管你的經理或老板是你的上司,但在這個例子中,他就是你的客戶(當然通常情況下,客戶是公司外部委托公司開發軟件的人或單位)。當遇到上面這種情況,我們只有少得可憐的一條信息“一個聊天程序”,首先可以做的,就是請求客戶提供更加確切的信息。比如,你問經理“對這個程序的功能能不能提供一些更具體的信息?”。他可能會像這樣回答:“哦,很簡單,可以登錄聊天程序,登錄的時候能夠通知其他在線用戶,然后與在線的用戶進行對話,如果不想對話了,就注銷或者直接關閉,就這些吧?!?/p>

有了上面這段話,我們就又可以得出下面幾個需求:
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.連接成功
6.1提示用戶,連接已經成功。
6.連接失敗
6.1 提示用戶,連接不成功
5.在用戶界面變更控件狀態

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.分析與設計

我們已經很清楚地知道了程序需要做些什么,盡管現在還不知道該如何去做。我們甚至可以編寫出這個程序所需要的接口,以后編寫代碼的時候,我們只要去實現這些接口就可以了。這也符合面向接口編程的原則。另外我們注意到,盡管這是一個聊天程序,但是卻可以明確地劃分為兩部分,一部分發送消息,一部分接收消息。另外注意上面標識為自動的語句,它們暗示這個操作需要通過事件的通知機制來完成。關于委托和事件,可以參考這兩篇文章:

  • C#中的委托和事件 - 委托和事件的入門文章,同時捎帶講述了Observer設計模式和.NET的事件模型
  • C#中的委托和事件(續) - 委托和事件更深入的一些問題,包括異常、超時的處理,以及使用委托來異步調用方法。

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()則是離開應用程序。

總結

這篇文章簡單地分析、設計及實現了一個聊天程序。這個程序只是對無服務器模式實現聊天的一個嘗試。我們分析了需求,隨后編寫了幾個用例,并對本地、遠程的概念做了定義,接著編寫了程序接口并最終實現了它。這個程序還有很嚴重的不足:它無法實現自動上線通知,而必須要事先知道端口號并進行手動連接。為了實現一個功能強大且開發容易的程序,更好的辦法是使用集中型服務器模式。
感謝閱讀,希望這篇文章能對你有所幫助。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
欧美日本在线视频中文字字幕| 亚洲变态欧美另类捆绑| 国产精品视频区| 国产精品美女久久久免费| 欧美日韩在线视频首页| 成人激情免费在线| 国产精品夜色7777狼人| 久久久最新网址| 91国在线精品国内播放| 久久人人爽人人爽人人片av高清| 亚洲色无码播放| 国产做受高潮69| 国产精品伦子伦免费视频| 亚洲一区二区免费在线| 欧美国产精品日韩| 成人在线精品视频| 久久久免费观看| 日韩av电影在线免费播放| 一级做a爰片久久毛片美女图片| 国产大片精品免费永久看nba| 草民午夜欧美限制a级福利片| 日韩中文在线中文网三级| 欧美性猛交xxxx乱大交蜜桃| 国产国产精品人在线视| 精品国产乱码久久久久久虫虫漫画| 这里精品视频免费| 亚洲欧美成人网| 久久久亚洲国产天美传媒修理工| 精品欧美aⅴ在线网站| 久久大大胆人体| 日韩欧美一区二区三区| 91午夜在线播放| 色综合影院在线| 亚洲一区第一页| 欧美大胆在线视频| 亚洲国产精品va在线观看黑人| 亚洲精品suv精品一区二区| 欧美成人性生活| 97人人爽人人喊人人模波多| 性色av一区二区三区| 日本精品久久久久影院| 国产精品v片在线观看不卡| 国产在线拍偷自揄拍精品| 在线观看欧美成人| 国产精品高潮呻吟久久av黑人| 亚洲丝袜一区在线| 亚洲成人精品久久| 国产欧美亚洲视频| 成人www视频在线观看| 欧美性猛交xxxx免费看漫画| 一本久久综合亚洲鲁鲁| 欧美巨大黑人极品精男| 亚洲999一在线观看www| 亚洲新中文字幕| 欧美一级高清免费| www.99久久热国产日韩欧美.com| 综合网日日天干夜夜久久| 精品国产乱码久久久久久天美| 中文日韩电影网站| 久久久久久久久久久久av| 国产成人激情小视频| 日韩在线资源网| 午夜精品三级视频福利| 欧美亚洲在线视频| 欧美野外猛男的大粗鳮| 亚洲精品av在线| 国产视频精品自拍| 精品久久久久久久久久久| 爽爽爽爽爽爽爽成人免费观看| 国产精品欧美一区二区三区奶水| 欧美大尺度电影在线观看| 欧美激情一区二区久久久| 亚洲成av人片在线观看香蕉| 国产亚洲精品激情久久| 久久精品美女视频网站| 久久国产精品首页| 国产精品激情av电影在线观看| 国产精品色悠悠| 久久精品视频免费播放| 欧美午夜女人视频在线| 美日韩精品免费观看视频| 欧美激情精品久久久久久蜜臀| 一本一本久久a久久精品综合小说| 日韩av在线免费观看| 隔壁老王国产在线精品| 久久青草精品视频免费观看| 精品国偷自产在线视频| 亚洲一区av在线播放| 久久精品这里热有精品| 国产精品久久久久999| 精品亚洲男同gayvideo网站| 在线日韩第一页| 免费99精品国产自在在线| 91精品国产成人www| 中文字幕综合一区| 欧美精品一区二区免费| 成人黄色免费看| 在线观看日韩www视频免费| 国产久一一精品| 亚洲性av在线| 欧美日韩国产中文字幕| 亚洲视频999| 久久精品成人欧美大片古装| 欧美性xxxx在线播放| 亚洲色图校园春色| 欧美精品在线观看| 亚洲**2019国产| 日韩av免费一区| 97精品一区二区视频在线观看| 久久99精品久久久久久青青91| 国产午夜精品一区二区三区| 国产999精品| 久久99精品久久久久久琪琪| 日韩欧美亚洲成人| 午夜精品一区二区三区av| 大伊人狠狠躁夜夜躁av一区| 欧美成人午夜激情视频| 国产免费一区二区三区在线能观看| 亚洲a在线播放| 日韩欧美国产网站| 亚洲第一福利网站| 韩国三级电影久久久久久| 日韩免费观看网站| 精品日韩视频在线观看| 欧美日韩美女在线观看| 亚洲精品影视在线观看| 日韩美女av在线| 亚洲欧美国产日韩天堂区| 久精品免费视频| 久久久精品久久久| 欧美男插女视频| 国产精品91久久久久久| 尤物yw午夜国产精品视频明星| 国产va免费精品高清在线观看| 69视频在线免费观看| 国语自产精品视频在线看抢先版图片| 亚洲精品久久久久久久久久久久久| 伦理中文字幕亚洲| 亚洲女人被黑人巨大进入| 亚洲精品一区中文字幕乱码| 97香蕉久久超级碰碰高清版| 国产97在线亚洲| 国产欧美一区二区三区久久人妖| 国产精品视频地址| 亚洲三级 欧美三级| 久久综合国产精品台湾中文娱乐网| 欧美日韩一区二区免费在线观看| 91久久夜色精品国产网站| 久久九九亚洲综合| 亚洲自拍偷拍福利| 国产视频亚洲精品| 亚洲91精品在线| 亚洲视频在线播放| 亚洲第一综合天堂另类专| 成人免费淫片视频软件| 成人国产精品久久久久久亚洲| 欧美限制级电影在线观看| 97在线看福利| 久久久日本电影| 精品福利樱桃av导航| 国产啪精品视频网站| 91免费高清视频| 久久久精品网站| 国产精品爱啪在线线免费观看|