自己寫即時通訊軟體

最近因想要嘗試 C# 這個程式語言,所以找了一個主題來自我挑戰一下,寫一個給公司內部使用的即時通訊軟體,目標很簡單,只要能雙方對話並且能使用公司帳號登入,登入後會定期的通知最新的待簽的電子簽核文件就好。

在網路上可以找到這個簡單的範本,雖然簡單但該有的都有,可以大幅度的節省很多開發的功夫,內容請自行參考以下網址。http://www.codeproject.com/Articles/429144/Simple-Instant-Messenger-with-SSL-Encryption-in-Cs

該程式分成兩段,一端是 Server 端,是一個純 Console 的軟體,要看了解程式可能需要點 網路通訊 及 Multi-Thread 的觀念,自定義與 Client 通訊的標準,因為我要增加功能,所以這一段我還有加上一些進去;但我不需要可以自動註冊變成使用者,所以關於註冊這段我是全部 Remark 掉。

另一段是 WinForm 的 Client 端,這一段相對來說比較簡單製作,我也沒有做大幅度的變動盡量維持原本的 Layout,原本的簡單地架構足以應付溝通的工作。

原始碼內定義的通訊協定

        public const byte IM_OK = 0;           // OK
        public const byte IM_Login = 1;        // Login
        public const byte IM_Register = 2;     // Register
        public const byte IM_TooUsername = 3;  // Too long username
        public const byte IM_TooPassword = 4;  // Too long password
        public const byte IM_Exists = 5;       // Already exists
        public const byte IM_NoExists = 6;     // Doesn’t exist
        public const byte IM_WrongPass = 7;    // Wrong password
        public const byte IM_IsAvailable = 8;  // Is user available?
        public const byte IM_Send = 9;         // Send message
        public const byte IM_Received = 10;    // Message received

以下修改是在 Server端

修改一

在測試自定義的通訊協定時,一方面是原始碼兩邊都要改,另一方面在 Client端要新增許多新的 Handler 去對應收到這類的封包要如何去處理他,例如新增一個要求現在有哪些使用者上線的這個功能,就要去攔截收到這個封包之後的處理方式。

以下是我自定義的部分

        public const byte IM_Ask_jobs = 101;           // Request waiting jobs

        public const byte IM_Return_jobs = 102;        // Return waiting jobs

        public const byte IM_Ask_OnlineList = 103;     // Ask for ONLineList

        public const byte IM_Return_OnlineList = 104;  // Return ONLine member list

修改二

在 Server 端增加連結資料庫的查詢,因為我們的電簽是放在 Oracle DB 上面,包括使用者資料都是抓 ERP 內的員工資料,所以這些資料連線要在 Server 端處理好,但 Server 同時要負責接收新的連線,還要處理Client端相互通訊,及檢查最新的尚未簽核單據,都需要 Multi-thread 來處理;但我為了節省 Connection 數量,只要在主程式做一個連線連到資料庫,其他的 Thread 都是透過主程式的 Connection 去跑 Query,這一點在小量的測試環境都還運作正常,上線後有問題再來修改。

修改三

修改原先處理使用者資料庫的程式,原程式把使用者的資訊都存在 Users.dat 這個檔案裏面,但是我是打算抓取ERP 內的資料,所以在UserInfo class增加了一些欄位,在 Server 啟動時也一口氣地把所有的 User 都讀到記憶體內,減少每次使用者登入都要去讀取資料庫的困擾,但麻煩的是一但有使用者更新資料,就必須額外寫一段程式每60分鐘去把原先存在記憶體的資訊重新更新一次,因為我在記憶體中把各使用者的連線上來的 Connection 參數都存下來,這樣我才能知道要把訊息傳給誰,但也產生無法直接把記憶體內容直接清掉重來的麻煩。

UserInfo.cs 內調整過的使用者基本資料

        public string UserName;             // User Name           

        public string Password;             // User Password

        public string ADAccount;               // OSUser (AD account)

        public string CompanyCode;             // Code of Company

        public string EmpCode;              // Code of Emmploye

        public string DepartmentName;             // name of department

        //[NonSerialized] public bool LoggedIn;      // Is logged in and connected?

        //[NonSerialized] public Client Connection;  // Connection info

        public bool LoggedIn;      // Is logged in and connected?

        public Client Connection;  // Connection info

修改四

新增一個檢查最後 5 分鐘內所需要自己簽核(包含代理別人的工作)的單據,這個就是一個全新的 Thread,但會跟主程式分享在記憶體中的使用者清單,透過資料庫查詢功能,把最新的單據清單撈近來,檢查是否有代理其他同仁的工作,然後比對已經上線人員的清單,發現對方已上線就使用 Connection 所記錄發訊息通知對方;但這邊有點 Tricky,因為所有的通訊協定都是一個封包就結束,而待辦事項可能會有多筆,所以我這邊也加了一個判斷如果沒有更多的訊息後,就發一個結束的資訊給 Client端,這樣就可以一口氣呈現有多筆待辦事項。

其實這個 Thread 是每五分鐘執行一次,由主程式呼叫之後會先休息五分鐘,之後去資料庫抓資料,同時會有個變數來計算如果自己被執行 12 次(一個小時),就會順便去資料庫撈取最新的員工資料,把[修改三]的自動更新部分並到這邊來處理。

修改五

另外是處理上線使用者清單,由於我是屬於老派的開發者,所以我喜歡把東西獨到記憶體後來處理,會比每次需要到資料庫去抓得快多了,一旦收到 Client 端發出更新清單需求,我就會去記憶體中統計有哪些人上線,然後把上線人員的資訊做成一個大字串傳給 Client,其中我用逗號(,)來區隔使用者不同的資訊,利用分號(;)來分隔不同的使用者,等到 Client 收到後再自行解開做字串分割存到不同的陣列欄位去。

以下是 Client 端的修正

修改六

首先修正如果收到其他同事送來的訊息,其實Client 端是不知道的,所以在我收到這類的訊息時,會先去檢查跟發信者是否已經開始交談視窗(檢查一個陣列aMsg),如果沒有就開一個起來,另外我也修改了TalkForm的建構,讓開啟交談視窗就會直接顯示對方送來的訊息。

void im_MessageReceived(object sender, IMReceivedEventArgs e)

        {

            this.BeginInvoke(new MethodInvoker(delegate

            {

                bool bOpened = false;

                string[] sList = e.From.Split(‘,’);

                textBoxSystemNote.Text  = String.Format(“{0} says {1}”, e.From, e.Message);

 

                for (int iCnt=1; iCnt < iMsg; iCnt ++)

                {

                    if (aMsg[iCnt] == sList[0])

                    {

                        bOpened = true;

                    }

                

                }

 

                if (!bOpened)

                {

                   

                    for (int iCnt = 1; iCnt < iMsg; iCnt++)

                    {

                        if (aMsg[iCnt] == “”)

                        {

                            aMsg[iCnt] = sList[0];

                            iCnt = iMsg;

                        }

                    }

                    TalkForm tf = new TalkForm(im, e.From, e.Message.ToString(), aMsg, iMsg);

                    talks.Add(tf);

                    tf.Show();

                }

               

            }));

        }

 

修改七

上線用戶清單,補強原先功能,讓顯示已經登入的使用者在Client端的頁面上,配合Server端的修改五功能,把列表顯示在ListBox 上,使用者直接雙擊該使用者就跳出對話方塊 (TalkForm),但要留意如果用戶清單變更後,可能這個ListBox 要一併跟著更新喔。

修改八

新增加一個待辦事項的 Form,處理收到最新待報事項後自動顯示著通知面板,上面條列出最新的待簽單據,並設定如果沒有任何動作的話10秒內自動關閉,以免打擾到正在工作的用戶,但設定直接點選該項目後會自動開啟網頁連結讓使用者可直接簽核。

面板實作範例如下:

IM Client Waiting List
IM Client Waiting List

修改九

最後就是一些小細節的修正,例如最小化後會縮到工具列去、聊天時按 Enter 就送出、變更 Icon、Publish 設定自動檢查新版等等…

因程式碼內部有許多公司資訊,所以不太方便直接公布,歡迎有興趣的人可以一起討論,其實還有用戶通訊內容的紀錄、Server Log File 等細節還需要再花點時間修飾,不過到目前為止已經可以上線測試,可以維持穩穩地12 小時不當正常使用喔。

感謝原作者 Teapot418 的原始碼 (http://www.codeproject.com/Members/Teapot418),  Very appreciate your work.

發表迴響