0
0

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リクエストを送信し原理を学ぶ(Linux)

Posted at

URLからデータを取得する流れ

(1) ソケットを作成(socket)

(2) ドメイン名をDNSでIPに変換(gethostbyname など)

(3) IPアドレスとポート80にソケットでTCP接続(connect)

(4) HTTPリクエスト(テキスト)をソケットから送信(write/send)

(5) HTTPレスポンスをソケットから受信(read/recv)

(6) ソケットを閉じて通信終了(close)

1.ソケットを生成

Linuxではソケットもファイルと同じくファイルディスクリプタ(FD)で管理される。
ここでソケットFDを生成しておくとwrite()やclose()で扱えるようになる。
图片.png

socket.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main(){

  int sockfd = socket(AF_INET,    // IPv4
                     SOCK_STREAM, // 信頼性のある通信
                     0);          // プロトコル自動選択(SOCK_STREAMならTCPが選ばれる)
                     
  if(sockfd < 0){
    puts("ソケット作成失敗!");
    return 1;
  }
  
  printf("ソケット作成成功! ソケットFD=%d\n",sockfd);
  
  // ソケットを閉じる
  close(sockfd);
                     
  return 0;
}

2.ドメイン→IPアドレスへ変換

ドメイン名をIPアドレスに変換する。
gethostbyname()も自作できなくはないが、複雑な上にかなり話が逸れるので今回は標準関数を仕様して、次回自力で実装する。

图片.png

gethostbyname.c
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <string.h>

int main(){

  const char *host = "www.google.com";
  
  //ホスト名→IPアドレスへ変換
  struct hostent *server = gethostbyname(host);
  if(server == NULL) {
    puts("解析失敗");
    return 1;
  }
  
  //gethostbyname()が返したIPアドレス
  struct in_addr *addr = (struct in_addr *)server->h_addr_list[0];
  
  //16進数で表示
  unsigned char *bytes = (unsigned char *)&(addr->s_addr);
  printf("IPアドレス(生データ): ");
  for (int i = 0; i < sizeof(addr->s_addr); i++) {
      printf("%02x ", bytes[i]);
  }
  printf("\n");

  //10進数で表示
  char *ip = inet_ntoa(*addr);
  printf("ホスト名: %s\n", host);
  printf("IPアドレス: %s\n", ip);
                     
  return 0;
}

hostent構造体の定義

Grepで検索する

test@test-ThinkPad-X280:~/http$ grep -A10 'struct hostent' /usr/include/netdb.h
struct hostent
{
  char *h_name;			/* Official name of host.  */
  char **h_aliases;		/* Alias list.  */
  int h_addrtype;		/* Host address type.  */
  int h_length;			/* Length of address.  */
  char **h_addr_list;		/* List of addresses from name server.  */
#ifdef __USE_MISC
# define	h_addr	h_addr_list[0] /* Address, for backward compatibility.*/
#endif
};

in_addr構造体の定義

※server->h_addr_list[0]の中身を表示する為に使っただけで、最終的にhtmlを取得するソースの中では使用していない。(記事の一番した参照)
Grepで検索する

test@test-ThinkPad-X280:~$ grep -A3 'struct in_addr' /usr/include/netinet/in.h
struct in_addr
  {
    in_addr_t s_addr;
  };
test@test-ThinkPad-X280:~$ grep typedef /usr/include/netinet/in.h | grep in_addr_t
typedef uint32_t in_addr_t;
test@test-ThinkPad-X280:~$ echo '#include <stdint.h>' | gcc -E -dD -xc - | grep int32_t
typedef signed int __int32_t;
typedef unsigned int __uint32_t;
typedef __int32_t __int_least32_t;
typedef __uint32_t __uint_least32_t;
typedef __int32_t int32_t;
typedef __uint32_t uint32_t;

3.サーバへ接続

ソケットを作成し、接続先のIPアドレスも取得できたら、いよいよ「TCP接続」を行います。

connect()を用いてソケットから指定されたIPアドレスとポート番号に接続を試みます。

图片.png

connect.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
    const char *host = "example.com";
    const int port = 80;

    // ソケット生成 (上で説明済み)
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        puts("socket");
        return 1;
    }

    // ドメイン→IP変換
    struct hostent *server = gethostbyname(host);
    if(server == NULL) {
        puts("解析失敗");
        return 1;
  }
  

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));// 構造体の全てのバイトを0で初期化
    server_addr.sin_family = AF_INET;//IPv4使用
    server_addr.sin_port = htons(port);
    memcpy(&server_addr.sin_addr.s_addr,
                         server->h_addr,  // ドメインから取得したIPアドレス
                         server->h_length // IPアドレスの長さ
          );

    // サーバーへ接続する
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        puts("connect");
        close(sockfd);
        return 1;
    }
    
    printf("接続成功!\n");

    close(sockfd);
    return 0;
}

memset() とは?
void *memset(void *s, int c, size_t n);
s: 初期化したいメモリ領域の先頭ポインタ
c: 埋める値(0〜255、1バイト)
n: 何バイト分埋めるか

4.HTTPリクエストの文字列を生成

以下の文字列を生成する。
Hostは接続先によって変更。

GET / HTTP/1.1\r\n
Host: www.google.com\r\n
Connection: close\r\n
\r\n

5. HTTPリクエストを送信する

TCP接続済みのソケットFDに対して、HTTPリクエスト文字列(request)を送信します。
write() はファイルに書き込むのと同様に使えます。
(Linuxでは全てをファイルとして扱う)

    if (write(sockfd, request, strlen(request)) < 0) {
        puts("write");
        close(sockfd);
        return 1;
    }

6. HTTPレスポンスを受信して表示する

read()から取得する。
一回で全て取得出来るとは限らないのでwhileでループにして取得していく。
実際にはTCPソケットが内部的に持っている受信バッファから少しずつ取得している。
あまり細かいことはLinuxカーネル内部の実装を読まないと分からない。

    char buffer[2048];
    int bytes;
    while ((bytes = read(sockfd, buffer, sizeof(buffer)-1)) > 0) {
        buffer[bytes] = '\0';
        printf("%s", buffer);
    }

example.comからデータを取得する。

图片.png

http.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
    const char *host = "example.com";
    const int port = 80;

    // ソケットを生成
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return 1;
    }

    // ドメイン→IPアドレスへ変換
    struct hostent *server = gethostbyname(host);
    if (server == NULL) {
        puts("ip取得!");
        return 2;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    memcpy(&server_addr.sin_addr.s_addr, server->h_addr, server->h_length);

    // サーバーへ接続する
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        puts("connect");
        close(sockfd);
        return 3;
    }

    // HTTPリクエストの文字列を生成
    char request[1024];
    snprintf(request, sizeof(request),
             "GET / HTTP/1.1\r\n"
             "Host: %s\r\n"
             "Connection: close\r\n"
             "\r\n", host);
             
    if (write(sockfd, request, strlen(request)) < 0) {
        puts("write");
        close(sockfd);
        return 4;
    }

    // HTTPレスポンスを受信して表示する
    char buffer[2048];
    int bytes;
    while ((bytes = read(sockfd, buffer, sizeof(buffer)-1)) > 0) {
        buffer[bytes] = '\0';
        printf("%s", buffer);
    }

    // socketを閉じる
    close(sockfd);
    return 0;
}
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?