# 網路程式設計第13周
## 觀念題
### 第一題: 抓封包
- 請仿照本週影片31:03中,找出練習1的broadcast server送出的廣播封包,其來源與目的port為多少。
:::info
**答案**

- 從上圖可以看到是從65511送到5678
- 也就是說
- Server: 65511
- Client: 5678
:::
### 第二題: 連續開兩個
- 在練習1中,可以同時執行兩個server嗎?效果有何不同?
:::info
**答案**

- 我故意錯開跑
- 他其實就會收到兩個不同來源端的封包
- 如果抓封包看一下

- 是真的有兩個不同來源端,~~等於是一個頻道雙倍封包,賺翻了~~
- 原因是server並無綁定port,因此不會有錯誤產生
:::
- 在練習1中,可以同時執行兩個client嗎?原因為何?
:::info
**答案**

- 恭喜bind error
- 可以看到會有一個能順利跑,但另外一個會bind error
- 原因在於Client是需要一個綁定port的動作,因此當主機沒有該port給你,就不能使用,因此產生bind error
- 但是我們可以用兩台電腦進行連線,這樣可以喔
- 以這裡的實驗來說,我使用虛擬機,連接VLAN是可以作用的,因此這邊是無法在同一台主機使用,但不同台主機是OK的

:::
### 第三題: 程式的比較
- 練習2的broadcast server與練習5的broadcast server有何不同?請使用wireshark抓取所送出的封包,驗証你的答案。
:::info
**答案**
- 練習二中是透過同一個port傳送不同類型的資料給client
- 也因此其實客戶端會收到他不要的資料,~~簡稱垃圾~~
- 也因此程式必須要偷偷peek封包內容再去過濾

- 由上圖可以看到封包的來源都是相同的啾咪
- 練習五中是透過不同的port傳送不同類型的資料給client
- 也因此客戶端必須要手動調整port,再去接收
- 也因此每次一調整就要close掉再去bind一次

- 由上圖可以看到封包的來源都是不同的
:::
## 實作題
- 請修改練習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")
```
:::
- 執行結果

## 心得
這周學了廣播程式,如果說protocol是人與人溝通的方式,那感覺廣播程式很像一個人在路上大叫給全部人聽的東西,雖然感覺很方便,但要謹慎使用,否則會引來廣播風暴。
話說作業感覺根本是上一周的延伸,利用主程式調整頻道,多序的副程式負責收,所以又重新複習了上周,不錯不錯。
## 附錄
[全部的程式碼](https://github.com/kevin0920911/NetworkGIF/tree/main/w13)