# 網路程式設計 HW13 群播網路程式作業 學生: 1084837廖經翰 ## 練習1. 基本群播程式 這次的作業練習1是利用UDP的群播功能來讓server群播訊息給client,server會群播0~9的數字給client,client會收到這些數字訊息並且輸出訊息內容 ( 可以開啟多個server群播,也可以開啟多個client接收訊息 ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:˙0%;" src="https://hackmd.io/_uploads/HJme57v8n.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:70%;" src="https://hackmd.io/_uploads/S1SDcXP83.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<string.h> #include<winsock.h> #define MAXLINE 20 int main() { struct sockaddr_in serv , clnt; int i = 1 , n; char str[MAXLINE]; //set WSA and initial WSAStartup SOCKET serv_sd; WSADATA wsadata; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用UDP協定 serv_sd = socket( AF_INET , SOCK_DGRAM , 0 ); //開啟群播的參數 int multicastTTL = 1; //設定群播 if( setsockopt(serv_sd , IPPROTO_IP , IP_MULTICAST_TTL , (char*)&multicastTTL , sizeof(multicastTTL)) == SOCKET_ERROR ) { printf( "setsockopt() failed !\n" ); } int clnt_len = sizeof( clnt ); printf( "server will multicast...\n" ); //設定client的結構 clnt.sin_addr.s_addr = inet_addr( "224.1.1.1" ); clnt.sin_family = AF_INET; clnt.sin_port = htons( 5678 ); while( 1 ) { //設定str裡面要傳送的內容 memset( str , i % 10 + '0' , sizeof(str) ); //用群播的方式傳送訊息 sendto( serv_sd , str , strlen(str) , 0 , (LPSOCKADDR)&clnt , clnt_len ); printf( "server multicast: %s\n" , str ); //緩衝時間1秒 Sleep( 1000 ); i++; } //關閉socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<string.h> #include<winsock.h> #define MAXLINE 1024 int main() { //set WSA and initial WSAStartup SOCKET serv_sd; WSADATA wsadata; struct sockaddr_in serv , clnt; int n; char str[MAXLINE]; //開啟群播 int flag = 1 , len = sizeof(int); //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用UDP協定 serv_sd = socket( AF_INET , SOCK_DGRAM , 0 ); //設定群播 if( setsockopt(serv_sd , SOL_SOCKET , SO_REUSEADDR , (const char*)&flag , len) < 0 ) { printf( "setsockopt() error !\n" ); } //設定client的結構 clnt.sin_addr.s_addr = 0; //0表示使用電腦自己的IP clnt.sin_family = AF_INET; clnt.sin_port = htons( 5678 ); //綁定client結構裡設定的資訊 if( bind(serv_sd , (LPSOCKADDR) &clnt , sizeof(clnt)) < 0 ) { printf( "bind() error !\n" ); system( "pause" ); return 0; } //建立一個群播的物件 struct ip_mreq multi_req; //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.1.1.1" ); //填入群播物件的網卡 multi_req.imr_interface.s_addr = htonl(INADDR_ANY); //把剛剛填入資料的群播物件做設定,IP_ADD_MEMBERSHIP為加入群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_ADD_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } int serv_len = sizeof( serv ); while( 1 ) { //接收server傳送過來的訊息 n = recvfrom( serv_sd , str , MAXLINE , 0 , (LPSOCKADDR) &serv , &serv_len ); str[n] = '\0'; printf( "client (from: %s): %s\n" , inet_ntoa(serv.sin_addr) , str ); } //關閉TCP socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system( "pause" ); } ``` ## 練習2. 多頻道 作業練習2是利用UDP的群播功能來讓server廣播訊息給client,server一次會群播3個訊息,也就是數字、小寫英文、大寫英文,但是這3個訊息傳送出去的頻道也就是address不一樣,所以client可以選擇頻道來接收想要的訊息,總共有6個頻道: * 頻道1: 數字頻道,client會收到數字訊息 * 頻道2: 退出數字頻道,這樣client就不會收到數字的訊息 * 頻道3: 小寫英文頻道,client會收到小寫英文訊息 * 頻道4: 退出小寫英文頻道,這樣client就不會收到小寫英文的訊息 * 頻道5: 大寫英文頻道,client會收到大寫英文訊息 * 頻道6: 退出大寫英文頻道,這樣client就不會收到大寫英文的訊息 這樣client就可以選擇頻道來接收想要的資訊,或是退出頻道來選擇不收到該頻道的資訊,並且有加入thread的功能,所以main主要是負責處理切換頻道的部分,而thread主要是負責接收server傳送過來的訊息並且輸出訊息內容,因為有thread幫忙,這樣client就可以隨時切換頻道不會被限制 ( 可以開啟多個server群播,也可以開啟多個client接收訊息 ) 執行畫面: <center><img style="margin-top:auto;border:1px #eee;width:80%;" src="https://hackmd.io/_uploads/HyKF14w82.png" /></center> <center>server</center> <br> <center><img style="margin-top:auto;border:1px #eee;width:80%;" src="https://hackmd.io/_uploads/HkY9yEDLn.png" /></center> <center>client</center> <br> server程式碼: ```c= #include<stdio.h> #include<string.h> #include<winsock.h> #define MAXLINE 20 int main() { struct sockaddr_in serv , clnt; int i = 1 , n; char str[MAXLINE]; //set WSA and initial WSAStartup SOCKET serv_sd; WSADATA wsadata; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , &wsadata ); //使用UDP協定 serv_sd = socket( AF_INET , SOCK_DGRAM , 0 ); //開啟群播的參數 int multicastTTL = 1; //設定群播 if( setsockopt(serv_sd , IPPROTO_IP , IP_MULTICAST_TTL , (char*)&multicastTTL , sizeof(multicastTTL)) == SOCKET_ERROR ) { printf( "setsockopt() failed !\n" ); } int clnt_len = sizeof( clnt ); printf( "server will multicast...\n" ); //設定client的結構 clnt.sin_addr.s_addr = inet_addr( "224.111.1.1" ); clnt.sin_family = AF_INET; clnt.sin_port = htons( 5678 ); while( 1 ) { //頻道1只會顯示數字 clnt.sin_addr.s_addr = inet_addr( "224.111.1.1" ); //設定client的結構 clnt.sin_port = htons( 5678 ); memset( str , i % 10 + '0' , sizeof(str) ); //設定str裡面要傳送的內容 sendto( serv_sd , str , strlen(str) , 0 , (LPSOCKADDR)&clnt , clnt_len ); //用群播的方式傳送訊息 printf( "server multicast: %s\n" , str ); //頻道3只會顯示小寫英文 clnt.sin_addr.s_addr = inet_addr( "224.111.1.2" ); //設定client的結構 clnt.sin_port = htons( 5678 ); memset( str , (i-1) % 26 + 'a' , sizeof(str) ); //設定str裡面要傳送的內容 sendto( serv_sd , str , strlen(str) , 0 , (LPSOCKADDR)&clnt , clnt_len ); //用群播的方式傳送訊息 printf( "server multicast: %s\n" , str ); //頻道6只會顯示大寫英文 clnt.sin_addr.s_addr = inet_addr( "224.111.1.3" ); //設定client的結構 clnt.sin_port = htons( 5678 ); memset( str , (i-1) % 26 + 'A' , sizeof(str) ); //設定str裡面要傳送的內容 sendto( serv_sd , str , strlen(str) , 0 , (LPSOCKADDR)&clnt , clnt_len ); //用群播的方式傳送訊息 printf( "server multicast: %s\n" , str ); //緩衝時間1秒 Sleep( 1000 ); i++; } //關閉socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system("pause"); } ``` client程式碼: ```c= #include<stdio.h> #include<string.h> #include<winsock.h> #define MAXLINE 1024 //global variable SOCKET serv_sd; //set WSA and initial WSAStartup WSADATA wsadata; char str[MAXLINE]; char str_r[MAXLINE]; struct sockaddr_in serv , clnt; int time_out = 50 , n , channel = 1; int flag = 1 , len = sizeof(int); //開啟群播 //負責接收server傳送過來的訊息並且輸出訊息內容 void *recv_messg( void *argu ) { while( 1 ) { int clnt_len = sizeof( clnt ); //接收server傳送過來的訊息 n = recvfrom( serv_sd , str , MAXLINE , 0 , (LPSOCKADDR) &clnt , &clnt_len ); //輸出訊息內容 if( n > 0 ) { printf( "client (from: %s): %s , %d\n" , inet_ntoa(clnt.sin_addr) , str , n ); } } } int main() { int n , i , j , total = 0 , thread_id = 0; //呼叫WSAStartup()註冊WinSock DLL的使用 WSAStartup( 0x101 , (LPWSADATA) &wsadata ); //使用UDP協定 serv_sd = socket( AF_INET , SOCK_DGRAM , 0 ); //設定群播 if( setsockopt(serv_sd , SOL_SOCKET , SO_REUSEADDR , (const char*)&flag , len) < 0 ) { printf( "setsockopt() error !\n" ); } //設定client的結構 serv.sin_addr.s_addr = 0; //0表示使用電腦自己的IP serv.sin_family = AF_INET; serv.sin_port = htons( 5678 ); //綁定client結構裡設定的資訊 if( bind(serv_sd , (LPSOCKADDR) &serv , sizeof(serv)) < 0 ) { printf( "bind() error !\n" ); system( "pause" ); return 0; } int serv_len = sizeof( serv ); //建立一個thread來接收server傳送過來的訊息 CreateThread( NULL , 0 , (LPTHREAD_START_ROUTINE) recv_messg , NULL , 0 , (LPDWORD) &thread_id ); //建立一個群播的物件 struct ip_mreq multi_req; //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.1" ); //填入群播物件的網卡 multi_req.imr_interface.s_addr = htonl(INADDR_ANY); //負責處理切換頻道的部分 while( 1 ) { scanf( "%d" , &channel ); printf( "switch to channel %d\n" , channel ); printf( "\n" ); if( channel == 1 ) //切換頻道1也就是addr:224.111.1.1 接收數字的訊息 { //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.1" ); //把剛剛填入資料的群播物件做設定,IP_ADD_MEMBERSHIP為加入群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_ADD_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } } else if( channel == 2 ) //切換頻道2為退出數字頻道,也就是不接收數字訊息 { //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.1" ); //把剛剛填入資料的群播物件做設定,IP_DROP_MEMBERSHIP為退出群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_DROP_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } } else if( channel == 3 ) //切換頻道3也就是addr:224.111.1.2 接收小寫英文的訊息 { //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.2" ); //把剛剛填入資料的群播物件做設定,IP_ADD_MEMBERSHIP為加入群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_ADD_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } } else if( channel == 4 ) //切換頻道4為退出小寫英文頻道,也就是不接收小寫英文訊息 { //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.2" ); //把剛剛填入資料的群播物件做設定,IP_DROP_MEMBERSHIP為退出群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_DROP_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } } else if( channel == 5 ) //切換頻道5也就是addr:224.111.1.3 接收大寫英文的訊息 { //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.3" ); //把剛剛填入資料的群播物件做設定,IP_ADD_MEMBERSHIP為加入群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_ADD_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } } else if( channel == 6 ) //切換頻道6為退出大寫英文頻道,也就是不接收大寫英文訊息 { //填入群播物件的address也就是頻道 multi_req.imr_multiaddr.s_addr = inet_addr( "224.111.1.3" ); //把剛剛填入資料的群播物件做設定,IP_DROP_MEMBERSHIP為退出群組會員 if( setsockopt(serv_sd , IPPROTO_IP , IP_DROP_MEMBERSHIP , (const char*)&multi_req , sizeof(multi_req)) < 0 ) { printf( "setsockopt() failed !\n" ); } } } //關閉TCP socket closesocket(serv_sd); //結束WinSock DLL的使用 WSACleanup(); system( "pause" ); } ``` ## 本週心得 這次老師教的課程為群播的應用,上一次是講廣播,這次的群播終於來了,但可以發現群播的方式會比廣播的方式還要複雜,不管是架構還是程式碼上都比廣播複雜,但是我非常喜歡群播,因為可以讓client選擇是否要加入群播群組或是退出群播群組,這樣可以讓client更自由的選擇是否要接收這個訊息,但可能程式碼就要有比較多的設定,這次是第13個作業,也就是這門課的最後一個作業了,我覺得經過老師13個作業的洗禮,讓更熟悉網路程式的想法與設計,這門課我非常喜歡因為比較偏向應用的部分,而且也非常重程式,這樣可以讓我學習到很多有關於網路的架構與程式應用,一想到這是最後一個作業也就表示這門課準備要進入尾聲,讓我好捨不得王皓立老師那麼用心的課程與教導,也要準備跟助教說再見了,因為即將畢業,真希望以後的生活可以預見像王皓立老師那麼用心的老師,也希望王皓立老師對教學的用心與熱誠可以一直持續到您退休,您真的是我遇見的老師中,少數對課程與學生那麼用心的老師,不管是個性還是教學態度與對待學生都是非常好的一位老師,好希望以後可以再遇見這樣的老師,這邊我也祝老師與助教,身體健康,萬事如意,學業進步,事事順利!! <br> <center><img style="margin-top:auto;border:1px #eee;width:80%;" src="https://hackmd.io/_uploads/ry__NNwIh.png" /></center> <br> ### 作者: 廖經翰