程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 基於TCP的局域網多用戶通信、文件傳送程序詳解

基於TCP的局域網多用戶通信、文件傳送程序詳解

編輯:關於VC++

看了袁淵先生在VC知識庫《在線雜志》第14期發表的文章《基於TCP/IP的局域網多用戶通信》,感覺受益頗多,但也覺得裡面有一些不太完善的地方,具體來說主要有:

兩個服務器單獨運行,且主線程均阻塞,用戶界面死鎖,不便於控制;

聊天服務器線程和互斥量的使用可能導致死鎖;

不能實現文件傳送(文件傳送可不能由服務器轉發,否則非把它累趴下不可^-^);

不能由用戶進行網絡設置,所以在不同的網絡使用必須修改源程序等等;

我在此基礎上重新設計編寫了一個系統,具體如下:

一、構架設計

整個系統分為三個相關的程序模塊,即注冊登陸服務器(wbQQRegSer)、聊天通信服務器(wbQQChat)以及用戶程序(wbQQClient)。其中,注冊登陸服務器負責用戶的注冊、登陸以及數據庫管理;通信服務器負責完成數據轉發以及共享數據結構的管理;用戶端則完成注冊、登陸、通信和文件傳送功能。在進行文件傳送時,任一客戶程序均可以既作為文件傳送服務器發送文件,也可以作為客戶端接收文件,實現半雙工的文件傳送。整個系統構成如圖一:

圖一 系統構架圖

二、注冊登錄服務器設計

注冊登錄服務器采用面向連接的並發式方式,服務器設計成為一個對話框程序。調用WSAStartup初始化動態庫,socket函數創建套接字,bind函數綁定本地IP地址和端口,listen函數使套接字進入偵聽,然後由於調用accept()函數將產生阻塞,所以不宜在主線程中調用該函數,因而在初始化網絡後當用戶按下“運行注冊登錄服務器”按鈕後,利用偵聽套接字啟動注冊登錄線程RegLoad(void *s)進入無限循環,在線程中調用accept函數,用來接受來自客戶端的連接請求,每當一個連接請求到來時,accept()函數將產生一個新的套接字,利用這個套接字產生一個新的線程talkToClient(void *cs)與客戶端進行通信並讀寫數據庫,通信完畢後關閉該套接字和線程,原來的偵聽套接字繼續處於偵聽狀態。

兩個服務器程序可以在同一台物理機器上運行,也可以在不同的機子上運行,為方便服務器的控制,在注冊登錄服務器調用函數

CreateProcess( NULL,
    ".\\..\\wbQQChat\\wbQQChat.exe",   // Command line.
    NULL,       // Process handle not inheritable.
    NULL,       // Thread handle not inheritable.
    FALSE,      // Set handle inheritance to FALSE.
    0,        // No creation flags.
    NULL,       // Use parent''''s environment block.
    NULL,       // Use parent''''s starting directory.
    &si,       // Pointer to STARTUPINFO structure.
    &pi )
  

創建聊天通信服務器進程,想關閉時則調用TerminateProcess(m_hProcChat, 2)函數關閉此進程。

三、聊天通信服務器設計

聊天通信服務器設計為無界面的進程(創建時先建一個基於對話框的應用程序,然後把對話框類刪除,把APP類裡面與對話框有關的語句全刪除即可創建無界面進程),采用共享數據結構,為每個客戶端創建兩個線程,實現接收和轉發的功能。第一個線程用於發送,

hHandleSend = AfxBeginThread(SendData,(LPVOID)clientSocket,0,0,CREATE_SUSPENDED,NULL);

第二個線程用於接收:

hHandleRecv = AfxBeginThread(RecvData, (LPVOID)clientSocket);

四、客戶端設計

客戶端設計成為對話框的用戶界面,主要分成四個模塊,分別是注冊模塊、登錄模塊、聊天模塊和文件傳送模塊。

