# 網路程式設計 HW09 非攔阻模式作業 學生: 1084837廖經翰 ## 應用1. 聊天室第1版解除一人一句限制 聊天室第1版為client會每間隔1秒,送出I miss you!,然後server會一直接收資訊並印出資訊,並且回傳I know.。但這次的作業應用1是需要把server改成一直接收資訊,然後每間隔5秒就會回傳I know,然後client用不同間隔的話,server也只會每5秒才回傳client,因為client還有server雙方都有接收訊息,所以雙方都必須要把socket改成為非攔阻模式 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:60%;" src="https://i.imgur.com/EmgYEea.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:60%;" src="https://i.imgur.com/PI4GJBb.png" /></center> <center>client</center> <br> server程式碼: ```C= #include<stdio.h> #include<winsock.h> #include<time.h> int main() { //set WSA and initial WSAStartup SOCKET sd , clnt_sd; WSADATA wsadata; struct sockaddr_in serv , clnt; int i , n; char str[1024]; clock_t start_time , end_time; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 1234 ); //綁定server結構裡設定的資訊 bind( sd , (struct sockaddr*) &serv , sizeof(serv) ); listen( sd , 5 ); int len_clnt = sizeof( clnt ); printf( "Server waits...\n" ); clnt_sd = accept( sd , (struct sockaddr*) &clnt , &len_clnt ); printf( "Client is connected\n" ); //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; ioctlsocket( clnt_sd , FIONBIO , &imode ); end_time = 0; float time; while(1) { //接收訊息 n = recv( clnt_sd , str , 100 , 0 ); //如果有接收到訊息,才會印出訊息內容 if( n > 0 ) { str[n] = '\0'; printf( "client: %s\n" , str ); } start_time = clock(); time = ( start_time - end_time ) / CLOCKS_PER_SEC; //每隔五秒的時候,傳送一次訊息 if( time > 5 ) { strcpy( str , "I know." ); printf( "server: %s\n" , str ); send( clnt_sd , str , strlen(str)+1 , 0 ); end_time = clock(); } //緩衝時間1秒 Sleep( 1000 ); } //關閉TCP socket closesocket(sd); closesocket(clnt_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```C= #include<stdio.h> #include<winsock.h> int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[100] = "I love NP!"; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 1234 ); //連接至server connect( sd , (struct sockaddr*) &serv , sizeof(serv) ); printf( "Connect to server\n" ); //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; ioctlsocket( sd , FIONBIO , &imode ); while(1) { strcpy( str , "I miss you!"); printf( "client: %s\n" , str ); send( sd , str , strlen(str) , 0 ); //緩衝時間0.5秒 Sleep( 500 ); //清空記憶體 memset( str , 0 , 100 ); //接收訊息 n = recv( sd , str , 100 , 0 ); //如果有接收到訊息,才會印出訊息內容 if( n > 0 ) { str[n] = '\0'; printf( "server: %s\n" , str ); } } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 應用2. 聊天室第2版解除一人一句限制 作業應用2是需要把原本的聊天室第2版,改成server要先等待5秒讓雙方都連入之後,不管順序,收到誰的訊息就轉送給另一方,接著client執行後,要先讓使用者輸入名稱與時間間隔,這樣子就會依照使用著輸入的時間間隔來固定週期發送其名稱,因為client還有server雙方都有接收訊息,所以雙方都必須要把socket改成為非攔阻模式 ( client.c程式只有一個,但要開兩次也就是 client1(間隔1秒) 與 client2(間隔2秒) ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:60%;" src="https://i.imgur.com/KBBCKbp.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:60%;" src="https://i.imgur.com/HVxKe5h.png" /></center> <center>client1</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:60%;" src="https://i.imgur.com/9r3SDSY.png" /></center> <center>client2</center> <br> server程式碼: ```C= #include<stdio.h> #include<winsock.h> int main() { //set WSA and initial WSAStartup SOCKET sd , clnt_sd1 , clnt_sd2; WSADATA wsadata; struct sockaddr_in serv , clnt1 , clnt2; int i , n; char str[100]; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 1234 ); //綁定server結構裡設定的資訊 bind( sd , (struct sockaddr*) &serv , sizeof(serv) ); listen( sd , 5 ); int len_clnt1 = sizeof( clnt1 ); int len_clnt2 = sizeof( clnt2 ); printf( "Server waits...\n" ); clnt_sd1 = accept( sd , (struct sockaddr*) &clnt1 , &len_clnt1 ); printf( "one client connects\n" ); clnt_sd2 = accept( sd , (struct sockaddr*) &clnt2 , &len_clnt2 ); printf( "two client connects\n" ); //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; ioctlsocket( clnt_sd1 , FIONBIO , &imode ); ioctlsocket( clnt_sd2 , FIONBIO , &imode ); while(1) { //接收client1的訊息 n = recv( clnt_sd1 , str , 100 , 0 ); //如果有接收到訊息,才會印出訊息內容 if( n > 0 ) { str[n] = '\0'; printf( "client[1]: %s\n" , str ); //把收到的訊息傳送給client2 send( clnt_sd2 , str , strlen(str)+1 , 0 ); } //接收client2的訊息 n = recv( clnt_sd2 , str , 100 , 0 ); //如果有接收到訊息,才會印出訊息內容 if( n > 0 ) { str[n] = '\0'; printf( "client[2]: %s\n" , str ); //把收到的訊息傳送給client1 send( clnt_sd1 , str , strlen(str)+1 , 0 ); } //緩衝時間0.1秒 Sleep( 100 ); } //關閉TCP socket closesocket(sd); closesocket(clnt_sd1); closesocket(clnt_sd2); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```C= #include<stdio.h> #include<winsock.h> #include<time.h> int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n , intv_time; char str[100]; char str1[100]; clock_t start_time , end_time; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 1234 ); //連接至server connect( sd , (struct sockaddr*) &serv , sizeof(serv) ); printf( "Connect to server\n" ); //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; ioctlsocket( sd , FIONBIO , &imode ); printf( "client name: " ); gets( str1 ); printf( "time interval (sec): "); scanf( "%d" , &intv_time ); //緩衝時間5秒 Sleep( 5000 ); end_time = 0; float time; while(1) { start_time = clock(); time = ( start_time - end_time ) / CLOCKS_PER_SEC; //每次間隔時間,傳送一次訊息 if( time > intv_time ) { strcpy( str , str1 ); printf( "client: %s\n" , str ); send( sd , str , strlen(str) , 0 ); end_time = clock(); } //清空記憶體 memset( str , 0 , 100 ); //接收訊息 n = recv( sd , str , 100 , 0 ); //如果有接收到訊息,才會印出訊息內容 if( n > 0 ) { str[n] = '\0'; printf( "other: %s\n" , str ); } //緩衝時間0.1秒 Sleep( 100 ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 應用3. server可服務多個client , 且client可陸續連入 原本的client連至server之後會接收並印出server送來文字,然後server讓任何一個client連入後,開始以間隔1秒方式,不停傳送訊息。但這次的作業應用3是需要把server改成讓client可以隨時連入,並且開始接收文字,還有server最多可以讓5個client連入聊天室,因為這次主要在server的非欄阻部分,所以client可維持攔阻模式 ( client.c程式只有一個,但要開五次也就是模擬有五位client ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://i.imgur.com/aspfVQQ.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:100%;" src="https://i.imgur.com/lOaqfHz.png" /></center> <center> client1 ~ client5 </center> <br> server程式碼: ```C= #include<stdio.h> #include<winsock.h> #define maxclient_num 5 int main() { //set WSA and initial WSAStartup SOCKET sd , clnt_sd[ maxclient_num ]; WSADATA wsadata; struct sockaddr_in serv , clnt; int i , n; char str[100] = "I love NP!"; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 1234 ); //綁定server結構裡設定的資訊 bind( sd , (struct sockaddr*) &serv , sizeof(serv) ); listen( sd , 5 ); //當imode為1的時候,設定為非攔阻模式 u_long imode = 1; ioctlsocket( sd , FIONBIO , &imode ); int len_clnt = sizeof( clnt ); printf( "Server waits...\n" ); //初始化所有的client for( int i = 0 ; i < maxclient_num ; i++ ) { clnt_sd[i] = -1; } //初始化連入聊天室的人數 int curr_client_num = 0; while(1) { n = accept( sd , (struct sockaddr*) &clnt , &len_clnt ); //如果有client要連進聊天室,那就把聊天室的位子給這個client if( n != -1 ) { clnt_sd[ curr_client_num ] = n; printf( "client[%d] is connected\n" , curr_client_num ); curr_client_num++; } //如果聊天室的位子有client的話,就把聊天室的訊息傳給該client for( int i = 0 ; i < maxclient_num ; i++ ) { if( clnt_sd[i] != -1 ) { send( clnt_sd[i] , str , strlen(str)+1 , 0 ); printf( "send to client[%d]: %s\n" , i , str ); } } //緩衝時間1秒 Sleep( 1000 ); } //關閉TCP socket closesocket(sd); for( int i = 0 ; i < maxclient_num ; i++ ) { closesocket( clnt_sd[i] ); } //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```C= #include<stdio.h> #include<winsock.h> int main() { //set WSA and initial WSAStartup SOCKET sd; WSADATA wsadata; struct sockaddr_in serv; int i , n; char str[100] = "I love NP!"; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用TCP協定 sd = socket( AF_INET , SOCK_STREAM , 0 ); //設定server的結構 serv.sin_addr.s_addr = inet_addr( "127.0.0.1" ); serv.sin_family = AF_INET; serv.sin_port = htons( 1234 ); //連接至server connect( sd , (struct sockaddr*) &serv , sizeof(serv) ); printf( "Connect to server\n" ); while(1) { //接收訊息 n = recv( sd , str , 100 , 0 ); //如果沒有接收到訊息,就跳出迴圈 if( n <= 0 ) { break; } printf( "recv: %s\n" , str ); } //關閉TCP socket closesocket(sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` ## 本週心得 這次老師教的課程為非攔阻模式的應用,其實之前就有要等待程式接收訊息的疑問了,因為這樣需要一職等整個程式就會一直卡在那邊不動顯然是不行的,今天老師介紹的非攔阻模式就是要去解決這一個問題,因為有了非攔阻模式可以讓程式不會因為訊息還沒傳送過來就卡死在那邊,反而會先跳過recv()然後繼續執行下面的程式,之後如果有訊息傳送過來以後,再去接收,這樣會讓整個程式變得順暢一點,但是雖然非攔阻模式可以讓程式繼續執行,但是反而對程式來說比較吃資源,效能也有可能會下降,所以必須用sleep()來讓程式有個緩衝,不然這樣對程式來說傷害還大,資源應該會被吃掉,緩衝的時間也不能設定太大與太小,這樣會讓程式的效能變得不平衡,這次老師的教程都是使用TCP的非攔阻模式之應用,都沒有使用UDP的非攔阻模式之應用,希望之後老師可以講解一下UDP的非攔阻模式之應用,謝謝老師這周所教的非攔阻模式的教學影片,我自己收穫很多,也非常期待老師之後的課程與教學,這次的作業比上一次還要少,這樣助教大哥應該會改的比較輕鬆吧XD <br> 關於5/3上課的時候,真的覺得一屆不如一屆,我在下面上課也覺得非常的火大與不應該,這屆大三我覺得還好,但這屆大二的程度與上課態度明顯有下降,自己的作業上台分享也講不出來,那很明顯就不是本人寫的,支持老師與助教嚴格審視,而且作業的完成度與細節都沒有做得很好,程式碼都不給要怎麼讓助教改作業?只貼成果有甚麼用==,有時候覺得ChatGPT是一個雙面刃,是一個很好的工具沒錯,但也會讓學生過度依賴導致學生的程度下降,有些大二同學上課的態度也非常不好,還有什麼翹課去打球還說給老師聽,當下聽到真的蠻傻眼的,如果我是老師也會生氣,非常支持老師與助教變得嚴格一些來治治這些小屁孩 ### 作者: 廖經翰