---
# System prepended metadata

title: 網路程式設計 HW13 群播網路程式作業

---

# 網路程式設計 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>

### 作者: 廖經翰