在程序運行後的第一個對話框,客戶可以選擇登錄或注冊,若是注冊則啟動注冊向導,分三步完成注冊工作,第一步為基本信息登記,包括頭像選擇、用戶名、性別、密碼,其中用戶名和密碼將在注冊成功後登錄使用。第二步為詳細資料,包括真實姓名、城市、E-mail地址和電話號碼。第三步為網絡設置,分別是注冊登錄服務器的IP地址和端口號,聊天通信服務器的IP地址和端口號,也就是說兩個服務器程序可以分別位於不同的物理機器,以減輕服務器運行時的負荷。點擊確定後,客戶端將與指定的IP地址和端口號去連接注冊登錄服務器,成功連接後服務器執行注冊操作,並返回注冊結果。

客戶注冊成功後,即可用注冊時的用戶名和密碼進行登錄,將登錄信息按注冊時的網絡設置發往服務器,服務器執行登錄操作並返回注冊結果,登錄成功則連接聊天通信服務器,否則退出程序。

登錄成功出現聊天對話框,可以從下拉組合框選擇好友,發送信息的同時將信息寫入聊天記錄文件,服務器收到信息後依照接收者用戶名進行轉發。若客戶收到信息則閃動托盤處的圖標,提示用戶收到信息,用戶可以點擊回答進行回復。

當登錄成功後,用戶也可以在選擇好友後點擊傳送文件按鈕來進行文件傳送。當客戶A向客戶B發送文件時,A彈出傳送文件對話框,提示給B發送文件,等待B的回應,客戶B將彈出消息框告知A向B發送文件,B可以接收也可以拒收。文件收發完畢後,點擊關閉按鈕關閉文件傳送對話框。

五、網絡傳輸協議設計

為了讓客戶端和服務器能夠協同工作,必須在通信過程中定義一套規則也就是協議,讓雙方能夠相互聽懂,並依照協議執行相應的功能塊。

客戶端注冊時發送的消息為Reg: + BasicDlg.m_strUserName + BasicDlg.m_nAge + sex + BasicDlg.m_strPassWd + MiscDlg.m_strTruName + MiscDlg.m_strCity + MiscDlg.m_strEmail + res + MiscDlg.m_strTel,注冊時發送消息的頭部為Reg。登錄時發送的消息為:Load: + m_strUserName + m_strPassWd,登錄時發送消息的頭部為Load。注冊登錄服務器收到客房端的消息後檢查其頭部,若是Reg則執行注冊操作,注冊成功則返回success!,用戶名已經存在則返回exist!,其它原因注冊不成功則返回Error!;若是Load則執行登錄操作,登錄成功返回success!,登錄不成功則返回error!。客戶端依照返回信息做出相應提示,並執行相應功能模塊。

登錄成功後,客戶端將自己的用戶名發送給聊天通信服務器,服務器為客戶端創建一個套接字,兩個線程,並填充socketInfo結構,連入鏈表。客戶端發送消息結構為:“接收者用戶名” + “:” + “發送者頭像ID” + “~” + “(星期、月、日、年、時、分、秒)” +"\t" +"發送者用戶名" +“->” + “接收者用戶名” +"\n\r" + “發送的消息”,其頭部均為接收者用戶名,服務器依照用戶名查找鏈表,截掉頭部後把原信息進行轉發,若客戶端關閉, 則發送消息為Close!,服務器從鏈表中刪除相應項。

客戶端可能收到的消息有三種,第一種為普通消息,結構如前所述;第二種為SendFile!,表示對方想向己方傳送文件;第三種為Refuse!,表示對方拒絕接收己方文件。客戶端A想給客戶端B傳送文件,則發送消息為SendFile!,B收到SendFile!後彈出消息框,提示對方向己方傳送文件,接收按“是”,執行文件接收功能;拒絕按“否”,發送Refuse!

六、附加說明

本軟件在win2000professionSP4 + vc6MFC環境下開發和測試通過,使用前要注冊ODBC數據源;數據源名稱:wbQQuser;類型:ACCESS;文件名:wbQQuser.mdb,不明了之處請參看源程序,注釋很清楚。

多線程通信使用的全局變量導致函數耦合度較大。

有些函數太長,導致功能不單一,內聚度降低。

客戶端點擊傳送文件後,應使該按鈕無效,直到文件傳送完畢或文件傳送線程關閉再使之有效,如不使用全局變量有什麼好辦法實現。

我認為學好一種技術愛好是最好的老師,交流是最好的方法,請高手賜教。

QQ:2105629

Email:[email protected]

志存高遠,腳踏實地,生命不息,奮斗不止!

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved