2015年8月20日 星期四

C語言實踐多人聊天室



這是程式的流程圖,每個人寫法不一樣流程圖也會稍微不同,不過大致上描述如圖


多人聊天室的架構為一個server端負責讓多個client來連線,所以server的工作就是負責大家的連線總管和群播訊息給所有用戶



首先是select的使用,詳細查詢manpage( http://linux.die.net/man/2/select  ),這邊做簡單的說明,selectc函數是用來讓程式監視多個檔案控制代碼的狀態變化的,也就是程式(server端)在執行的時候調用select能夠處理一些事件的觸發(client連線),當沒有事件的時候程式處於等待未發生的事件狀態,程式會停在select這裡等待,直到被監視的檔案控制代碼有一個或多個發生了狀態改變。

select回傳值有3種: 小於0代表select發生錯誤;等於0代表逾時事件被觸發(timeout結構有設定才會執行);大於0代表成功執行並維持監聽狀態,監視有無內或外部事件觸發(與select的第二、三、四參數有關)


而select監控檔案的控制碼來判斷事件的觸發,檔案代碼也就是指一般的整數代碼  0 = 標準輸入;1=標準輸出;2=標準錯誤輸出。

int select(int sockfdfd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

先從第二、三、四參數設定開始說明,所謂的fd_set型態變數就是指readfdswritefdsexceptfds,使用select之前必須先設定fd_set型態參數,然後把這些變數的位址&readfds&writefds&exceptfds傳遞給select函數來監控。這三個參數都是一個整數控制碼的集合(大於2的整數),其中readfds為第二個參數是用來指檔案系統狀態準備ok,可做讀取的動作;writefds為第三個參數是用來指檔案系統狀態準備ok,可做寫出;第四個參數是exceptfds代表額外特殊狀況發生處理的控制碼設定,通常不使用的話以NULL取代。第五個參數是計時器設定,設定的秒數內為select監聽的持續時間,一旦超出設定的時間select的回傳值為0。
struct timeval {
    long    tv_sec;         /* seconds */
    long    tv_usec;        /* microseconds */
};

而回來講第一個參數整數值的選擇必須是目前檔案整數代碼最大值+1,前面有提到基本系統具備的標準輸入(0)、標準輸出(1)、標準錯誤輸出(2)整數代碼,對Server端來說也具備基本的(0、1、2)之外,開啟與client連線後自己的sock值為3,第一個連線的Client端的clientfd(就是accpet()的回傳值)就是4,第二個連上的Client端的clientfd值為5,依此最大值的+1來做為select的第一個參數。

第二點是select函數內的參數使用,建立完連線之後(accept()...)開始以while迴圈來重覆做select的監控功能,所以第一件事必須先定義出fd_set型態參數,第二件事將這個檔案描述清單集合初始化歸零,再來第三件事就是加入這個清單內要讓select監聽的檔案整數代碼,第四件事設定計時器,處理select timeout(select回傳值為0)之後要處理的事情,第五件事select執行監控這些清單內的整數代碼有無事件發生,第六件事就是使用FD_ISSET()函數來判定監控清單內的整數代碼有發生變化就執行什麼事,前面的參數是檔案整數代碼;後面的參數是清單,例如監控清單內有加入clientfd的整數代碼,就可以偵測當server端收到訊息後進入此FD_ISSET(clientfd,&rfd) 函數內來做recv的接收訊息。

clientfd=accept(socket,...)
 while(1) ///進入無窮迴圈
   {
      int stat;  
      fd_set rfd; //1.檔案描述字元型態,類似建立一個開關清單讓select來偵測
      FD_ZERO(&rfd);  //2. 初始化 
       
      FD_SET(clientfd,&rfd);     //3. 增加要監聽的檔案狀態碼到清單中(例:0=基本輸入)   

      struct timeval timer;   //4.設定計時器
      timer.tv_sec =1;    //秒單位
           
      stat=select(clientfd+1,&rfd,NULL,NULL,&timer);  //5.監聽清單內的狀態碼變化
.
       if(stat<0){
          perror("select error!");
       }
     else if(stat==0){
       printf("timeout\n");
       continue;
     }
.      else{
           FD_ISSET(clientfd,&rfd) //6.若開關清單內的檔案狀態碼有變化(事件產生)就執行
  {
  recv  
.......
} .
}}



第三點是多人聊天室的架構方式概念,有了整數代碼的概念再來就是如何發訊息,Server端主要負責的工作是將client端連上server,以及群播client的訊息給所有client知道,而群播的時候要計算和儲存client目前人數以及上線人數的最大值,如此一來才能準確的發送給有上線的client,下線的client就不被發送訊息否則會產生錯誤。注意的是如果client端離線而下一個client連線的整數代碼會是以client端整數代碼的最小值(4)開始往後填,因此要思考相同整數代碼但前後不同client上線的使用,需以產生一個二維陣列來存放整數代碼和對應的client名稱。







沒有留言:

張貼留言