涂笑語, 王美玉, 李 毅, 朱友華
(南通大學信息科學技術學院,江蘇南通226019)
Linux系統作為自由、開源的類Unix 操作系統,廣泛應用于個人計算機、服務器以及各種嵌入式終端設備。它的優勢在于具有開放性、多用戶、多任務、豐富的網絡功能、可靠的系統安全等。用戶可以對加入革奴計劃(GNU’s Not Unix,GNU)的軟件進行任意使用或者修改源代碼。目前,國內外有各式各樣界面友好、功能齊全的即時通信軟件,但是它們都是基于Windows、Android和iOS系統,或者只是功能較局限的Linux版本,且不是開源的。另外,不同商業軟件使用各自自有的即時通信協議,互相之間無法通信,這也不利于即時通信軟件的發展。設計一個基于Linux 的開源即時通信系統十分有必要。
傳輸控制協議(Transmission Control Protocol,TCP)的特點是面向連接且相對可靠[1]。通過該協議傳輸數據時,首先要進行3 次握手來建立服務器與客戶端之間的連接;為保證數據完整性和準確性,如數據驗證結果與原來不符,則需要重傳數據;若數據傳輸順利完成,則需通過4 次揮手來關閉連接[2]。TCP 適用于傳輸大量數據且對可靠性要求較高的情況,其缺點是開銷較大、速度較慢[3]。
本設計采用基于TCP 的C / S 架構[4]。C / S 架構模式是非對稱的,核心是數據庫服務[5]。它將連接在網絡中的多臺終端組成一個整體,客戶端和服務器分別負責完成不同的功能。服務器用來響應客戶端的請求,針對不同的請求類型為其提供對應的服務;客戶端根據自己的需求,向服務器發出不同的請求[6]。用戶A、B之間的信息傳輸通過服務器進行中轉。C / S架構的基本工作原理如圖1 所示。

圖1 C/ S架構基本工作原理
目前市面上常見的即時通信軟件設計思路大致如圖2 所示[7]。首先打開應用程序,顯示主界面,一般都會有注冊賬戶、登錄和退出這3 個基本功能。注冊時,無論成功與否,系統均會有提示。登錄時,如果用戶名和密碼匹配且正確,則可以成功登錄。登錄成功后,系統會根據不同的用戶身份顯示對應的功能界面。在本系統設計中,管理員用戶有獨享功能。

圖2 設計思路
在服務器模塊中需要使用數據庫來存放用戶注冊的用戶名、密碼以及聊天記錄。系統設計過程中,使用SQLite的編程接口函數來實現創建表、添加數據、更新數據和查詢數據這4 個操作[8]。數據庫打開函數要和關閉函數成對使用,因為如果數據庫沒有關閉,則其他程序無法使用該數據庫。而且數據庫的連接數有上限,如果有足夠數量的程序在關閉之前沒有關閉數據庫,則可能導致數據庫系統崩潰。
本次設計需要創建的表名為user,有4 個字段,存放的數據均為TEXT類型。第1 個字段username存放用戶名,第2 個字段password 存放密碼,第3 個字段to_name存放發送信息時接收方的用戶名,第4 個字段record存放具體的聊天內容。user 表的結構如表1所示。

表1 user表的結構
在傳輸數據時,客戶端與服務器之間需要同時遵守一個協議來規范各個模塊之間的通信。本系統的設計中,定義了一個結構體來規范通信時傳輸的所有數據類型。具體定義如下。
struct msg
{
int action;
char name[30];
char to_name[30];
char from_name[30];
char password[30];
char message[1024];
char online_name[30][30];
int row;
int column;
char record[100][100];
};
對于上面的結構體,action 中存放的是命令號,不同的命令號對應不同的指令;char類型的name數組存放用戶名,限定長度為30;接下來分別是接收方用戶名、發送方用戶名、密碼和消息內容;online_name 是一個二維數組,存放在線用戶名;最后3 個參數是消息記錄的行數、列數、具體消息記錄,這里限定最多查詢100 條消息記錄。客戶端與服務器之間進行通信時,收發的信息均以結構體的形式進行傳輸。客戶端或者服務器收到該結構體后按照具體功能需求獲取所需的數據。
命令號action也需要客戶端和服務器采用同一種規范,雙方可以正確識別命令,然后去執行對應的函數,實現相應的功能。具體的命令號協議見表2。

表2 命令號協議
Linux系統中的網絡編程是通過socket 編程接口來實現的[9]。設計中采用了基于TCP 的流式套接字類型,使用socket庫中的sockaddr_in 數據結構來存儲IP地址和端口號。編程過程中考慮到不同終端存儲數據時有大端模式和小端模式之分,還涉及到對網絡地址的字節序轉換。基于TCP 的C / S 架構中服務器的搭建流程如圖3 所示[10-11]。

圖3 服務器端的搭建流程
服務器端需要先打開數據庫,然后按照流程搭建服務器。服務器端搭建流程中用到了兩種套接字,第一種是socket()函數新建的套接字,listen()函數用來監聽該套接字;如果有客戶端請求連接,則新建一個通信套接字來專門用來處理與該客戶端的連接。需要注意的是,在執行listen()函數之后要創建多線程,主線程負責監聽客戶端的請求,每當有一個客戶端請求連接就需要新建一個線程來專門處理與該客戶端的通信[12-13]。accept()函數是一個阻塞型函數,如果沒有客戶端請求連接,則一直阻塞。客戶端與服務器連接成功后,負責通信的線程會執行receive_msg()函數。該函數內部會用read()函數讀取命令代號,根據命令號分別執行不同的函數,實現不同的功能,將對應的信息返回給目標客戶端。服務器端的執行流程如圖4所示。

圖4 服務器端的執行流程
(1)注冊reg()。收到注冊請求后,先在數據庫中查找想要注冊的用戶名,如果該用戶名未被注冊,則將用戶名和密碼插入數據庫中,返回注冊成功代號;如果用戶名已被注冊,則返回錯誤號。
(2)登錄log_in()。收到登錄請求后,檢查該用戶是否已登錄、是否已在線、用戶名和密碼是否匹配,檢查用戶身份,向客戶端返回身份代號,將該用戶插入在線用戶列表。
(3)群聊chat-all()。將用戶想要發送的信息保存到數據庫的聊天記錄字段,給當前所有在線用戶發送該信息。
(4)私聊chat_ private()。檢查目標用戶是否存在、是否在線,如果在線,保存聊天記錄,向該用戶發送信息。
(5)修改密碼modify_password()。使用update命令在數據庫中將當前用戶的password字段更新為想要修改的密碼。
(6)查看聊天記錄chat_record()。使用select命令去數據庫中查詢記錄,只要username 或者to_name中有任意一個字段與當前用戶名匹配,即為當前用戶的聊天記錄,將其返回客戶端即可。
(7)踢人kick_out()。檢查目標用戶是否存在、是否在線,如果在線,將其通信套接字置零并移出在線列表。
(8)禁言與解禁banned()/ unbanned()。該功能主要在客戶端實現,服務器只需返回標志位。
此外,C / S架構中客戶端的搭建流程如圖5 所示。

圖5 客戶端的搭建流程
客戶端運行之后會顯示主頁面,可以選擇注冊、登錄或者退出,系統會根據輸入的命令號的不同對應執行不同的函數。如果注冊成功,則系統會在服務器端的數據庫中添加用戶名、密碼等相關信息。如果用戶登錄時輸入的用戶名和密碼均存在且與服務器端的數據庫匹配,則可以登錄成功。登錄成功之后,客戶端會利用多線程技術進行讀寫分離,即主線程函數根據用戶身份顯示對應功能界面,根據用戶輸入的命令專門負責向服務器發送消息,新建線程專門負責從服務器接收消息,根據服務器返回的命令號去解析消息結構體,從中獲取所需的數據,顯示該命令對應功能所需的信息。客戶端的執行流程如圖6 所示。
(1)注冊reg()。從鍵盤讀取用戶輸入的用戶名和密碼,如果符合要求,則發送給服務器,等待服務器返回注冊狀態。
(2)登錄log_in()。輸入用戶名和密碼,發送給服務器,等待服務器返回登錄狀態。
(3)向服務器發消息write_to_server()。主線程負責執行該函數,根據不同用戶身份,系統顯示不同的功能界面。對于普通用戶,主要功能有:查看在線用戶、群聊、私聊、修改密碼、查看聊天記錄和退出登錄。對于管理員用戶,新增了踢人、禁言和解禁的功能。用戶輸入不同的命令號,執行相應的函數。

圖6 客戶端的執行流程
(4)從服務器讀消息read_from_server()。新建的線程負責執行該函數,根據從服務器端返回的命令號的不同,解析消息結構體中的數據并顯示出對應的提示信息。如客戶端收到的是群聊或者私聊命令,顯示消息結構體中的文本信息并提示用戶消息類型。如客戶端收到的是踢人命令,表示當前登錄的用戶被管理員踢出聊天室,調用函數退出程序。禁言和解禁功能主要在客戶端的代碼中實現,首先定義一個全局變量flag作為標志位,默認為“0”,表示未被禁言。當客戶端收到禁言命令時,將flag 置為“1”,表示當前用戶被禁言。此外,還要在write_to_server()函數中設置條件,發送群聊和私聊請求時如遇到flag 為“1”就不會向服務器發消息,在用戶界面提示已被禁言,這樣就實現了禁言功能。解禁功能編程思路與禁言一致,只需要將標志位flag重新置為“0”[14]。
本實驗設計了一個基于Linux 平臺的即時通信系統,實現了注冊、登錄、查看聊天記錄、群聊、私聊、修改密碼、查看聊天記錄等基本功能,新增管理員用戶獨享踢人、禁言和解禁功能。經過測試,系統實現了預期的功能,滿足通信需求,而且系統運行穩定,具有一定的實用性。同時還可對系統進行圖形化界面設計、引入數據傳輸的加密算法和實現與外部網絡的連接等進一步優化操作[15-16]。