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