涂笑語(yǔ), 王美玉, 李 毅, 朱友華
(南通大學(xué)信息科學(xué)技術(shù)學(xué)院,江蘇南通226019)
Linux系統(tǒng)作為自由、開源的類Unix 操作系統(tǒng),廣泛應(yīng)用于個(gè)人計(jì)算機(jī)、服務(wù)器以及各種嵌入式終端設(shè)備。它的優(yōu)勢(shì)在于具有開放性、多用戶、多任務(wù)、豐富的網(wǎng)絡(luò)功能、可靠的系統(tǒng)安全等。用戶可以對(duì)加入革奴計(jì)劃(GNU’s Not Unix,GNU)的軟件進(jìn)行任意使用或者修改源代碼。目前,國(guó)內(nèi)外有各式各樣界面友好、功能齊全的即時(shí)通信軟件,但是它們都是基于Windows、Android和iOS系統(tǒng),或者只是功能較局限的Linux版本,且不是開源的。另外,不同商業(yè)軟件使用各自自有的即時(shí)通信協(xié)議,互相之間無(wú)法通信,這也不利于即時(shí)通信軟件的發(fā)展。設(shè)計(jì)一個(gè)基于Linux 的開源即時(shí)通信系統(tǒng)十分有必要。
傳輸控制協(xié)議(Transmission Control Protocol,TCP)的特點(diǎn)是面向連接且相對(duì)可靠[1]。通過(guò)該協(xié)議傳輸數(shù)據(jù)時(shí),首先要進(jìn)行3 次握手來(lái)建立服務(wù)器與客戶端之間的連接;為保證數(shù)據(jù)完整性和準(zhǔn)確性,如數(shù)據(jù)驗(yàn)證結(jié)果與原來(lái)不符,則需要重傳數(shù)據(jù);若數(shù)據(jù)傳輸順利完成,則需通過(guò)4 次揮手來(lái)關(guān)閉連接[2]。TCP 適用于傳輸大量數(shù)據(jù)且對(duì)可靠性要求較高的情況,其缺點(diǎn)是開銷較大、速度較慢[3]。
本設(shè)計(jì)采用基于TCP 的C / S 架構(gòu)[4]。C / S 架構(gòu)模式是非對(duì)稱的,核心是數(shù)據(jù)庫(kù)服務(wù)[5]。它將連接在網(wǎng)絡(luò)中的多臺(tái)終端組成一個(gè)整體,客戶端和服務(wù)器分別負(fù)責(zé)完成不同的功能。服務(wù)器用來(lái)響應(yīng)客戶端的請(qǐng)求,針對(duì)不同的請(qǐng)求類型為其提供對(duì)應(yīng)的服務(wù);客戶端根據(jù)自己的需求,向服務(wù)器發(fā)出不同的請(qǐng)求[6]。用戶A、B之間的信息傳輸通過(guò)服務(wù)器進(jìn)行中轉(zhuǎn)。C / S架構(gòu)的基本工作原理如圖1 所示。
圖1 C/ S架構(gòu)基本工作原理
目前市面上常見的即時(shí)通信軟件設(shè)計(jì)思路大致如圖2 所示[7]。首先打開應(yīng)用程序,顯示主界面,一般都會(huì)有注冊(cè)賬戶、登錄和退出這3 個(gè)基本功能。注冊(cè)時(shí),無(wú)論成功與否,系統(tǒng)均會(huì)有提示。登錄時(shí),如果用戶名和密碼匹配且正確,則可以成功登錄。登錄成功后,系統(tǒng)會(huì)根據(jù)不同的用戶身份顯示對(duì)應(yīng)的功能界面。在本系統(tǒng)設(shè)計(jì)中,管理員用戶有獨(dú)享功能。
圖2 設(shè)計(jì)思路
在服務(wù)器模塊中需要使用數(shù)據(jù)庫(kù)來(lái)存放用戶注冊(cè)的用戶名、密碼以及聊天記錄。系統(tǒng)設(shè)計(jì)過(guò)程中,使用SQLite的編程接口函數(shù)來(lái)實(shí)現(xiàn)創(chuàng)建表、添加數(shù)據(jù)、更新數(shù)據(jù)和查詢數(shù)據(jù)這4 個(gè)操作[8]。數(shù)據(jù)庫(kù)打開函數(shù)要和關(guān)閉函數(shù)成對(duì)使用,因?yàn)槿绻麛?shù)據(jù)庫(kù)沒有關(guān)閉,則其他程序無(wú)法使用該數(shù)據(jù)庫(kù)。而且數(shù)據(jù)庫(kù)的連接數(shù)有上限,如果有足夠數(shù)量的程序在關(guān)閉之前沒有關(guān)閉數(shù)據(jù)庫(kù),則可能導(dǎo)致數(shù)據(jù)庫(kù)系統(tǒng)崩潰。
本次設(shè)計(jì)需要?jiǎng)?chuàng)建的表名為user,有4 個(gè)字段,存放的數(shù)據(jù)均為TEXT類型。第1 個(gè)字段username存放用戶名,第2 個(gè)字段password 存放密碼,第3 個(gè)字段to_name存放發(fā)送信息時(shí)接收方的用戶名,第4 個(gè)字段record存放具體的聊天內(nèi)容。user 表的結(jié)構(gòu)如表1所示。
表1 user表的結(jié)構(gòu)
在傳輸數(shù)據(jù)時(shí),客戶端與服務(wù)器之間需要同時(shí)遵守一個(gè)協(xié)議來(lái)規(guī)范各個(gè)模塊之間的通信。本系統(tǒng)的設(shè)計(jì)中,定義了一個(gè)結(jié)構(gòu)體來(lái)規(guī)范通信時(shí)傳輸?shù)乃袛?shù)據(jù)類型。具體定義如下。
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];
};
對(duì)于上面的結(jié)構(gòu)體,action 中存放的是命令號(hào),不同的命令號(hào)對(duì)應(yīng)不同的指令;char類型的name數(shù)組存放用戶名,限定長(zhǎng)度為30;接下來(lái)分別是接收方用戶名、發(fā)送方用戶名、密碼和消息內(nèi)容;online_name 是一個(gè)二維數(shù)組,存放在線用戶名;最后3 個(gè)參數(shù)是消息記錄的行數(shù)、列數(shù)、具體消息記錄,這里限定最多查詢100 條消息記錄??蛻舳伺c服務(wù)器之間進(jìn)行通信時(shí),收發(fā)的信息均以結(jié)構(gòu)體的形式進(jìn)行傳輸??蛻舳嘶蛘叻?wù)器收到該結(jié)構(gòu)體后按照具體功能需求獲取所需的數(shù)據(jù)。
命令號(hào)action也需要客戶端和服務(wù)器采用同一種規(guī)范,雙方可以正確識(shí)別命令,然后去執(zhí)行對(duì)應(yīng)的函數(shù),實(shí)現(xiàn)相應(yīng)的功能。具體的命令號(hào)協(xié)議見表2。
表2 命令號(hào)協(xié)議
Linux系統(tǒng)中的網(wǎng)絡(luò)編程是通過(guò)socket 編程接口來(lái)實(shí)現(xiàn)的[9]。設(shè)計(jì)中采用了基于TCP 的流式套接字類型,使用socket庫(kù)中的sockaddr_in 數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)IP地址和端口號(hào)。編程過(guò)程中考慮到不同終端存儲(chǔ)數(shù)據(jù)時(shí)有大端模式和小端模式之分,還涉及到對(duì)網(wǎng)絡(luò)地址的字節(jié)序轉(zhuǎn)換。基于TCP 的C / S 架構(gòu)中服務(wù)器的搭建流程如圖3 所示[10-11]。
圖3 服務(wù)器端的搭建流程
服務(wù)器端需要先打開數(shù)據(jù)庫(kù),然后按照流程搭建服務(wù)器。服務(wù)器端搭建流程中用到了兩種套接字,第一種是socket()函數(shù)新建的套接字,listen()函數(shù)用來(lái)監(jiān)聽該套接字;如果有客戶端請(qǐng)求連接,則新建一個(gè)通信套接字來(lái)專門用來(lái)處理與該客戶端的連接。需要注意的是,在執(zhí)行l(wèi)isten()函數(shù)之后要?jiǎng)?chuàng)建多線程,主線程負(fù)責(zé)監(jiān)聽客戶端的請(qǐng)求,每當(dāng)有一個(gè)客戶端請(qǐng)求連接就需要新建一個(gè)線程來(lái)專門處理與該客戶端的通信[12-13]。accept()函數(shù)是一個(gè)阻塞型函數(shù),如果沒有客戶端請(qǐng)求連接,則一直阻塞??蛻舳伺c服務(wù)器連接成功后,負(fù)責(zé)通信的線程會(huì)執(zhí)行receive_msg()函數(shù)。該函數(shù)內(nèi)部會(huì)用read()函數(shù)讀取命令代號(hào),根據(jù)命令號(hào)分別執(zhí)行不同的函數(shù),實(shí)現(xiàn)不同的功能,將對(duì)應(yīng)的信息返回給目標(biāo)客戶端。服務(wù)器端的執(zhí)行流程如圖4所示。
圖4 服務(wù)器端的執(zhí)行流程
(1)注冊(cè)reg()。收到注冊(cè)請(qǐng)求后,先在數(shù)據(jù)庫(kù)中查找想要注冊(cè)的用戶名,如果該用戶名未被注冊(cè),則將用戶名和密碼插入數(shù)據(jù)庫(kù)中,返回注冊(cè)成功代號(hào);如果用戶名已被注冊(cè),則返回錯(cuò)誤號(hào)。
(2)登錄log_in()。收到登錄請(qǐng)求后,檢查該用戶是否已登錄、是否已在線、用戶名和密碼是否匹配,檢查用戶身份,向客戶端返回身份代號(hào),將該用戶插入在線用戶列表。
(3)群聊chat-all()。將用戶想要發(fā)送的信息保存到數(shù)據(jù)庫(kù)的聊天記錄字段,給當(dāng)前所有在線用戶發(fā)送該信息。
(4)私聊chat_ private()。檢查目標(biāo)用戶是否存在、是否在線,如果在線,保存聊天記錄,向該用戶發(fā)送信息。
(5)修改密碼modify_password()。使用update命令在數(shù)據(jù)庫(kù)中將當(dāng)前用戶的password字段更新為想要修改的密碼。
(6)查看聊天記錄chat_record()。使用select命令去數(shù)據(jù)庫(kù)中查詢記錄,只要username 或者to_name中有任意一個(gè)字段與當(dāng)前用戶名匹配,即為當(dāng)前用戶的聊天記錄,將其返回客戶端即可。
(7)踢人kick_out()。檢查目標(biāo)用戶是否存在、是否在線,如果在線,將其通信套接字置零并移出在線列表。
(8)禁言與解禁banned()/ unbanned()。該功能主要在客戶端實(shí)現(xiàn),服務(wù)器只需返回標(biāo)志位。
此外,C / S架構(gòu)中客戶端的搭建流程如圖5 所示。
圖5 客戶端的搭建流程
客戶端運(yùn)行之后會(huì)顯示主頁(yè)面,可以選擇注冊(cè)、登錄或者退出,系統(tǒng)會(huì)根據(jù)輸入的命令號(hào)的不同對(duì)應(yīng)執(zhí)行不同的函數(shù)。如果注冊(cè)成功,則系統(tǒng)會(huì)在服務(wù)器端的數(shù)據(jù)庫(kù)中添加用戶名、密碼等相關(guān)信息。如果用戶登錄時(shí)輸入的用戶名和密碼均存在且與服務(wù)器端的數(shù)據(jù)庫(kù)匹配,則可以登錄成功。登錄成功之后,客戶端會(huì)利用多線程技術(shù)進(jìn)行讀寫分離,即主線程函數(shù)根據(jù)用戶身份顯示對(duì)應(yīng)功能界面,根據(jù)用戶輸入的命令專門負(fù)責(zé)向服務(wù)器發(fā)送消息,新建線程專門負(fù)責(zé)從服務(wù)器接收消息,根據(jù)服務(wù)器返回的命令號(hào)去解析消息結(jié)構(gòu)體,從中獲取所需的數(shù)據(jù),顯示該命令對(duì)應(yīng)功能所需的信息??蛻舳说膱?zhí)行流程如圖6 所示。
(1)注冊(cè)reg()。從鍵盤讀取用戶輸入的用戶名和密碼,如果符合要求,則發(fā)送給服務(wù)器,等待服務(wù)器返回注冊(cè)狀態(tài)。
(2)登錄log_in()。輸入用戶名和密碼,發(fā)送給服務(wù)器,等待服務(wù)器返回登錄狀態(tài)。
(3)向服務(wù)器發(fā)消息write_to_server()。主線程負(fù)責(zé)執(zhí)行該函數(shù),根據(jù)不同用戶身份,系統(tǒng)顯示不同的功能界面。對(duì)于普通用戶,主要功能有:查看在線用戶、群聊、私聊、修改密碼、查看聊天記錄和退出登錄。對(duì)于管理員用戶,新增了踢人、禁言和解禁的功能。用戶輸入不同的命令號(hào),執(zhí)行相應(yīng)的函數(shù)。
圖6 客戶端的執(zhí)行流程
(4)從服務(wù)器讀消息read_from_server()。新建的線程負(fù)責(zé)執(zhí)行該函數(shù),根據(jù)從服務(wù)器端返回的命令號(hào)的不同,解析消息結(jié)構(gòu)體中的數(shù)據(jù)并顯示出對(duì)應(yīng)的提示信息。如客戶端收到的是群聊或者私聊命令,顯示消息結(jié)構(gòu)體中的文本信息并提示用戶消息類型。如客戶端收到的是踢人命令,表示當(dāng)前登錄的用戶被管理員踢出聊天室,調(diào)用函數(shù)退出程序。禁言和解禁功能主要在客戶端的代碼中實(shí)現(xiàn),首先定義一個(gè)全局變量flag作為標(biāo)志位,默認(rèn)為“0”,表示未被禁言。當(dāng)客戶端收到禁言命令時(shí),將flag 置為“1”,表示當(dāng)前用戶被禁言。此外,還要在write_to_server()函數(shù)中設(shè)置條件,發(fā)送群聊和私聊請(qǐng)求時(shí)如遇到flag 為“1”就不會(huì)向服務(wù)器發(fā)消息,在用戶界面提示已被禁言,這樣就實(shí)現(xiàn)了禁言功能。解禁功能編程思路與禁言一致,只需要將標(biāo)志位flag重新置為“0”[14]。
本實(shí)驗(yàn)設(shè)計(jì)了一個(gè)基于Linux 平臺(tái)的即時(shí)通信系統(tǒng),實(shí)現(xiàn)了注冊(cè)、登錄、查看聊天記錄、群聊、私聊、修改密碼、查看聊天記錄等基本功能,新增管理員用戶獨(dú)享踢人、禁言和解禁功能。經(jīng)過(guò)測(cè)試,系統(tǒng)實(shí)現(xiàn)了預(yù)期的功能,滿足通信需求,而且系統(tǒng)運(yùn)行穩(wěn)定,具有一定的實(shí)用性。同時(shí)還可對(duì)系統(tǒng)進(jìn)行圖形化界面設(shè)計(jì)、引入數(shù)據(jù)傳輸?shù)募用芩惴ê蛯?shí)現(xiàn)與外部網(wǎng)絡(luò)的連接等進(jìn)一步優(yōu)化操作[15-16]。