Created
June 21, 2025 19:01
-
-
Save masakielastic/72915525527c88fa78313e7a8421f904 to your computer and use it in GitHub Desktop.
poll で HTTP/1 サーバー
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <unistd.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <poll.h> | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #define PORT 8080 | |
| #define MAX_CLIENTS 100 | |
| #define BUFFER_SIZE 4096 | |
| // HTTPレスポンス用のテンプレート | |
| const char *http_response_template = | |
| "HTTP/1.1 200 OK\r\n" | |
| "Content-Type: text/html\r\n" | |
| "Content-Length: %d\r\n" | |
| "Connection: close\r\n" | |
| "\r\n" | |
| "%s"; | |
| const char *html_content = | |
| "<html><head><title>HTTP Server</title></head>" | |
| "<body><h1>Hello from C HTTP Server!</h1>" | |
| "<p>This server uses poll() for I/O multiplexing.</p></body></html>"; | |
| // ソケットを非ブロッキングモードに設定 | |
| int set_nonblocking(int fd) { | |
| int flags = fcntl(fd, F_GETFL, 0); | |
| if (flags == -1) { | |
| perror("fcntl F_GETFL"); | |
| return -1; | |
| } | |
| if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { | |
| perror("fcntl F_SETFL"); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| // HTTPリクエストを処理してレスポンスを送信 | |
| void handle_http_request(int client_fd, char *request) { | |
| char response[BUFFER_SIZE]; | |
| int content_length = strlen(html_content); | |
| // HTTPレスポンスを作成 | |
| int response_length = snprintf(response, sizeof(response), | |
| http_response_template, | |
| content_length, html_content); | |
| // レスポンスを送信 | |
| send(client_fd, response, response_length, 0); | |
| printf("Sent response to client (fd: %d)\n", client_fd); | |
| } | |
| // リスニングソケットを作成・設定 | |
| int create_server_socket(int port) { | |
| int server_fd; | |
| struct sockaddr_in address; | |
| int opt = 1; | |
| // ソケット作成 | |
| if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { | |
| perror("socket failed"); | |
| return -1; | |
| } | |
| // SO_REUSEADDRオプションを設定 | |
| if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, | |
| &opt, sizeof(opt)) < 0) { | |
| perror("setsockopt failed"); | |
| close(server_fd); | |
| return -1; | |
| } | |
| // アドレス設定 | |
| address.sin_family = AF_INET; | |
| address.sin_addr.s_addr = INADDR_ANY; | |
| address.sin_port = htons(port); | |
| // バインド | |
| if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { | |
| perror("bind failed"); | |
| close(server_fd); | |
| return -1; | |
| } | |
| // リッスン | |
| if (listen(server_fd, 10) < 0) { | |
| perror("listen failed"); | |
| close(server_fd); | |
| return -1; | |
| } | |
| // 非ブロッキングモードに設定 | |
| if (set_nonblocking(server_fd) < 0) { | |
| close(server_fd); | |
| return -1; | |
| } | |
| return server_fd; | |
| } | |
| // pollfd配列から指定されたfdを削除 | |
| void remove_fd_from_poll(struct pollfd *fds, int *nfds, int fd) { | |
| for (int i = 0; i < *nfds; i++) { | |
| if (fds[i].fd == fd) { | |
| // 最後の要素を削除する位置に移動 | |
| fds[i] = fds[*nfds - 1]; | |
| (*nfds)--; | |
| break; | |
| } | |
| } | |
| } | |
| int main() { | |
| int server_fd; | |
| struct pollfd fds[MAX_CLIENTS + 1]; // +1 for server socket | |
| int nfds = 0; | |
| char buffer[BUFFER_SIZE]; | |
| printf("Starting HTTP server on port %d...\n", PORT); | |
| // サーバーソケット作成 | |
| server_fd = create_server_socket(PORT); | |
| if (server_fd < 0) { | |
| exit(EXIT_FAILURE); | |
| } | |
| // サーバーソケットをpollfd配列に追加 | |
| fds[0].fd = server_fd; | |
| fds[0].events = POLLIN; | |
| nfds = 1; | |
| printf("Server listening on port %d\n", PORT); | |
| while (1) { | |
| // poll()でI/Oイベントを待機 | |
| int ret = poll(fds, nfds, -1); | |
| if (ret < 0) { | |
| perror("poll failed"); | |
| break; | |
| } | |
| // 各ファイルディスクリプタをチェック | |
| for (int i = 0; i < nfds; i++) { | |
| if (fds[i].revents & POLLIN) { | |
| if (fds[i].fd == server_fd) { | |
| // 新しい接続を受け入れ | |
| 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) { | |
| if (errno != EAGAIN && errno != EWOULDBLOCK) { | |
| perror("accept failed"); | |
| } | |
| continue; | |
| } | |
| printf("New client connected (fd: %d, IP: %s)\n", | |
| client_fd, inet_ntoa(client_addr.sin_addr)); | |
| // クライアントソケットを非ブロッキングモードに設定 | |
| if (set_nonblocking(client_fd) < 0) { | |
| close(client_fd); | |
| continue; | |
| } | |
| // 新しいクライアントをpollfd配列に追加 | |
| if (nfds < MAX_CLIENTS + 1) { | |
| fds[nfds].fd = client_fd; | |
| fds[nfds].events = POLLIN; | |
| nfds++; | |
| } else { | |
| printf("Too many clients, rejecting connection\n"); | |
| close(client_fd); | |
| } | |
| } else { | |
| // クライアントからのデータを受信 | |
| int client_fd = fds[i].fd; | |
| ssize_t bytes_read = recv(client_fd, buffer, | |
| sizeof(buffer) - 1, 0); | |
| if (bytes_read <= 0) { | |
| // 接続が閉じられたかエラー | |
| if (bytes_read == 0) { | |
| printf("Client disconnected (fd: %d)\n", client_fd); | |
| } else { | |
| perror("recv failed"); | |
| } | |
| close(client_fd); | |
| remove_fd_from_poll(fds, &nfds, client_fd); | |
| } else { | |
| // HTTPリクエストを処理 | |
| buffer[bytes_read] = '\0'; | |
| printf("Received request from client (fd: %d):\n%s\n", | |
| client_fd, buffer); | |
| handle_http_request(client_fd, buffer); | |
| // HTTP/1.0のためConnection: closeなので接続を閉じる | |
| close(client_fd); | |
| remove_fd_from_poll(fds, &nfds, client_fd); | |
| } | |
| } | |
| } | |
| // エラーイベントをチェック | |
| if (fds[i].revents & (POLLERR | POLLHUP | POLLNVAL)) { | |
| printf("Error on fd %d, closing connection\n", fds[i].fd); | |
| close(fds[i].fd); | |
| remove_fd_from_poll(fds, &nfds, fds[i].fd); | |
| } | |
| } | |
| } | |
| // クリーンアップ | |
| close(server_fd); | |
| return 0; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment