国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項超值服

開通VIP
C#編寫簡單的聊天程序

C#編寫簡單的聊天程序

引言

這是一篇基于Socket進(jìn)行網(wǎng)絡(luò)編程的入門文章,我對于網(wǎng)絡(luò)編程的學(xué)習(xí)并不夠深入,這篇文章是對于自己知識的一個鞏固,同時希望能為初學(xué)的朋友提供一點(diǎn)參考。文章大體分為四個部分:程序的分析與設(shè)計、C#網(wǎng)絡(luò)編程基礎(chǔ)(篇外篇)、聊天程序的實(shí)現(xiàn)模式、程序?qū)崿F(xiàn)。

程序的分析與設(shè)計

1.明確程序功能

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

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

1.1請求客戶提供更詳細(xì)信息

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

有了上面這段話,我們就又可以得出下面幾個需求:

  1. 程序可以進(jìn)行登錄。
  2. 登錄后可以通知其他在線用戶。
  3. 可以與其他用戶進(jìn)行對話。
  4. 可以注銷或者關(guān)閉。

1.2對于用戶需求進(jìn)行提問,并進(jìn)行總結(jié)

經(jīng)常會有這樣的情況:可能客戶給出的需求仍然不夠細(xì)致,或者客戶自己本身對于需求就很模糊,此時我們需要做的就是針對用戶上面給出的信息進(jìn)行提問。接下來我就看看如何對上面的需求進(jìn)行提問,我們至少可以向經(jīng)理提出以下問題:

NOTE:這里我穿插一個我在見到的一個印象比較深刻的例子:客戶往往向你表達(dá)了強(qiáng)烈的意愿他多么多么想擁有一個屬于自己的網(wǎng)站,但是,他卻沒有告訴你網(wǎng)站都有哪些內(nèi)容、欄目,可以做什么。而作為開發(fā)者,我們顯然關(guān)心的是后者。

  1. 登錄時需要提供哪些內(nèi)容?需不需要提供密碼?
  2. 允許多少人同時在線聊天?
  3. 與在線用戶聊天時,可以將一條消息發(fā)給一個用戶,還是可以一次將消息發(fā)給多個用戶?
  4. 聊天時發(fā)送的消息包括哪些內(nèi)容?
  5. 注銷和關(guān)閉有什么區(qū)別?
  6. 注銷和關(guān)閉對對方需不需要給對方提示?

由于這是一個范例程序,而我在為大家講述,所以我只能再充當(dāng)一下客戶的角色,來回答上面的問題:

  1. 登錄時只需要提供用戶名稱就可以了,不需要輸入密碼。
  2. 允許兩個人在線聊天。(這里我們只講述這種簡單情況,允許多人聊天需要使用多線程)
  3. 因為只有兩個人,那么自然是只能發(fā)給一個用戶了。
  4. 聊天發(fā)送的消息包括:用戶名稱、發(fā)送時間還有正文。
  5. 注銷并不關(guān)閉程序,只是離開了對話,可以再次進(jìn)行連接。關(guān)閉則是退出整個應(yīng)用程序。
  6. 注銷和關(guān)閉均需要給對方提示。

好了,有了上面這些信息我們基本上就掌握了程序需要完成的功能,那么接下來做什么?開始編碼了么?上面的這些屬于業(yè)務(wù)流程,除非你對它已經(jīng)非常熟悉,或者程序非常的小,那么可以對它進(jìn)行編碼,但是實(shí)際中,我們最好再編寫一些用例,這樣會使程序的流程更加的清楚。

1.3編寫用例

通常一個用例對應(yīng)一個功能或者叫需求,它是程序的一個執(zhí)行路徑或者執(zhí)行流程。編寫用例的思路是:假設(shè)你已經(jīng)有了這樣一個聊天程序,那么你應(yīng)該如何使用它?我們的使用步驟,就是一個用例。用例的特點(diǎn)就每次只針對程序的一個功能編寫,最后根據(jù)用例編寫代碼,最終完成程序的開發(fā)。我們這里的需求只有簡單的幾個:登錄,發(fā)送消息,接收消息,注銷或關(guān)閉,上面的分析是對這幾點(diǎn)功能的一個明確。接下來我們首先編寫第一個用例:登錄。

