# [2020q3](http://wiki.csie.ncku.edu.tw/sysprog/schedule) 第 18 週測驗題 ###### tags: `sysprog2020` :::info 目的: 引導學員學習 regular expression 及 socket 程式設計 ::: ==[作答表單](https://docs.google.com/forms/d/e/1FAIpQLSePkfc8lQ4GSc_BjY31cYqbqHF2dzspprMgEAb6ZOQZAKcDnA/viewform)== --- ### 測驗 `1` LeetCode [10. Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/) 要求判定給定兩個字串 `s` 與 `p` 是否匹配 (match),要考慮到特殊符號 `.` 與 `*`: * 符號 `.` 可對應各種字元 * 符號 `*` 代表前方字元可使用各種數量 (包含 0 個),例如 `p = "a*";`,則無論 `s` 為空字串或者任意個 `'a'` 組成,均可傳回 `true` 比對樣式的每個字元作為一個狀態 (包含 `.` 任何字元),串接為成單一路線的狀態,最後是結束狀態。 ![](https://i.imgur.com/dzE0eGb.png) > $\uparrow$ `ab.d` 所建立的狀態機 輸入字串從起始狀態開始判斷,沿著路線走到結束狀態,每個狀態從輸入字串取一個字元跟該狀態的字元比較,若不匹配則 `isMatch` 直接判斷失敗,若符合則消耗該字元,把剩下的輸入字串丟到下一個狀態繼續比較。若成功走到結束狀態,且輸入字串已全部被消耗完,則 `isMatch` 判斷成功 ![](https://i.imgur.com/H7WGnik.png) > $\uparrow$ 狀態機消耗輸入字串 s 的示意圖,只有在最上面的狀況,消耗所有的輸入字串字元,才算 `isMatch` = true 考慮到 `*` 符號,包含零個或是多個字元,在這個狀態中不一定只消耗單一字元,因此需要特別處理,根據可輸入字串的資料,消耗一個到多個該狀態處理的字元,剩下的輸入字串移送到下個狀態再比較。 ![](https://i.imgur.com/4Qfwn6Q.png) > $\uparrow$ 考慮 `*` 符號的特別處理,進入到 'c' 狀態的輸入字串為 'ccc',考慮消耗各種可能,將 'ccc', 'cc', 'c', 及 '∅' 移送到下個狀態進行判斷 以下程式碼為 LeetCode [10. Regular Expression Matching](https://leetcode.com/problems/regular-expression-matching/) 的一個可能實作: ```cpp #include <stdbool.h> bool isMatch(char *s, char *p) { if (!p || !*p) return !s || !*s; if (!s || !*s) { char *str = p; while (*str && str[1]) { if (str[1] != '*') return false; str += 2; } return !*str; } if (p[1] == '*') return isMatch(s, p + 2) || p && *p && (s[0] == p[0] || p[0] == '.') && isMatch(s + 1, p); return p && *p && (s[0] == p[0] || p[0] == '.') && isMatch(s + 1, p + 1); } ``` 透過動態規劃,重寫為不需要遞迴呼叫的實作: ```cpp #include <stdbool.h> bool isMatch(char *s, char *p) { if (!p || !*p) return !s || !*s; int len = strlen(s); bool m[len + 1]; memset(m, 0, sizeof(m)); m[0] = true; for (; *p; p++) { if (*(p + 1) == '*') continue; if (*p == '*') { char c = *(p - 1); for (int i = 1; i <= len; i++) m[i] = EXP1; } else { char c = *p; for (int i = len; i > 0; i--) m[i] = EXP2; m[0] = false; } } return m[len]; } ``` 請補完程式碼,使得其執行符合 LeetCode 預期並通過給定的測試資料。 ==作答區== EXP1 = ? * `(a)` `m[i] && (s[i] == c || c == '.')` * `(b)` `m[i - 1] && (s[i - 1] == c || c == '.')` * `(c)` `m[i - 1] && (s[i] == c || c == '.')` * `(d)` `m[i] || (m[i - 1] && (s[i - 1] == c || c == '.'))` EXP2 = ? * `(a)` `m[i] && (s[i] == c || c == '.')` * `(b)` `m[i - 1] && (s[i - 1] == c || c == '.')` * `(c)` `m[i - 1] && (s[i] == c || c == '.')` * `(d)` `m[i] || (m[i - 1] && (s[i - 1] == c || c == '.'))` --- ### 測驗 `2` 請詳閱 LeetCode [468. Validate IP Address](https://leetcode.com/problems/validate-ip-address/) 題目要求,再考慮以下可能的實作: ```cpp #include <ctype.h> #include <stdlib.h> static int checkIPversion(char *s) { for (int count = 0; *s; s++) { if (*s == '.') return 4; if (*s == ':') return 6; if (++count >= 5) break; } return -1; } static bool checkV4(char *IP) { int size = 0; char *walker = IP, *runner = IP; while (*runner) { if (runner - walker > 3) return false; if (*runner != '.' && !isdigit(*runner)) return false; if (*runner == '.') { if (walker == runner) return false; *runner = '\0'; int tmp = atoi(walker); if (tmp < 0 || tmp > 255) return false; if (runner - walker > 1 && *walker == '0') return false; size++; walker = ++runner; if (size > 3) return false; } else ++runner; } if (walker == runner) return false; int tmp = atoi(walker); if (tmp < 0 || tmp > 255) return false; if (runner - walker > 1 && *walker == '0') return false; return size == 3; } static bool checkV6(char *IP) { int size = 0; char *walker = IP, *runner = IP; while (*runner) { if (COND1) return false; *runner = toupper(*runner); if (*runner != ':' && (!isdigit(*runner) && (*runner < 'A' || *runner > 'F'))) return false; if (*runner == ':') { if (walker == runner) return false; size++; walker = ++runner; if (COND2) return false; } else ++runner; } return walker != runner && COND3 && COND4; } char *validIPAddress(char *IP) { switch (checkIPversion(IP)) { case 4: if (checkV4(IP)) return "IPv4"; case 6: if (checkV6(IP)) return "IPv6"; } return "Neither"; } ``` 請補完程式碼,使得執行符合 LeetCode 預期並通過給定的測試資料。 ==作答區== COND1 = * `(a)` `runner - walker > 4` * `(b)` `runner - walker > 5` * `(c)` `runner - walker > 6` * `(d)` `runner - walker > 7` COND2 = * `(a)` `size > 4` * `(b)` `size > 5` * `(c)` `size > 6` * `(d)` `size > 7` COND3 = * `(a)` `runner - walker <= 4` * `(b)` `runner - walker <= 5` * `(c)` `runner - walker <= 6` * `(d)` `runner - walker <= 7` COND4 = ? * `(a)` `size == 4` * `(b)` `size == 5` * `(c)` `size == 6` * `(d)` `size == 7` :::success 延伸題目: * 閱讀 [Leetcode Challenge: Validate IP Address](https://medium.com/@ryanyang1221/leetcode-challenge-validate-ip-address-6-16-d9e1f368b8ed),理解 regular expression 的實作方式 * 不使用 regex,實作更有效率的 C 程式碼 ::: --- ### 測驗 `3` 給定檔案 `index.html`,其內容如下: ```htmlmixed <html><head></head><body><h1> HELLO WORLD </h1></body></html> ``` 今有一個 Linux 環境之下的 C 程式 (`server.c`) 可讀取 `index.html` 的內容,並傾聽 port 55688,待網頁瀏覽器連線後,顯示 `index.html` 的內容。 程式碼如下: ```cpp #include <errno.h> #include <fcntl.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> #include <sys/sendfile.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #define PORT "55688" #define MAX_EVENTS 5 static int lfd, efd; static struct epoll_event events[MAX_EVENTS]; static void set_socket_non_blocking(int sfd); static char buf[512]; static void send_response(int sfd) { int cfd = open("index.html", O_RDONLY, 0777); if (cfd < 0) perror("open:"); struct stat stats_buf; int byte_to_send = fstat(cfd, &stats_buf); if (byte_to_send < 0) perror("fstats"); char good_response[1024] = {"HTTP/1.1 200 OK\r\n\r\n"}; send(sfd, good_response, strlen(good_response), 0); int ret = sendfile(sfd, cfd, NULL, stats_buf.st_size); if (ret < 0) perror("send file/response\r\n"); close(sfd); } static void handle_client(int cfd) { int count = read(cfd, buf, 512); if (count < 0) perror("Nothing to read"); struct epoll_event event = {.data.fd = cfd, .events = EPOLLOUT | EPOLLET}; if (epoll_ctl(efd, EPOLL_CTL_MOD, cfd, &event) < 0) perror("epoll_ctl"); } static void accept_connection() { struct sockaddr addr; socklen_t addr_len; int conn_fd = accept(lfd, &addr, &addr_len); if (conn_fd < 0) perror("accept connection"); set_socket_non_blocking(conn_fd); struct epoll_event event = {.data.fd = conn_fd, .events = EPOLLIN | EPOLLET}; if (epoll_ctl(efd, EPOLL_CTL_ADD, conn_fd, &event) < 0) { close(conn_fd); perror("epoll_ctl"); } } void serve() { memset(&events, 0, MAX_EVENTS * sizeof(struct epoll_event)); int evt_list = epoll_wait(efd, events, MAX_EVENTS, -1); if (evt_list < 0) { fprintf(stdin, "EPOLL WAIT: efd %d %s\n", evt_list, strerror(errno)); abort(); } for (int i = 0; i < evt_list; i++) { if (events[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) { printf("event [%d]: errno %d\n", i, events[i].events); close(events[i].data.fd); perror("closing socket"); } else { if (events[i].data.fd == lfd) ACT1; else if (events[i].events & EPOLLIN) ACT2; else ACT3; } } } static void set_socket_non_blocking(int sfd) { int flags = fcntl(sfd, F_GETFL); if (flags < 0) perror("fcntl"); flags |= O_NONBLOCK; if (fcntl(sfd, F_SETFL, flags) < 0) perror("fcntl"); } static int create_socket_listener(char *port) { struct addrinfo hints, *result, *r_ptr; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = 0; hints.ai_flags = AI_PASSIVE; int s = getaddrinfo(NULL, port, &hints, &result); if (s != 0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(EXIT_FAILURE); } int sfd; for (r_ptr = result; r_ptr != NULL; r_ptr = r_ptr->ai_next) { sfd = socket(r_ptr->ai_family, r_ptr->ai_socktype, r_ptr->ai_protocol); if (sfd < 0) continue; s = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &s, sizeof(int)); if (s < 0) { perror("setsockopt"); exit(EXIT_FAILURE); } if (bind(sfd, r_ptr->ai_addr, r_ptr->ai_addrlen) == 0) break; close(sfd); } if (!r_ptr) { perror("Could not bind\n"); freeaddrinfo(result); exit(EXIT_FAILURE); } freeaddrinfo(result); return sfd; } int main() { lfd = create_socket_listener(PORT); set_socket_non_blocking(lfd); /* SOMAXCONN = 128 backlogs */ if (listen(lfd, SOMAXCONN) < 0) { perror("listen"); exit(EXIT_FAILURE); } if ((efd = epoll_create(NUM)) < 0) { close(lfd); perror("epoll_create"); } struct epoll_event event = {.data.fd = lfd, .events = EPOLLIN | EPOLLET}; if (epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &event) < 0) { close(lfd); perror("epoll_ctl add"); exit(EXIT_FAILURE); } for (;;) serve(); return 0; } ``` 請補完程式碼。 ==作答區== ACT1 = ? * `(a)` `send_response(events[i].data.fd)` * `(b)` `accept_connection()` * `(c)` `handle_client(events[i].data.fd)` ACT2 = ? * `(a)` `send_response(events[i].data.fd)` * `(b)` `accept_connection()` * `(c)` `handle_client(events[i].data.fd)` ACT3 = ? * `(a)` `send_response(events[i].data.fd)` * `(b)` `accept_connection()` * `(c)` `handle_client(events[i].data.fd)` NUM = ? * `(a)` `1` * `(b)` `0` * `(c)` `-1` ---