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名稱。







2015年8月14日 星期五

以C語言設計一個支援傳一個網頁含一張圖片的多工 Web Server

這次主要是要先用 [ wireshark ] 抓封包軟體來觀察一個伺服器如何去回應瀏覽器使用者請求,再來處理程式方面的撰寫,之後抓封包來驗證自己的程式是否正確回應

******************概念部分****************************
第一點:  瀏覽器的使用者連線部分要以Server的ip位址和通訊埠(port)去連上伺服器
     127.0.0.1:4700
       伺服器的ip(這邊由於是在本機上操作,因此用迴路ip就行): 伺服器端設定http server 的port

第二點:  瀏覽器與伺服器的互動方式是瀏覽器先發出 [ GET ] 請求伺服器端的index.html檔,伺服器端收到請求之後回覆HTTP/1.0 200 OK 代表請求成功收到且傳送html給瀏覽器端,再依照html檔裡面的資訊(例如圖片)瀏覽器端再去跟伺服器發出 [GET ] 請求來讀取圖片,伺服器再次回覆HTTP/1.0 200 OK。彼此的互動是一來一往的。
      HTTP/1.0 200 OK
        伺服器的http版本為1.0,200 OK為成功收到的狀態碼,不同的狀態碼代表不一樣的訊息

第三點:  在抓封包的時候可能會看到HTTP/1.1 304 Not Modified代表對client的request的回應,而不是HTTP/1.1 200 OK,這是因為只要曾經瀏覽過該webserver的網站時,瀏覽器有個功能就是緩存之前的資料,只要下次再重新瀏覽該webserver,webserver就會比對資料的更新時間,沒改變就回HTTP/1.1 304 Not Modified直接存取緩存資料,可以加快下載瀏覽速度,反之從未瀏覽過的webserver或資料有更動時才會回HTTP/1.1 200 OK,再次從webserver端讀取新的資料到瀏覽器上面。

第四點:   程式方面伺服器端至少需要具備兩個檔案,一個是http server的程式,另一個是html檔( 或者寫在http server裡面,但程式看起來會很冗雜,獨立寫一個html檔方便直接用瀏覽器驗證html檔有無錯誤 )

第五點:   http協定方面是以每一行程式文字敘述藉由\r\n代表一個項目的結束,整個http協定的結束要再增加\r\n再文字程式碼最後面
例如:  code==>  "HTTP/1.0 200 OK\r\n\r\n
    第一個\r\n代表http協定的標頭項(header)若單純如上直接撰寫第二個\r\n在標頭後面,則這個伺服器的http回覆只有標頭而沒有任何的檔案資訊



********************************************************************************

1.   首先是最基本的連線三向交握

2.   Client 連線成功後第一個發出請求的HTTP封包,要求讀取一個html




3.  Server回傳GET成功收到的Response給用戶端


4.   Server端回完Response後關閉與用戶端的連線,等待處理下一次的請求


5.    第二次三向交握( 讀圖 )



6.   Client端讀到HTML檔後,發出GET讀HTML裡面的圖檔的請求,用戶端的第二次GET請求


7. Server回傳GET成功收到的Response給用戶端


============================================

(1)  第一次用select的時候,把select放在處理完GET後的地方,發現client端怎麼都跑不出圖片來而timeout斷線,後來才想到要在client送請求的時候就要下select,讓server做判斷有資料進來,再去做處理請求或者逾時的client要斷線的動作。

再來是一開始不是很懂HTTP協定,以為只要GET和HTTP/寫在裡面就好,但事實上HTTP是看字串最後的\r\n代表一行標頭參數資料的結束,整個HTTP協定結束判斷要再加\r\n,之後的內容才是Body,亂寫參數client端會看不懂而錯誤,參數填錯而圖片跟html如果還能讀的到的原因是伺服器送的資料不再是http協定的格式,會以tcp封包丟給瀏覽器使用者,但變成一般的傳輸資料算是一種bug發生。

以及多次編譯的時候,會跑出bind正使用中而不能編譯檔案,這是因為要下寫ㄧ行程式setsockopt來使bind address能重新被使用的功能,最後是static要加在副函式前面,讓只有這個process內部能使用,避免其他的process用到而產生錯誤。

(2). 從觀察的tcp封包來看,每一次Client所發出的請求而web server端回應完之後就斷線,web server端再與下個請求去做連線、完成處理、回應後再斷線,因此為http1.0,如果是web server是支援http1.1版本,每當client第一次連線後,之後所有的client請求都在這一次的連線中被web server處理完成、回應完後才會斷線。


2015年8月12日 星期三

以ARP封包網路攻擊