在開始之前,我們先明確一個概念:客戶端,服務(wù)端。因為這個程序只是在兩個人(機(jī)器)之間聊天,那么我們大致可以繪出這樣一個圖來:

我們期望用戶A和用戶B進(jìn)行對話,那么我們就需要在它們之間建立起連接。盡管“用戶A”和“用戶B”的地位是對等的,但按照約定俗稱的說法:我們將發(fā)起連接請求的一方稱為客戶端(或叫本地),另一端稱為服務(wù)端(或叫遠(yuǎn)程)。所以我們的登錄過程,就是“用戶A”連接到“用戶B”的過程,或者說客戶端(本地)連接到服務(wù)端(遠(yuǎn)程)的過程。在分析這個程序的過程中,我們總是將其分為兩部分,一部分為發(fā)起連接、發(fā)送消息的一方(本地),一方為接受連接、接收消息的一方(遠(yuǎn)程)。

登錄和連接(本地)
主路徑可選路徑
1.打開應(yīng)用程序,顯示登錄窗口 
2.輸入用戶名 
3.點(diǎn)擊“登錄”按鈕,登錄成功3.“登錄”失敗

如果用戶名為空,重新進(jìn)入第2步。

4.顯示主窗口,顯示登錄的用戶名稱 
5.點(diǎn)擊“連接”,連接至遠(yuǎn)程 
6.連接成功
6.1提示用戶,連接已經(jīng)成功。
6.連接失敗
6.1 提示用戶,連接不成功
5.在用戶界面變更控件狀態(tài)

5.2連接為灰色,表示已經(jīng)連接

5.3注銷為亮色,表示可以注銷

5.4發(fā)送為亮色,表示可以發(fā)消息

 

這里我們的用例名稱為登錄和連接,但是后面我們又打了一個括號,寫著“本地”,它的意思是說,登錄和連接是客戶端,也就是發(fā)起連接的一方采取的動作。同樣,我們需要寫下當(dāng)客戶端連接至服務(wù)端時,服務(wù)端采取的動作。

登錄和連接(遠(yuǎn)程)
主路徑可選路徑
1-4 同客戶端 
5.等待連接 
6.如果有連接,自動在用戶界面顯示“遠(yuǎn)程主機(jī)連接成功” 

接下來我們來看發(fā)送消息。在發(fā)送消息時,已經(jīng)是登錄了的,也就是“用戶A”、“用戶B”已經(jīng)做好了連接,所以我們現(xiàn)在就可以只關(guān)注發(fā)送這一過程:

發(fā)送消息(本地)
主路徑可選路徑
1.輸入消息 
2.點(diǎn)擊發(fā)送按鈕2.沒有輸入消息,重新回到第1步
3.在用戶界面上顯示發(fā)出的消息3.服務(wù)端已經(jīng)斷開連接或者關(guān)閉

3.1在客戶端用戶界面上顯示錯誤消息

然后我們看一下接收消息,此時我們只關(guān)心接收消息這一部分。

接收消息(遠(yuǎn)程)
主路徑可選路徑
1.偵聽到客戶端發(fā)來的消息,自動顯示在用戶界面上。 

注意到這樣一點(diǎn):當(dāng)遠(yuǎn)程主機(jī)向本地返回消息時,它的用例又變?yōu)榱松厦娴挠美鞍l(fā)送消息(本地)”。因為它們的角色已經(jīng)互換了。

最后看一下注銷,我們這里研究的是當(dāng)我們在本地機(jī)器點(diǎn)擊“注銷”后,雙方采取的動作:

注銷(本地主動)
主路徑可選路徑
1.點(diǎn)擊注銷按鈕,斷開與遠(yuǎn)程的連接 
2.在用戶界面顯示已經(jīng)注銷 
3.更改控件狀態(tài)

3.1注銷為灰色,表示已經(jīng)注銷

3.2連接為亮色,表示可以連接

3.3發(fā)送為灰色,表示無法發(fā)送

 

與此對應(yīng),服務(wù)端應(yīng)該作出反應(yīng):

注銷(遠(yuǎn)程被動)
主路徑可選路徑
1.自動顯示遠(yuǎn)程用戶已經(jīng)斷開連接。 

注意到一點(diǎn):當(dāng)遠(yuǎn)程主動注銷時,它采取的動作為上面的“本地主動”,本地采取的動作則為這里的“遠(yuǎn)程被動”。

至此,應(yīng)用程序的功能分析和用例編寫就告一段落了,通過上面這些表格,之后再繼續(xù)編寫程序變得容易了許多。另外還需要記得,用例只能為你提供一個操作步驟的指導(dǎo),在實(shí)現(xiàn)的過程中,因為技術(shù)等方面的原因,可能還會有少量的修改。如果修改量很大,可以重新修改用例;如果修改量不大,那么就可以直接編碼。這是一個迭代的過程,也沒有一定的標(biāo)準(zhǔn),總之是以高效和合適為標(biāo)準(zhǔn)。

2.分析與設(shè)計

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

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

2.1消息Message

首先我們可以定義消息,前面我們已經(jīng)明確了消息包含三個部分:用戶名、時間、內(nèi)容,所以我們可以定義一個結(jié)構(gòu)來表示這個消息:

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}]: {2} ", userName, postDate, content);
    }
}

2.2消息發(fā)送方IMessageSender

從上面我們可以看出,消息發(fā)送方主要包含這樣幾個功能:登錄、連接發(fā)送消息、注銷。另外在連接成功或失敗時還要通知用戶界面,發(fā)送消息成功或失敗時也需要通知用戶界面,因此,我們可以讓連接和發(fā)送消息返回一個布爾類型的值,當(dāng)它為真時表示連接或發(fā)送成功,反之則為失敗。因為登錄沒有任何的業(yè)務(wù)邏輯,僅僅是記錄控件的值并進(jìn)行顯示,所以我不打算將它寫到接口中。因此我們可以得出它的接口大致如下:

public interface IMessageSender {
    bool Connect(IPAddress ip, int port);       // 連接到服務(wù)端
    bool SendMessage(Message msg);              // 發(fā)送用戶
    void SignOut();                                 // 注銷系統(tǒng)
}

2.3消息接收方IMessageReceiver

而對于消息接收方,從上面我們可以看出,它的操作全是被動的:客戶端連接時自動提示,客戶端連接丟失時顯示自動提示,偵聽到消息時自動提示。注意到上面三個詞都用了“自動”來修飾,在C#中,可以定義委托和事件,用于當(dāng)程序中某種情況發(fā)生時,通知另外一個對象。在這里,程序即是我們的IMessageReceiver,某種情況就是上面的三種情況,而另外一個對象則為我們的用戶界面。因此,我們現(xiàn)在首先需要定義三個委托:

public delegate void MessageReceivedEventHandler(string msg);  
public delegate void ClientConnectedEventHandler(IPEndPoint endPoint);
public delegate void ConnectionLostEventHandler(string info);

接下來,我們注意到接收方需要偵聽消息,因此我們需要在接口中定義的方法是StartListen()和StopListen()方法,這兩個方法是典型的技術(shù)相關(guān),而不是業(yè)務(wù)相關(guān),所以從用例中是看不出來的,可能大家現(xiàn)在對這兩個方法是做什么的還不清楚,沒有關(guān)系,我們現(xiàn)在并不寫實(shí)現(xiàn),而定義接口并不需要什么成本,我們寫下IMessageReceiver的接口定義:

