# 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) (有可能換題目!)