作業系統課程設計——多線程通訊-TCP聊天程式-VC++
作業系統課程設計任務書
一、設計題目:多機進程通訊
應用Socket進程通訊技術編寫聊天室程式,實現基於伺服器的並發多機資訊轉寄。如果各用戶端線上則可以即時聊天、發送接收檔案,如果某用戶端離線,則發送給他的訊息可由伺服器端進行內容緩衝,待重新連線後可以自動接收通過伺服器轉寄的資訊或檔案。緩衝與轉寄的控制演算法可參考作業系統課程中生產者消費者進程同步方法、緩衝池技術等相關理論。
二、 設計思路和功能的詳細描述
採用TCP協議,所以屬於客戶機/伺服器模式,因此需要聊天伺服器端和聊天用戶端兩個程式,實現的功能是:任意一台裝有用戶端程式的電腦都可以通過伺服器端的IP地址與伺服器相連,然後進入聊天室與串連到伺服器的其他客戶進行聊天。當客戶聊天結束時,可以點斷開與伺服器中斷連線,以釋放進程讓其他等待的客戶進入聊天室,本聊天室最大同時支援50個用戶端的串連,如果伺服器配置較高可以修改程式來增加同時串連數。
三、採用的方法、技術、運行環境及其配置
本聊天程式採用TCP協議,用VC++編寫,屬於客戶機/伺服器模式。採用了多線程的機制。其中使用windows Sockets實現多台電腦(多個進程)間的通訊,SOCKET實際在電腦中提供了一個通訊連接埠,可以通過這個連接埠與任何一個具有SOCKET介面的電腦通訊。應用程式在網路上傳輸,接收的資訊都通過這個SOCKET介面來實現。在客戶機/伺服器模式中客戶應用程式向伺服器程式請求服務。一個服務程式通常在一個眾所周知的地址監聽對服務的請求,也就是說,服務進程一直處於休眠狀態,直到一個客戶對這個服務的地址提出了串連請求。在這個時刻,服務程式被“驚醒”並且為客戶提供服務即對客戶的請求作出適當的反應。本聊天程式就是基於這中思想實現的,程式分為兩大部分:TCP聊天伺服器端和TCP聊天用戶端。兩者都擁有各自的SOCKET介面,其中伺服器端SOCKET介面需要綁定到固定地址上(實現語句:ock=Socket(AF_INET,SOCK_STREAM,0);),等待用戶端的串連(實現語句:listen(sock,5);)。等待用戶端的串連的過程就是通過多進程機制來實現的。
聊天程式是在VISUAL C++6.0上編譯實現的,在WINDOWS2000,XP上測試回合成功。
對客戶電腦配置無特殊要求,由於所設定的最大串連進程為50,所以對伺服器要求也不高。
四、關鍵來源程式及其詳細的注釋
<一>、伺服器端:
1、Socket初始化
//初始化對話方塊
BOOL CCSocketDlg::OnInitDialog()
{
count=0;
m_list.InsertColumn(0,"訊息");
m_list.SetColumnWidth(0,435);
m_edit.SetLimitText(99);
for (int i=0;i<50;i++)//初始化SOCKET數組
msgsock=NULL;
serv.sin_addr.s_addr=htonl(INADDR_ANY); //設定地址
serv.sin_family=AF_INET;
serv.sin_port=5000;//htons(5000);
addlen=sizeof(serv);
m_button.EnableWindow(FALSE);
sock=socket(AF_INET,SOCK_STREAM,0); //建立socket
if (bind(sock,(sockaddr*)&serv,addlen)) //綁定
{
m_edit.SetWindowText("綁定錯誤");
}else
{
m_edit.SetWindowText("伺服器建立成功");//顯示提示資訊,表示伺服器建立成功
listen(sock,5); //開始偵聽
AfxBeginThread(&thread,0); //調用線程
}
return TRUE;
}
2、接收線程
//伺服器線程
UINT thread(LPVOID p)
{
char buff[100];//定義緩衝區
CSize size;
size.cx=0;
size.cy=30;
int s=1,msgcount,loop=1,flag=0;
CCSocketDlg *dlg=(CCSocketDlg*)AfxGetApp()->GetMainWnd();//獲得當前運行對話方塊控制代碼
msgcount=dlg->getcount();//擷取還沒有被佔用的數組序號
if (msgcount==-1)//如果不等於-1,則表示,還有空缺SOCKET
loop=0;
if(loop)
{
s=1;
dlg->msgsock[msgcount]=accept(dlg->sock,(sockaddr*)&(dlg->serv),&(dlg->addlen));//
用空缺的SOCKET等待客戶串連
if (dlg->msgsock[msgcount]==INVALID_SOCKET)
{
dlg->m_edit.SetWindowText("Error accept");//如果返回錯誤,則提示錯誤
}
else
{
AfxBeginThread(thread,0);//如果和用戶端串連成功,則再次啟動一個線程
dlg->SetForegroundWindow();//顯示連機成功資訊
dlg->m_list.InsertItem(dlg->count++,"串連成功");
dlg->m_list.InsertItem(dlg->count++,inet_ntoa(dlg->serv.sin_addr));
dlg->m_list.Scroll(size);
dlg->m_button.EnableWindow(TRUE);
while(s!=SOCKET_ERROR)//如果沒有發生錯誤,則一直迴圈等待資料的到來
{
s=recv(dlg->msgsock[msgcount],buff,100,0); //迴圈接收資料
dlg->SetForegroundWindow();
if (s!=SOCKET_ERROR)如果接收成功,則顯示接收到的資料
{
dlg->m_list.InsertItem(dlg->count++,buff);
dlg->m_list.Scroll(size);
dlg->sendtoall(dlg->msgsock[msgcount],buff);//發送資訊到所有的用戶端
}
}
send(dlg->msgsock[msgcount],"Disconnected",100,0);//如果發生錯誤,發送串連中斷訊息到用戶端
dlg->m_list.InsertItem(dlg->count++,"Disconnected");//顯示相關資訊
dlg->m_list.Scroll(size);
dlg->msgsock[msgcount]=NULL;//將該SOCKET設定為空白
for (int i=0;i<50;i++)//判斷是否還有其他客戶在保持串連
if (dlg->msgsock!=NULL)
flag=1;
if (flag!=1)//如果沒有客戶串連了,則將發送按鈕設定為不可用
dlg->m_button.EnableWindow(FALSE);
closesocket(dlg->msgsock[msgcount]);//關閉串連
}
}
AfxEndThread(0); //終止線程
return 0;
}
3、資料發送
當使用者在文字框中輸入要發送的資訊後,然後單擊“發送”按鈕,則執行以下代碼。
//發送資料
void CCSocketDlg::OnButton1()
{
char buff[100];
m_edit.GetWindowText(buff,99);//獲得當前文字框中的資訊
m_edit.SetWindowText("");//清空文字框的資訊
m_list.InsertItem(count++,buff);//向列表框中插入要發送的資料
CSize size;
size.cx=0;
size.cy=30;
m_list.Scroll(size);
for (int i=0;i<50;i++)//迴圈向所有客戶發送資訊
{
if (msgsock!=NULL)
send(msgsock,buff,100,0);
}
}
<二>、用戶端
1、
串連伺服器
串連伺服器的程式是在“串連”按扭的單擊事件中處理的。
//串連伺服器,串連按扭處理事件
void CCSocketcliDlg::OnButton2()
{
char ipaddress[35];//定義標量以儲存伺服器位址
m_edit2.GetWindowText(ipaddress,30);//獲得伺服器位址
cli.sin_addr.s_addr=inet_addr(ipaddress);//設定SOCKET需要串連的地址
cli.sin_family=AF_INET;
cli.sin_port=5000;//htons(5000);//設定伺服器連接埠
clisock=socket(AF_INET,SOCK_STREAM,0);
//建立socket
ee=1;
AfxBeginThread(thread,0); //啟動線程
}
2、
接收資料線程
當使用者單擊“串連”按鈕以後,程式進行相關設定,最後調用了“AfxBeginThread(thread,0);”啟動了該線程。
UINT thread(LPVOID v)
{
char buff[100];
char array[25][30]=//定義數組用來存放一些IP地址
{"192.168.0.3",
…(這裡省略了部分IP)
"192.168.0.30"};
CSize size;
size.cx=0;
size.cy=30;
int s=1,addcount=0;
CCSocketcliDlg *dlg=(CCSocketcliDlg*) AfxGetApp()->GetMainWnd();//獲得對話方塊
dlg->m_connect.EnableWindow(FALSE);
dlg->m_disconnect.EnableWindow(TRUE);
while(connect(dlg->clisock,(sockaddr*)&(dlg->cli),sizeof(dlg->cli)) && dlg->ee!=0) //串連到伺服器
{
dlg->m_edit.SetWindowText("等待.....");
for (int i=0;i<=65000;i++)//空迴圈
for(int j=0;j<=200;j++);
if (addcount==25)
addcount=0;
dlg->cli.sin_addr.s_addr=inet_addr(array[addcount++]);//如果串連不成功,則串連下一個地址
}
if (dlg->ee==1)//如果串連成功,則顯示相關資訊
dlg->m_list.InsertItem(dlg->count++,"串連成功");
dlg->m_button1.EnableWindow(TRUE);//設定發送按鈕為可用狀態
dlg->SetForegroundWindow();
while(s!=SOCKET_ERROR && dlg->ee!=0) //迴圈獲得資料
{
s=recv(dlg->clisock,buff,100,0); //調用recv函數接收資料
dlg->SetForegroundWindow();
if (s!=SOCKET_ERROR && dlg->ee!=0)//如果沒有發生錯誤,同時沒有中斷連線,則顯示接收到的資料
dlg->m_list.InsertItem(dlg->count++,buff);
dlg->m_list.Scroll(size);
}
send(dlg->clisock,"Disconnected",100,0);//如果發生錯誤,發送斷開命令
dlg->m_button1.EnableWindow(FALSE);//設定相關控制項屬性
dlg->m_connect.EnableWindow(TRUE);
dlg->m_disconnect.EnableWindow(FALSE);
closesocket(dlg->clisock);//關閉SOCKET
AfxEndThread(0);//終止該線程
return 0;
}
3、
資料發送
資料發送就是用戶端向伺服器及其他用戶端發送資訊,其中向其他使用者發送資訊是通過伺服器實現的,因此,客戶只需要將訊息發送到伺服器就可以了。
//使用者單擊發送按扭,發送資訊
void CCSocketcliDlg::OnButton1()
{
char buff[100];
CSize size;
size.cx=0;
size.cy=30;
m_edit.GetWindowText(buff,99); //獲得發送資訊
m_edit.SetWindowText("");
m_list.InsertItem(count++,buff);
m_list.Scroll(size);
send(clisock,buff,100,0); //發送資料
}
五、執行結果及其分析。
1、開啟伺服器端聊天程式。
附件: 您所在的使用者組無法下載或查看附件
圖一:建立伺服器端(提示:伺服器建立成功)
伺服器端建立成功,並自動綁定了伺服器的IP。等待用戶端的串連請求,此時的發送按扭是停用,當有用戶端串連上時發送按扭就變為可用。在下面的編輯框裡輸入資訊就可以發送了。
附件: 您所在的使用者組無法下載或查看附件
圖二:有用戶端串連成功(IP地址為伺服器位址)
附件: 您所在的使用者組無法下載或查看附件
圖三:伺服器端發送“你好”並接受“用戶端回複”兩條資訊
附件: 您所在的使用者組無法下載或查看附件
圖四:有用戶端中斷連線(提示:Disconnected)
2、用戶端程式運行。
用戶端程式中輸入伺服器端所在的IP,然後點串連,如果成功就會提示串連成功,然後在下面的編輯框裡輸入資訊就可以發送了。如果串連不成功就會提示等待,此時有可能是伺服器IP輸入錯誤或是伺服器端達到了最大的進程。用戶端程式可以在程式原始碼中預先設定一批伺服器的IP,然後點串連,如果當前顯示的伺服器不能串連成功,則程式自動連接預先設定的第二個IP,一直迴圈測試連接,當與一台伺服器串連成功為時,提示串連成功,並停止迴圈。
附件: 您所在的使用者組無法下載或查看附件
圖五:用戶端運行(伺服器位址顯示的為預先設定的IP)
附件: 您所在的使用者組無法下載或查看附件
圖六:用戶端串連成功
附件: 您所在的使用者組無法下載或查看附件
圖七:用戶端發送“你好!”並接受“伺服器端回複”兩條資訊
六、心得體會
經過本次課程設計,基本掌握了Winsock編程的應用,對windows API函數的調用有了一定的瞭解,面對當前網上流行各種聊天室有了一定的認識,不再是那麼神秘。另外就是對基於伺服器的並發多機資訊轉寄技術的瞭解。在聊天室程式中是這樣實現的:當某一客戶機向其它客戶機發送資訊時,不是直接發給它,而是先發給伺服器,然後由伺服器分發給其它所有串連到伺服器的客戶機。還有就是對VC++的學習,通過這次課設讓我對VC++有了更深的認識,也瞭解到了它的功能的強大。最後最重要的是將作業系統課程中所學到了理論知識應用到了實踐,對其有了更深刻的認識。
七、主要參考資料:
1、電腦作業系統教程;
2、Visual c++網路通訊協議分析與應用實現(人民郵電出版社),汪曉平 鐘軍 編著;
3、MSDN參考。
轉至:http://www.youzaiyouzai.cn/showtopic.aspx?forumid=56&topicid=1570&go=next