1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語で簡易HTTPサーバーを作りHTML公開(備忘録)

Posted at

実証

このCプログラムは、TCPソケットを利用してポート8080でHTTPリクエストを受け取り、public/ ディレクトリ内の静的ファイルをHTTPレスポンスとして返す簡易Webサーバーです。
socket → bind → listen → accept → read(リクエスト)→ open + read(ファイル)→ write(レスポンス)という一連の流れに従って、Webアクセスを処理しています。

ファイル構造

c_http_server/
├── server.c # サーバー実装
├── server
└── public/
   └── index.html # 公開するHTML

起動

ubuntu@ubuntu:~/kaihatsu/server$ gcc server.c -o server

ubuntu@ubuntu:~/kaihatsu/server$ ./server
サーバーが起動しました: http://localhost:8080
LAN内の他の端末からはIPアドレスを指定してアクセスしてください

サーバー側のIPアドレスを調べる

ubuntu@ubuntu:~$ hostname -I
192.168.68.96 
ubuntu@ubuntu:~$ 

端末(ターミナル)から接続

ubuntu@ubuntu:~$ curl http://192.168.68.96:8080
<!DOCTYPE html>
<html>
<head>
    <title>C HTTP Server</title>
    <link rel="stylesheet" href="/style.css">
</head>
<body>
    <h1>C Language Server</h1>
    <script src="/app.js"></script>
</body>
</html>
ubuntu@ubuntu:~$ 

携帯から接続

ブラウザを開き
http://192.168.68.96:8080
と入力する。
image.png

Cコード

server.c
/*----------------------------------------------------------
HTTPサーバーの実装
C言語で記述された単一スレッドのWebサーバー
8080ポートで待機し、publicディレクトリ内の静的ファイルを配信
------------------------------------------------------------*/
#include <stdio.h> 
#include <stdlib.h>
#include <string.h>  // 文字列操作
#include <unistd.h>  // システムコール(read/writeなど)
#include <arpa/inet.h>  // ネットワーク関連操作
#include <sys/socket.h> // ソケット
#include <sys/stat.h>   // ファイル状態情報
#include <fcntl.h>      // ファイル制御

// 設定定数
#define PORT 8080          // 待機ポート番号
#define BUFFER_SIZE 2048   // バッファサイズ
#define ROOT_DIR "public/" // 静的ファイルのルートディレクトリ

/*----------------------------------------------------------
コンテンツタイプを取得する関数
引数 path: リクエストされたファイルパス
戻り値: MIMEタイプ文字列
------------------------------------------------------------*/
const char* get_content_type(const char *path) {
    const char *dot = strrchr(path, '.'); // 拡張子の検索
    if (dot) {
        if (strcmp(dot, ".css") == 0) return "text/css";
        if (strcmp(dot, ".js") == 0) return "application/javascript";
    }
    return "text/html"; // デフォルトはHTML
}

/*----------------------------------------------------------
HTTPレスポンスを送信する関数
引数 client_fd: クライアントのソケットディスクリプタ
     status: HTTPステータスコード
     content_type: Content-Typeヘッダの値
     body: レスポンスボディ
     content_length: コンテンツの長さ
------------------------------------------------------------*/
void send_response(int client_fd, int status, const char *content_type, 
                   const char *body, int content_length) {
    char header[BUFFER_SIZE];
    // HTTPヘッダの生成
    snprintf(header, sizeof(header), 
        "HTTP/1.1 %d OK\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %d\r\n\r\n",
        status, content_type, content_length);
    
    // ヘッダとボディの送信
    write(client_fd, header, strlen(header));
    write(client_fd, body, content_length);
}

/*----------------------------------------------------------
クエストを処理する関数
引数 client_fd: クライアントのソケットディスクリプタ
------------------------------------------------------------*/
void handle_request(int client_fd) {
    char buffer[BUFFER_SIZE] = {0};
    read(client_fd, buffer, sizeof(buffer)); // リクエストの読み取り
    
    // リクエストラインの解析(例: GET /index.html HTTP/1.1)
    char method[16], path[256];
    sscanf(buffer, "%s %s", method, path);
    
    // ルートパスの場合はindex.htmlを表示
    if (strcmp(path, "/") == 0) strcpy(path, "/index.html");
    
    // ローカルファイルパスの生成
    char full_path[512];
    snprintf(full_path, sizeof(full_path), "%s%s", ROOT_DIR, path + 1);
    
    // ファイルオープン
    int file_fd = open(full_path, O_RDONLY);
    if (file_fd == -1) { // ファイルが存在しない場合
        send_response(client_fd, 404, "text/plain", "Not Found", 9);
        return;
    }
    
    // ファイル内容の読み込み
    struct stat st;
    fstat(file_fd, &st); // ファイル情報の取得
    char *content = malloc(st.st_size); // メモリ確保
    read(file_fd, content, st.st_size);
    
    // レスポンス送信
    send_response(client_fd, 200, get_content_type(path), content, st.st_size);
    
    // リソースの解放
    free(content);
    close(file_fd);
}

/*----------------------------------------------------------
メイン関数
------------------------------------------------------------*/
int main() {
    int server_fd, client_fd;
    struct sockaddr_in address;
    
    // ソケットの作成
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("ソケットの作成に失敗しました");
        exit(EXIT_FAILURE);
    }
    
    // ソケットオプションの設定(アドレスの再利用を許可)
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    // アドレス構造体の設定
    address.sin_family = AF_INET;          // IPv4
    address.sin_addr.s_addr = INADDR_ANY;   // すべてのインターフェース
    address.sin_port = htons(PORT);         // ポート番号
    
    // ソケットのバインド
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("バインドに失敗しました");
        exit(EXIT_FAILURE);
    }
    
    // 接続待機開始 待機接続数を3に設定
    if (listen(server_fd, 3) < 0) {
        perror("リッスンに失敗しました");
        exit(EXIT_FAILURE);
    }
    
    printf("サーバーが起動しました: http://localhost:%d\n", PORT);
    printf("LAN内の他の端末からはIPアドレスを指定してアクセスしてください\n");
    
    // メインループ(接続を受け付けて処理)
    while (1) {
        socklen_t addrlen = sizeof(address);
        // 接続を受け付ける
        if ((client_fd = accept(server_fd, (struct sockaddr *)&address, &addrlen)) < 0) {
            perror("接続受け付けに失敗しました");
            continue;
        }
        handle_request(client_fd); // リクエスト処理
        close(client_fd);          // クライアント接続を閉じる
    }
    
    return 0;
}
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?