---
# System prepended metadata

title: net w13
tags: [network]

---

# 網路程式設計第13周
## 觀念題
### 第一題: 抓封包
- 請仿照本週影片31:03中，找出練習1的broadcast server送出的廣播封包，其來源與目的port為多少。
    :::info
    **答案**
    ![image](https://hackmd.io/_uploads/Bks6S56X0.png)
    - 從上圖可以看到是從65511送到5678
    - 也就是說
        - Server: 65511
        - Client: 5678
    :::
### 第二題: 連續開兩個
- 在練習1中，可以同時執行兩個server嗎？效果有何不同？
    :::info
    **答案**
    ![image](https://hackmd.io/_uploads/By8yYqTm0.png)
    - 我故意錯開跑
        - 他其實就會收到兩個不同來源端的封包
    - 如果抓封包看一下
        ![image](https://hackmd.io/_uploads/BkQFFq6QC.png)
        - 是真的有兩個不同來源端，~~等於是一個頻道雙倍封包，賺翻了~~
    - 原因是server並無綁定port，因此不會有錯誤產生
    :::
- 在練習1中，可以同時執行兩個client嗎？原因為何？
    :::info
    **答案**
    ![image](https://hackmd.io/_uploads/SJWMqcT7C.png)
    - 恭喜bind error
        - 可以看到會有一個能順利跑，但另外一個會bind error
    - 原因在於Client是需要一個綁定port的動作，因此當主機沒有該port給你，就不能使用，因此產生bind error
        - 但是我們可以用兩台電腦進行連線，這樣可以喔
            - 以這裡的實驗來說，我使用虛擬機，連接VLAN是可以作用的，因此這邊是無法在同一台主機使用，但不同台主機是OK的
            ![image](https://hackmd.io/_uploads/Sy0dFfRQA.png)
    :::
### 第三題: 程式的比較
- 練習2的broadcast server與練習5的broadcast server有何不同？請使用wireshark抓取所送出的封包，驗証你的答案。
:::info
**答案**
- 練習二中是透過同一個port傳送不同類型的資料給client
    - 也因此其實客戶端會收到他不要的資料，~~簡稱垃圾~~
    - 也因此程式必須要偷偷peek封包內容再去過濾
    ![image](https://hackmd.io/_uploads/H1KYh5p70.png)
    - 由上圖可以看到封包的來源都是相同的啾咪
- 練習五中是透過不同的port傳送不同類型的資料給client
    - 也因此客戶端必須要手動調整port，再去接收
    - 也因此每次一調整就要close掉再去bind一次
   ![image](https://hackmd.io/_uploads/S1q-R5p7R.png)
    - 由上圖可以看到封包的來源都是不同的
:::
## 實作題
- 請修改練習5的broadcast server與client程式，新增「廣播頻道清單」功能。
    - server保留原本的傳送外，另增加持續在port 8888傳送「廣播頻道清單」，
    - 清單內容為各頻道傳送的port number，
    - 例如：5678/5679/5680，就代表三個頻道的port number分別為5678,5679,5680。
    - client在執行後，則會先去聽port 8888，取得頻道清單，知道要轉台頻道對應的port number。
    - 再等待使用者輸入頻道號碼，就會切換到該頻道。
- 程式碼
    - Server
        :::spoiler C
        ```C=
        #include <stdio.h>
        #include <string.h>
        #include <winsock.h>

        #define MAXLINE 50
        const int PORT[] = {8888, 5678, 5679, 5680};
        int main(){
            // Initialize Winsock
            WSADATA wsaData;
            WSAStartup(0x101, &wsaData);

            /*
                Server information
                    BOARDSERVER
                        PORT:
                            8888 (list of boards)
                            5678 (channel 1)
                            5679 (channel 2)
                            5680 (channel 3)
            */
            SOCKET server = socket(AF_INET, SOCK_DGRAM, 0);
            BOOL broadcast = TRUE;
            if (setsockopt(server, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast, sizeof(broadcast)) == -1){
                printf("Error in setting Broadcast option");
                closesocket(server);
                WSACleanup();
                return 0;
            }

            /*
                For Client Information
                    *In this case, we use UDP => so we need to use sento() function
            */
            struct sockaddr_in clientAddr;
            clientAddr.sin_family = AF_INET;
            clientAddr.sin_addr.s_addr = inet_addr("255.255.255.255");
            while (TRUE){
                // SCOPE::PORT 8888
                {    
                    clientAddr.sin_port = htons(PORT[0]);
                    /*
                        list of boards
                    */
                    char message[MAXLINE]="5678/5679/5680";
                    sendto(server, message, strlen(message), 0, (LPSOCKADDR)&clientAddr, sizeof(clientAddr));
                    printf("Sent (%d): %s\n",PORT[0], message);
                }

                // SOCPE::PORT 5678
                {
                    clientAddr.sin_port = htons(PORT[1]);
                    /*
                        channel 1
                    */
                    char message[MAXLINE]="THIS IS 5678";
                    sendto(server, message, strlen(message), 0, (LPSOCKADDR)&clientAddr, sizeof(clientAddr));
                    printf("Sent (%d): %s\n",PORT[1], message);
                }

                // SCOPE::PORT 5679
                {
                    clientAddr.sin_port = htons(PORT[2]);
                    /*
                        channel 2
                    */
                    char message[MAXLINE]="THIS IS 5679";
                    sendto(server, message, strlen(message), 0, (LPSOCKADDR)&clientAddr, sizeof(clientAddr));
                    printf("Sent (%d): %s\n",PORT[2], message);
                }

                // SOCPE::PORT 5680
                {
                    clientAddr.sin_port = htons(PORT[3]);
                    /*
                        channel 3
                    */
                    char message[MAXLINE]="THIS IS 5680";
                    sendto(server, message, strlen(message), 0, (LPSOCKADDR)&clientAddr, sizeof(clientAddr));
                    printf("Sent (%d): %s\n",PORT[3], message);
                }
                Sleep(500);
            }
            closesocket(server);
            WSACleanup();
        }
        ```
        :::
        :::spoiler Python
        ```Python=
        import socket
        import time
        PORT = [8888, 5678, 5679, 5680]

        serversocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)

        clientIP, clientPort = "255.255.255.255", 8888
        while True:
            # PORT: 8888
            clientPort = PORT[0]
            message = "5678/5679/5680"
            serversocket.sendto(message.encode(), (clientIP, clientPort))
            print(f"Sent to {clientIP}:{clientPort}: {message}")

            # PORT: 5678
            clientPort = PORT[1]
            message = "THIS IS 5678"
            serversocket.sendto(message.encode(), (clientIP, clientPort))
            print(f"Sent to {clientIP}:{clientPort}: {message}")

            # PORT: 5679
            clientPort = PORT[2]
            message = "THIS IS 5679"
            serversocket.sendto(message.encode(), (clientIP, clientPort))
            print(f"Sent to {clientIP}:{clientPort}: {message}")

            # PORT: 5680
            clientPort = PORT[3]
            message = "THIS IS 5680"
            serversocket.sendto(message.encode(), (clientIP, clientPort))
            print(f"Sent to {clientIP}:{clientPort}: {message}")

            time.sleep(0.5)
        close(serversocket)
        ```
        :::
    - Client
        :::spoiler C
        ```C=
        #include <stdio.h>
        #include <string.h>
        #include <winsock.h>

        #define MAXLINE 50
        const int PORT[] = {8888, 5678, 5679, 5680};
        struct sockaddr_in serverAddr;
        SOCKET sd;
        void recvThread();

        int main(){
            // Initialize Winsock
            WSADATA wsaData;
            WSAStartup(0x101, &wsaData);

            /*
                Crete Socket connect(UDP) to server
            */
            sd = socket(AF_INET, SOCK_DGRAM, 0);
            BOOL broadcast = TRUE;
            if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast, sizeof(broadcast)) == -1){
                printf("Error in setting Broadcast option");
                closesocket(sd);
                WSACleanup();
                return 0;
            }

            /*
                Server Information
                    BOARDSERVER
                        PORT:
                            8888 (list of boards)
                            5678 (channel 1)
                            5679 (channel 2)
                            5680 (channel 3)
            */
            serverAddr.sin_family = AF_INET;
            serverAddr.sin_addr.s_addr = 0;
            serverAddr.sin_port = htons(PORT[0]);
            if (bind(sd, (LPSOCKADDR)&serverAddr, sizeof(serverAddr)) == -1){
                printf("Error in binding");
                closesocket(sd);
                WSACleanup();
                return 0;
            }
            CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)recvThread,NULL, 0, NULL);
            int port = 8888; 
            while(scanf("%d", &port)){
                closesocket(sd);
                sd = socket(AF_INET, SOCK_DGRAM, 0);
                if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast, sizeof(broadcast)) == -1){
                    printf("Error in setting Broadcast option");
                    closesocket(sd);
                    WSACleanup();
                    return 0;
                }

                switch (port)
                {
                case 5678:
                    printf("SWITCHING TO PORT %d\n", port);
                    serverAddr.sin_port = htons(PORT[1]);
                    if (bind(sd, (LPSOCKADDR)&serverAddr, sizeof(serverAddr)) == -1){
                        printf("Error in binding");
                        closesocket(sd);
                        WSACleanup();
                        return 0;
                    }
                    break;
                case 5679:
                    printf("SWITCHING TO PORT %d\n", port);
                    serverAddr.sin_port = htons(PORT[2]);
                    if (bind(sd, (LPSOCKADDR)&serverAddr, sizeof(serverAddr)) == -1){
                        printf("Error in binding");
                        closesocket(sd);
                        WSACleanup();
                        return 0;
                    }
                    break;
                case 5680:
                    printf("SWITCHING TO PORT %d\n", port);
                    serverAddr.sin_port = htons(PORT[3]);
                    if (bind(sd, (LPSOCKADDR)&serverAddr, sizeof(serverAddr)) == -1){
                        printf("Error in binding");
                        closesocket(sd);
                        WSACleanup();
                        return 0;
                    }
                    break;
                default:
                    printf("INVALID PORT\n");
                    printf("Switching to PORT 8888\n");
                    serverAddr.sin_port = htons(PORT[0]);
                    if (bind(sd, (LPSOCKADDR)&serverAddr, sizeof(serverAddr)) == -1){
                        printf("Error in binding");
                        closesocket(sd);
                        WSACleanup();
                        return 0;
                    }
                    break;
                }
            }
        }

        void recvThread(){
            int serverAddrLen = sizeof(serverAddr);
            char message[MAXLINE];
            while (TRUE){
                int n = recvfrom(sd, message, MAXLINE, 0, (LPSOCKADDR)&serverAddr, &serverAddrLen);
                if (n < 0) continue;
                message[n] = '\0';
                printf("Received: %s\n", message);
                Sleep(500);
            }
        }
        ```
        :::
        :::spoiler Python
        ```Python=
        import socket 
        import threading

        PORT = [8888, 5678, 5679, 5680]
        serverAddr, serverPort = "0.0.0.0" ,8888
        sd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

        def recvData():
            global serverPort
            global sd
            while True:
                data, addr = sd.recvfrom(1024)
                print(f"Received from {addr}: {data.decode()}")

        setsockopt = sd.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
        sd.bind((serverAddr, serverPort))
        print(sd)
        threading.Thread(target=recvData).start()
        while True:
            port = int(input(""))
            sd = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sd.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
            if port not in PORT:
                serverPort = 8888
                sd.bind((serverAddr, serverPort))
                print("Invalid port")
                continue
            elif port == 8888:
                serverPort = 8888
                sd.bind((serverAddr, serverPort))
                print("Switched to 8888")
            elif port == 5678:
                serverPort = 5678
                sd.bind((serverAddr, serverPort))
                print("Switched to 5678")
            elif port == 5679:
                serverPort = 5679
                sd.bind((serverAddr, serverPort))
                print("Switched to 5679")
            elif port == 5680:
                serverPort = 5680
                sd.bind((serverAddr, serverPort))
                print("Switched to 5680")
        ```
        :::
- 執行結果
    ![](https://github.com/kevin0920911/NetworkGIF/blob/main/w13/hw1.gif?raw=true)
## 心得
這周學了廣播程式，如果說protocol是人與人溝通的方式，那感覺廣播程式很像一個人在路上大叫給全部人聽的東西，雖然感覺很方便，但要謹慎使用，否則會引來廣播風暴。

話說作業感覺根本是上一周的延伸，利用主程式調整頻道，多序的副程式負責收，所以又重新複習了上周，不錯不錯。

## 附錄
[全部的程式碼](https://github.com/kevin0920911/NetworkGIF/tree/main/w13)