# 網路程式設計 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>
### 作者: 廖經翰