這次的報告有兩個方向,一個是以ARP封包發送給受害端使他不能得到網路服務,另一個是受害端仍可以正常上網,但是封包會先經由攻擊者再發往閘道路由器出去,使攻擊者可以竊聽到受害端封包資訊。實驗由兩台VMware Linux作業系統彼此在相同網域內模擬網路攻擊行為,左邊使用者jyen14是攻擊方,右邊jyen是受害方



1.   首先假設被害方的ARP表不知道攻擊方的資訊只知道閘道的資訊



2.  攻擊者送arp_request問對方mac address是多少,做開始進行攻擊之前的資訊蒐集
( 這邊是直接寫一個ARP封包程式並設定Request送給受害方來得到mac address,也可以使用[ IPSeizer ] 是一種應用程式供下載使用,可以直接掃出同網域內的使用者ip和mac address )



3.  被攻擊者收到arp_request也會增加攻擊者的ip和mac address到arp_table(實驗確認用~)


4.  本來被攻擊者可以上網(開始觀察攻擊行為)




5-1.  開始攻擊 
( 攻擊者送出arp_reply封包給受害方,而網路服務封包被送往攻擊者接收 )

5-2.  被攻擊者的arp_table閘道被更改mac address後不能上網 (封包往攻擊者過去)




6-1.   這是第二部分的觀察,設定攻擊者的電腦可以轉送封包
( Linux kernel環境下可以做轉發封包的設定 )



6-2.   送過來的封包轉送到本來的閘道路由器方向




6-3.   被攻擊者雖然封包流往攻擊方,但封包只經由攻擊方再到達閘道出去,因此不會斷網而達到竊聽封包的功能

=============================================
以上是一種ARP網路攻擊的探討,由於是在VMware裡面設定NAT的網路架構,因此攻擊行為都在本機上操作不會影響到實體區域網路,請以學術用途為主,千萬別實際運用在實體網路上,是非法行為!








2015年8月1日 星期六

可正確續傳的4G大檔案-client/server

整理一下上學期網路程式設計的作業報告,老師那時候講了一些在接收端(client)和發送端(server)上要注意研究的問題:
---------------------------------------------------------------------------------------------------------------------
狀況一: 單個client完整傳完
狀況二: client斷或server斷後續傳
狀況三: 多個client同ip同時傳,而某個client斷續傳
狀況四: 檔案已存在
狀況五: client網路斷掉timeout後離開再連Server續傳
狀況六: client網路斷掉後timeout之前連回去可續傳

---------------------------------------------------------------------------------------------------------------------

續傳程式大致上就幾個重點:

