# 2025q1 Homework5 (assessment)
contributed by < `Cheng5840` >
## 閱讀〈[因為自動飲料機而延畢的那一年](https://blog.opasschang.com/the-story-of-auto-beverage-machine-1)〉的啟發
大多數人害怕失敗,教育更強化此恐懼,導致逃避嘗試,但在追求真理的路上,即使看似徒勞,但只要能再站起,就能抵達任何地方。
這不是傳統的勵志文,而是血淋淋的紀實。專案未商業化,但經驗無價。每一次挫折與掙扎都讓作者更精進,體現真實人生中,成長勝於結果。作者強調,教育應教導學生如何面對失敗,而非逃避,這才是打破惡性循環的出路。
## 遇到的問題
select: select 會輪巡 fd_set 內的 fd,確認是否有 fd 是就緒的(可讀、可寫、exception)
那 select 是何時會 return?
> On Linux, select() may report a socket file descriptor as "ready
for reading", while nevertheless a subsequent read blocks. This
could for example happen when data has arrived but upon
examination has the wrong checksum and is discarded. There may be
other circumstances in which a file descriptor is spuriously
reported as ready. Thus it may be safer to use O_NONBLOCK on
sockets that should not block.
>On Linux, select() also modifies timeout if the call is
interrupted by a signal handler (i.e., the EINTR error return).
This is not permitted by POSIX.1. The Linux pselect() system call
has the same behavior, but the glibc wrapper hides this behavior
by internally copying the timeout to a local variable and passing
that variable to the system call.
TODO: show me the code!
[man page](https://man7.org/linux/man-pages/man2/select.2.html)說明的問題是甚麼還不了解
socket 甚麼時候會有 EAGAIN, EINTR 錯誤? 要如何解決?
----
如何區分 request 是來自 curl 還是 browser?
:::spoiler
```clike
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/select.h>
#include <dirent.h>
#include <sys/stat.h>
#define SERVER_PORT 1234
#define BUF_SIZE 1024
#define MAX_CLIENTS 100
void print_fd_set(fd_set *fds, int max_fd) {
printf("fd_set contents (up to %d):\n", max_fd);
for (int i = 0; i <= max_fd; i++) {
if (FD_ISSET(i, fds)) {
printf("fd %d: 1, ", i);
} else {
printf("fd %d: 0, ", i);
}
}
printf("\n\n");
printf("-----------------------------------------------------------------\n");
}
typedef struct {
int fd;
char ip[INET_ADDRSTRLEN];
int port;
} client_info_t;
int main() {
// Create a socket descriptor
int server_fd;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
return -1;
}
// Eliminates "Address already in use" error from bind
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) != 0) {
perror("Error setting SO_REUSEADDR on socket");
close(server_fd);
return -1;
}
// Set up server address
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// Bind socket
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
close(server_fd);
return -1;
}
// Listen for connections
if (listen(server_fd, SOMAXCONN) < 0) {
perror("Listen failed");
close(server_fd);
return -1;
}
printf("伺服器已啟動,等待連接...\n");
// Initialize client array and file descriptor sets
client_info_t clients[MAX_CLIENTS];
for (int i = 0; i < MAX_CLIENTS; i++) {
clients[i].fd = -1; // Mark as unused
}
fd_set read_fds; // read_fds represent a set of file descriptors
int max_fd = server_fd; // init the highest-numbered file descriptor to server_fd
print_fd_set(&read_fds, 31);
while (1) {
// Clear and set file descriptor sets
FD_ZERO(&read_fds); // Clear
printf("FD_ZERO: ");
print_fd_set(&read_fds, max_fd);
FD_SET(server_fd, &read_fds); // pull up the server_fd(index) bit in read_fds
printf("FD_SET server_fd: ");
print_fd_set(&read_fds, max_fd);
// Add active client sockets to read_fds
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd != -1) {
FD_SET(clients[i].fd, &read_fds);
if (clients[i].fd > max_fd) {
max_fd = clients[i].fd;
}
}
}
printf("FD_SET client_fd: ");
print_fd_set(&read_fds, max_fd);
// Use select to monitor sockets
if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) < 0) { // max_fd + 1 -> nfds
perror("Select failed");
continue;
}
printf("After select: ");
print_fd_set(&read_fds, max_fd);
// Check for new connection
if (FD_ISSET(server_fd, &read_fds)) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("Accept failed");
continue;
}
printf("After accept client: ");
print_fd_set(&read_fds, max_fd);
// Get client socket information
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
int client_port = ntohs(client_addr.sin_port);
printf("客戶端 %s:%d 已連接\n", client_ip, client_port);
// Find an empty slot for the new client
int i;
for (i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd == -1) {
clients[i].fd = client_fd;
strncpy(clients[i].ip, client_ip, INET_ADDRSTRLEN);
clients[i].port = client_port;
break;
}
}
if (i == MAX_CLIENTS) {
printf("太多客戶端,拒絕連接\n");
close(client_fd);
} else if (client_fd > max_fd) {
max_fd = client_fd;
}
}
// Check for data from clients
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].fd != -1 && FD_ISSET(clients[i].fd, &read_fds)) {
char buffer[BUF_SIZE];
ssize_t bytes_received = recv(clients[i].fd, buffer, BUF_SIZE - 1, 0);
if (bytes_received <= 0) {
if (bytes_received < 0) {
perror("Connection error");
} else {
printf("客戶端 %s:%d 已斷開連線\n", clients[i].ip, clients[i].port);
}
close(clients[i].fd);
clients[i].fd = -1;
continue;
}
buffer[bytes_received] = '\0';
printf("收到來自 %s:%d 的訊息: %s\n", clients[i].ip, clients[i].port, buffer);
// Send echo message back to client
if (send(clients[i].fd, buffer, bytes_received, 0) < 0) {
perror("Connection error");
close(clients[i].fd);
clients[i].fd = -1;
continue;
}
printf("訊息已送出...\n");
print_fd_set(&read_fds, max_fd);
}
}
}
// Cleanup
close(server_fd);
printf("伺服器關閉\n");
return 0;
}
```
:::
```bash
$ ./server_select
伺服器已啟動,等待連接...
fd_set contents (up to 31):
fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1, fd 4: 1, fd 5: 1, fd 6: 0, fd 7: 0, fd 8: 0, fd 9: 0, fd 10: 0, fd 11: 0, fd 12: 1, fd 13: 0, fd 14: 0, fd 15: 0, fd 16: 0, fd 17: 0, fd 18: 0, fd 19: 0, fd 20: 0, fd 21: 0, fd 22: 0, fd 23: 0, fd 24: 0, fd 25: 0, fd 26: 0, fd 27: 0, fd 28: 0, fd 29: 0, fd 30: 0, fd 31: 0,
-----------------------------------------------------------------
FD_ZERO: fd_set contents (up to 3):
fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 0,
-----------------------------------------------------------------
FD_SET server_fd: fd_set contents (up to 3):
fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1,
-----------------------------------------------------------------
FD_SET client_fd: fd_set contents (up to 3):
fd 0: 0, fd 1: 0, fd 2: 0, fd 3: 1,
-----------------------------------------------------------------
```
## 想投入的專案
>參考 [Linux 專題: 透過 Netfilter 自動過濾廣告](https://hackmd.io/@sysprog/BJb0NRYH3)
## TODO (一對一討論)
- Homework5
- select 是 level-triggered (LT)
https://man7.org/linux/man-pages/man2/open.2.html “when possible” (裝置或檔案系統允許時) O_NONBLOCK or O_NDELAY (二者指同一設定)
When possible, the file is opened in nonblocking mode.
Neither the open() nor any subsequent I/O operations on the
file descriptor which is returned will cause the calling
process to wait.
- 注意:
Note that the setting of this flag has no effect on the
operation of poll(2), select(2), epoll(7), and similar,
since those interfaces merely inform the caller about
whether a file descriptor is "ready", meaning that an I/O
operation performed on the file descriptor with the
O_NONBLOCK flag clear would not block.
- io_uring 機制 (Linux v5.1+ 才有真正的 AIO) ⇒類似 CMWQ: https://hackmd.io/@sysprog/linux2025-ktcp
- TX, RX => https://man7.org/linux/man-pages/man7/socket.7.html
(注意要選 (7) )
https://man7.org/linux/man-pages/man3/errno.3.html
- 看 ktcp 的描述 netstat
- TLS 的作用和交握過程
待辦事項
- TODO: 重新執行 https://hackmd.io/@sysprog/BJb0NRYH3 並紀錄運作過程的問題 (eBPF, TLS)
(有可能換題目!)