public interface IMessageReceiver {
    event MessageReceivedEventHandler MessageReceived; // 接收到發(fā)來的消息
    event ConnectionLostEventHandler ClientLost;            // 遠(yuǎn)程主動斷開連接
    event ClientConnectedEventHandler ClientConnected;  // 遠(yuǎn)程連接到了本地
    void StartListen();         // 開始偵聽端口
    void StopListen();          // 停止偵聽端口
}

我記得曾經(jīng)看過有篇文章說過,最好不要在接口中定義事件,但是我忘了他的理由了,所以本文還是將事件定義在了接口中。

2.4主程序Talker

而我們的主程序是既可以發(fā)送,又可以接收,一般來說,如果一個類像獲得其他類的能力,以采用兩種方法:繼承和復(fù)合。因為C#中沒有多重繼承,所以我們無法同時繼承實(shí)現(xiàn)了IMessageReceiver和IMessageSender的類。那么我們可以采用復(fù)合,將它們作為類成員包含在Talker內(nèi)部:

public class Talker {
    private IMessageReceiver receiver;
    private IMessageSender sender;

    public Talker(IMessageReceiver receiver, IMessageSender sender) {
        this.receiver = receiver;
        this.sender = sender;
    }
}

現(xiàn)在,我們的程序大體框架已經(jīng)完成,接下來要關(guān)注的就是如何實(shí)現(xiàn)它,現(xiàn)在讓我們由設(shè)計走入實(shí)現(xiàn),看看實(shí)現(xiàn)一個網(wǎng)絡(luò)聊天程序,我們需要掌握的技術(shù)吧。

C#網(wǎng)絡(luò)編程基礎(chǔ)(篇外篇)

這部分的內(nèi)容請參考 C#網(wǎng)絡(luò)編程 系列文章,共5個部分較為詳細(xì)的講述了基于Socket的網(wǎng)絡(luò)編程的初步內(nèi)容。

編寫程序代碼

如果你已經(jīng)看完了上面一節(jié)C#網(wǎng)絡(luò)編程,那么本章完全沒有講解的必要了,所以我只列出代碼,對個別值得注意的地方稍微地講述一下。首先需要了解的就是,我們采用的是三個模式中開發(fā)起來難度較大的一種,無服務(wù)器參與的模式。還有就是我們沒有使用廣播消息,所以需要提前知道連接到的遠(yuǎn)程主機(jī)的地址和端口號。

1.實(shí)現(xiàn)IMessageSender接口

public class MessageSender : IMessageSender {

    TcpClient client;
    Stream streamToServer;

    // 連接至遠(yuǎn)程
    public bool Connect(IPAddress ip, int port) {
        try {
            client = new TcpClient();
            client.Connect(ip, port);
            streamToServer = client.GetStream();    // 獲取連接至遠(yuǎn)程的流
            return true;
        } catch {
            return false;
        }
    }

    // 發(fā)送消息
    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();
    }
}

這段代碼可以用樸實(shí)無華來形容,所以我們直接看下一段。

2.實(shí)現(xiàn)IMessageReceiver接口

public delegate void PortNumberReadyEventHandler(int portNumber);

public class MessageReceiver : IMessageReceiver {

    public event MessageReceivedEventHandler MessageReceived;
    public event ConnectionLostEventHandler ClientLost;
    public event ClientConnectedEventHandler ClientConnected;

    // 當(dāng)端口號Ok的時候調(diào)用 -- 需要告訴用戶界面使用了哪個端口號在偵聽
    // 這里是業(yè)務(wù)上體現(xiàn)不出來,在實(shí)現(xiàn)中才能體現(xiàn)出來的
    public event PortNumberReadyEventHandler PortNumberReady;

    private Thread workerThread;
    private TcpListener listener;

    public MessageReceiver() {
        ((IMessageReceiver)this).StartListen();
    }