第一點是fseek的使用( http://linux.die.net/man/3/fseek )
fseek內的第一個參數fp2是檔案指標,第二個參數0代表指標要移動的offset值,第三個參數SEEK_END代表一種Action將指標移至最尾端。

check=fseek( fp2, 0, SEEK_END); //將檔案指標指到檔案最後.offset=0
total= ftell(fp2);                //回傳指標所移動的值,代表從檔案指標從開始到目前位置共多長

通常fseek會搭配ftell來得到檔案指標的位置,fseek回傳值check等於0代表成功執行,-1代表有錯誤。而作業要用到fseek的部分來做移動續傳檔案的檔案指標,再接著現有的部分傳完剩下的內容。


第二點fseek前的檔案指標開啟:
我這裡先使用找檔案的指令放在程式碼裡面,讓接收端掃描要求的檔案如果存在就把檔案大小當offset傳給server告訴他client已有完整檔案而server退出process,client也會離開執行,否則就當作offset傳。

如果續傳的情況下,打開檔案指標沒有用 ” a+”反而直接以fseek移動檔案指標來寫檔案會出錯,因為原本fopen打開的型態是以可讀寫方式從頭開始,因此要改成從檔案尾開始讀取寫入,資料才不會亂碼。

 if ( (fp = fopen(Filename, "a+")) == NULL){
                perror_check("fopen continue  error!");
         }


第三點是signal的處理:
用來判斷fork後的child process是否還存在(判斷是否僵屍process),如果完整的結束並且離開會child process會發signal給系統,就會到signal handle function裡做處理,waitpid裡的 ”-1”代表等待所有產生的child process結束,” WNOHANG” 代表沒有child process存在時立即回到這行。

static void handle(int signum){
                printf("clear! memory\n");
                waitpid(-1,NULL,WNOHANG);
}

int main(int argc,char* arv[]){
     signal(SIGCHLD,handle);  //當fork後的child process完成後發出signal並前往handle處理
......}


最後要記得檔案內產生的副函式要加static防止其他檔案用到這個程式產生的副函式而產生錯誤,以及任何socket連線之後結束程式要close 


=======================================================================

狀況一: 單個client完整傳完
左邊terminal是client端,右邊terminal是server端
client端連上server後,收到server端傳過來的檔名和大小,而client端先找自己有沒有該檔案
狀況一是client端沒有該檔案,因此回傳offset=0給server端,讓server從頭開始傳檔案。


完整正確傳完之後,server端要確定以fork出來在用傳送檔案的child process有沒有正確結束,不然會一直佔著記憶體空間。

驗證正確傳完的檔案有無損壞,解壓縮完就知道正確性。

========================================================================



狀況二: client斷或server斷後續傳
狀況二是client端與server端傳送中斷,由於傳送中斷,client端就直接跳回未連線狀態的時候,圖中間也看到檔案傳輸被斷在1.5G左右,而server端只是因為fork出來傳送檔案的child process被中斷,但仍然保持可以讓其他client端進來連線的狀態。


這是client端連回server端的開始畫面(在select timeout之後的重連),client端會先檢查本地有沒有該檔案,再來讀取檔案大小(offset)傳給server端並移動server端檔案指標到該offset的位置進行續傳。


從圖可以看到檔案續傳成功。

=======================================================================

狀況三: 多個client同ip同時傳,而某個client斷續傳
此圖左邊terminal為server端,右邊2個terminal位於同主機內不同資料夾下的client檔對server端進行連線,2個client端都沒有該檔案的情況下進行傳送檔案。


此圖顯示右邊client端連線中斷,但中間client端仍可以保持與server端的檔案傳送。


右邊client端連回server端的畫面(在select timeout之後的重連)即將從1.2G處開始續傳檔案,中間client不受右邊client端重連的影響,持續在傳送檔案。

顯示兩個client端的檔案都接收成功。

=======================================================================

狀況四: 檔案已存在
特別做這個狀況的探討是因為檔案就算存在,也要確確實實檢查client端的檔案是否完整,檔案過大或少都代表與server端的檔案內容不符,因此即便傳完也得試著檢查檔案。

=======================================================================

狀況五: client網路斷掉timeout後離開再連Server續傳

這個狀況五跟狀況二的續傳不太一樣,狀況二是基於傳送失敗而重連續傳,但狀況五的情形是網路斷掉(上圖以兩台VM不同ip下做測試),server端會根據自己程式碼的寫法(select設定timeout 30秒)來判斷client端有無重連(依據server端會發送帶有RST的封包給client接收),若30秒內client沒再次連上網的話,server端會直接關閉client端的socket,代表下次該client的連線屬於重新連線(新的一次三向交握)的續傳。

=======================================================================

狀況六: client網路斷掉timeout後之前連回網路可續傳

如最後所示,連回去後的續傳檔案正確接收


========================================================================
最後,可能會對圖片內傳送檔案的數值有個疑問
" 明明buffer size設整數為什麼傳送或接收端的值不符合size大小值? "

那是因為傳送檔案的時候,不管有沒有跨網段,封包在傳送時不太可能會實際按照你設定的buffer size來做傳輸,反倒是依據網路情況來做分割封包或調整size大小值,因此值就有時大有時小,反之傳送程式buffer size設得太小的話,大概不會被改size值但電腦會對這個傳送程式的process吃非常大的CPU,所以設定size的大小要看當時的檔案來做選擇,這邊我是設50K來做測試。








2014年10月29日 星期三

Verilog CRC16-CCITT晶片模擬簡易操作在 Quartus II 13.0 & Modelsim

   @@@電路圖@@@

1.前置作業:
   下載Quartus II(用來撰寫Verilog HDL語言程式)
   下載ModelSim (用來跑出CRC波形的程式)

2.建立專案:
    -1.創一個新專案(xxx.QPF檔)
    -2存放路徑
 紅色框框:存放專案與程式碼和模擬結果的資料夾(可自行建立)
 藍色框框:專案名稱(注意:需與程式碼內的開頭model crc-16
                   一樣名稱)

   -3撰寫程式碼(XXX.v檔)使用Verilog HDL語言

   -4編譯程式(成功之後會跳出successful的提示視窗)

    
    -5建立模擬波形檔

    

    第一步:插入程式碼內的Reg和clock等等模擬設定
    第二步:點選列出清單
    第三步:選擇插入元件
    第四步:全部移入到模擬設定
    

   
    -6設定初始值    

    紅色區:可設定電壓高低(選取區段波形可設定部分高電壓或
                 低電壓;紅色區右邊有個C鈕可以直接產生一個clock)
    紫色鈕:開始模擬
    <註:此程式碼設定datain為資料[01]以clock正緣觸發(0-1之間
           觸發),當put進來(put==1)才視作有data進來,reset為致0
           鈕 >

     -7ModelSim模擬結果圖
   

  此文章為本作者撰寫,若有引用請標明來源出處