    // 開始偵聽:顯示實(shí)現(xiàn)接口
    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);        // 端口號已經(jīng)OK,通知用戶界面
        }

        while (true) {
            TcpClient remoteClient;
            try {
                remoteClient = listener.AcceptTcpClient();
            } catch {
                break;
            }
            if (ClientConnected != null) {
                // 連接至本機(jī)的遠(yuǎn)程端口
                endPoint = remoteClient.Client.RemoteEndPoint as IPEndPoint;
                ClientConnected(endPoint);      // 通知用戶界面遠(yuǎn)程客戶連接
            }

            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);       // 已經(jīng)收到消息
                    }
                } catch (Exception ex) {
                    if (ClientLost != null) {
                        ClientLost(ex.Message);     // 客戶連接丟失
                        break;                      // 退出循環(huán)
                    }
                }
            }
        }
    }

    // 停止偵聽端口
    public void StopListen() {
        try {
            listener.Stop();
            listener = null;
            workerThread.Abort();
        } catch { }
    }
}

這里需要注意的有這樣幾點(diǎn):我們StartListen()為顯式實(shí)現(xiàn)接口,因為只能通過接口才能調(diào)用此方法,接口的實(shí)現(xiàn)類看不到此方法;這通常是對于一個接口采用兩種實(shí)現(xiàn)方式時使用的,但這里我只是不希望MessageReceiver類型的客戶調(diào)用它,因為在MessageReceiver的構(gòu)造函數(shù)中它已經(jīng)調(diào)用了StartListen。意思是說,我們希望這個類型一旦創(chuàng)建,就立即開始工作。我們使用了兩個嵌套的while循環(huán),這個它可以為多個客戶端的多次請求服務(wù),但是因為是同步操作,只要有一個客戶端連接著,我們的后臺線程就會陷入第二個循環(huán)中無法自拔。所以結(jié)果是:如果有一個客戶端已經(jīng)連接上了,其它客戶端即使連接了也無法對它應(yīng)答。最后需要注意的就是四個事件的使用,為了向用戶提供偵聽的端口號以進(jìn)行連接,我又定義了一個PortNumberReadyEventHandler委托。

3.實(shí)現(xiàn)Talker類

Talker類是最平庸的一個類,它的全部功能就是將操作委托給實(shí)際的IMessageReceiver和IMessageSender。定義這兩個接口的好處也從這里可以看出來:如果日后想重新實(shí)現(xiàn)這個程序,所有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;
        }
    }
           

    // 連接遠(yuǎn)程 - 使用主機(jī)名
    public bool ConnectByHost(string hostName, int port) {
        IPAddress[] ips = Dns.GetHostAddresses(hostName);
        return sender.Connect(ips[0], port);
    }

    // 連接遠(yuǎn)程 - 使用IP
    public bool ConnectByIp(string ip, int port) {
        IPAddress ipAddress;
        try {
            ipAddress = IPAddress.Parse(ip);
        } catch {
            return false;
        }
        return sender.Connect(ipAddress, port);
    }


    // 發(fā)送消息
    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.設(shè)計窗體,編寫窗體事件代碼

現(xiàn)在我們開始設(shè)計窗體,我已經(jīng)設(shè)計好了,現(xiàn)在可以先進(jìn)行一下預(yù)覽:

這里需要注意的就是上面的偵聽端口,是程序接收消息時的偵聽端口,也就是IMessageReceiver所使用的。其他的沒有什么好說的,下來我們直接看一下代碼,控件的命名是自解釋的,我就不多說什么了。唯一要稍微說明下的是txtMessage指的是下面發(fā)送消息的文本框,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);
    }

    // 有客戶端連接到本機(jī)
    void talker_ClientConnected(IPEndPoint endPoint) {
        ClientConnectedEventHandler del = delegate(IPEndPoint end) {
            IPHostEntry host = Dns.GetHostEntry(end.Address);
            txtContent.Text +=
                String.Format("System[{0}]:遠(yuǎn)程主機(jī){1}連接至本地。 ", DateTime.Now, end);
        };
        txtContent.Invoke(del, endPoint);
    }

    // 客戶端連接斷開
    void talker_ClientLost(string info) {
        ConnectionLostEventHandler del = delegate(string information) {
            txtContent.Text +=
                String.Format("System[{0}]: {1} ", DateTime.Now, information);
        };
        txtContent.Invoke(del, info);
    }

    // 發(fā)送消息
    private void btnSend_Click(object sender, EventArgs e) {
        if (String.IsNullOrEmpty(txtMessage.Text)) {
            MessageBox.Show("請輸入內(nèi)容!");
            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}]:遠(yuǎn)程主機(jī)已斷開連接 ", DateTime.Now);
            DisconnectStatus();
        }
    }


    // 點(diǎn)擊連接
    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("主機(jī)名稱或地址不能為空");
        }          

        try{
            port = Convert.ToInt32(txtPort.Text);
        }catch{
            MessageBox.Show("端口號不能為空,且必須為數(shù)字");
            return;
        }
       
        if (talker.ConnectByHost(host, port)) {
            ConnectStatus();
            txtContent.Text +=
                String.Format("System[{0}]:已成功連接至遠(yuǎn)程 ", DateTime.Now);
            return;
        }
           
        if(talker.ConnectByIp(ip, port)){
            ConnectStatus();
            txtContent.Text +=
                String.Format("System[{0}]:已成功連接至遠(yuǎn)程 ", DateTime.Now);
        }else{
            MessageBox.Show("遠(yuǎn)程主機(jī)不存在,或者拒絕連接!");
        }          

        txtMessage.Focus();
    }

    // 關(guān)閉按鈕點(diǎn)按
    private void btnClose_Click(object sender, EventArgs e) {
        try {
            talker.Dispose();
            Application.Exit();
        } catch {
        }
    }

    // 直接點(diǎn)擊右上角的叉
    private void PrimaryForm_FormClosing(object sender, FormClosingEventArgs e) {
        try {
            talker.Dispose();
            Application.Exit();
        } catch {
        }
    }

    // 點(diǎn)擊注銷
    private void btnSignout_Click(object sender, EventArgs e) {
        talker.SignOut();
        DisconnectStatus();
        txtContent.Text +=
            String.Format("System[{0}]:已經(jīng)注銷 ",DateTime.Now);
    }

    private void btnClear_Click(object sender, EventArgs e) {
        txtContent.Clear();
    }
}

在上面代碼中,分別通過四個方法訂閱了四個事件,以實(shí)現(xiàn)自動通知的機(jī)制。最后需要注意的就是SignOut()和Dispose()的區(qū)分。SignOut()只是斷開連接,Dispose()則是離開應(yīng)用程序。

總結(jié)

這篇文章簡單地分析、設(shè)計及實(shí)現(xiàn)了一個聊天程序。這個程序只是對無服務(wù)器模式實(shí)現(xiàn)聊天的一個嘗試。我們分析了需求,隨后編寫了幾個用例,并對本地、遠(yuǎn)程的概念做了定義,接著編寫了程序接口并最終實(shí)現(xiàn)了它。這個程序還有很嚴(yán)重的不足:它無法實(shí)現(xiàn)自動上線通知,而必須要事先知道端口號并進(jìn)行手動連接。為了實(shí)現(xiàn)一個功能強(qiáng)大且開發(fā)容易的程序,更好的辦法是使用集中型服務(wù)器模式。

感謝閱讀,希望這篇文章能對你有所幫助。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
深入理解Apache Mina (3)---- 與IoHandler相關(guān)的幾個類
后臺自動發(fā)送郵件的程序(Java Mail,WEB版)
C#使用Event在窗體之間傳遞消息和參數(shù)
模擬C#按鈕事件觸發(fā)過程
C# webBrowser禁止在新窗口打開,強(qiáng)制在本窗口打開
C#多線程解決界面卡死問題的完美解決方案 – 放肆雷特 | 胖子的技術(shù)博客
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服