C言語 socket通信でhttp通信

背景

 webアプリを作りたくて始めました.普通のアプリならjavascriptphpでゴニョゴニョすればいいと思うのですが(よくしらない),FPGAチップのZinqシリーズ上で動くFPGA上のデータをウェブブラウザ経由でモニタリングするものを考えていたため,メモリの直叩きが得意なCでサーバー側のプログラムを作る必要がありました.その第一歩としてまずはhttp通信をCで表現するとどうなるのか?そもそもhttpとは?を理解するべくsocket通信でhttp通信を試してみることにしました.

 

ネットワークの基礎

 ネットワークはOSI参照モデルによって階層分けされており,一番上のアプリケーション層は一つ下のプレゼンテーション層に支えられ...というように成り立っています.今自分が使ってるモノがどの層で動いているのかを意識するとわかりやすいです.

 OSI参照モデルはあくまで概念なのでイメージ程度に考えます.

 

表1 OSI参照モデル

f:id:mamenicaSPA:20220405174005p:plain

 

f:id:mamenicaSPA:20220405174033p:plain

 図1 ざっくりしたTCP/IP

 

 図1にざっくりした下層の解説図を表しました.

 ネットワーク層:クライアントはサーバーのIPアドレスを頼りに通信を始めます.クライアント-サーバー間にどんな機器が存在するか,どんなデータを送っているかを気にすることなく通信します.

 データリンク層:ネットワーク機器は宛先IPアドレスをもとに次にどこにデータを送ればよいか判断しデータを中継します.この時,機器に割り振られたアドレスがMACアドレスと呼ばれるものです.ネットワーク機器はMACアドレスで次の中継先にデータを送るので,中継先とどんなインターフェースでつながれているか,最終目標のIPアドレスは何なのかを気にすることなく通信します.

 物理層:ネットワーク機器の端子からは何らかの物理現象でデータを表現し送信します.MACアドレスIPアドレス,データの中身を気にすることなく,単に信号を光に変換したり,Wi-Fi電波で飛ばしたり,電気信号としてケーブルに流します.

 

 以上のようにしてクライアント-サーバー間で通信を行っています.実際にはIPアドレスを変換したりなんやら勘彌ら複雑ですが気にしないことにします.

 

socket通信の基礎

 C言語でsocketを使うと文字の通信ができるようになります.socket通信はセッション層らしいです.TCP通信では図2のように通信を行います.socketはこの一連の処理をなんやかんややってくれて,read(),write()関数だけで文字のやり取りができるようにしてくれます.

 

f:id:mamenicaSPA:20220405174300p:plain

図2 TCP通信の概要

 

httpの基礎

 httpはアプリケーション層で動くプロトコルです.クライアント(webブラウザ)はサーバーに対してデータ送信要求を示す文字列を送り,データが返ってくるという形です.(図3)今回の送信文字列は

GET /test.html HTTP/1.1

Host: 192.168.0.41:80

Connection: close

です.

1行目:メソッド(送信要求),要求オブジェクトURIプロトコル

2行目:IPアドレス,ポート番号

3行目:ヘッダ,上述のように記述すると送信が終わると接続が切れる.

ローカルIPアドレス192.168.0.41(ポート80)にHTTP ver 1.1 で/test.htmlのデータを要求しています.

 

f:id:mamenicaSPA:20220405174435p:plain

図3 http通信の概略

 

取得するhtmlファイルの大きさが大きいと1度のレスポンスで送り切れないので今回の実験では1度で送り切れるような簡単なtest.htmlを用意しました.

読みだしたtest.htmlファイルも載せておきます.サーバー側ではapacheが動いておりこのファイルをindex.htmlと同じディレクトリに保存して実験しました.

test.html

<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <title>test</title>
 </head>
 <body>
  <p>hello world</p>
 </body>
</html>

 

実行プログラム

 インターネット上をあされば数多の同様なプログラムが出てくると思います.しかしながらたいていのプログラムは名前解決が実装されてたりエレガントにエラー処理がされてたりしてわかりにくいので超絶シンプル記述を意識しました.

 httpは究極に単純化するとsocket通信のwrite()でGET要求を送ってread()でデータを読めばいいのです.

 今回はあらかじめLAN内にサーバーを立ててtest.htmlを用意し,そこにLinuxPCからアクセスする作戦で行きました.サーバーのアドレスは192.168.0.41を設定しました.

 

クライアントプログラム

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

int main()
{
	struct sockaddr_in server;
	int sock;
	char buf[1024];		//バッファ(1kB)
	int n;
 
	char request_messe[]="GET /test.html HTTP/1.1\r\nHost: 192.168.100.41:80\r\nConnection: close\r\n\r\n";	//文字数79

	/*ソケットの作成*/
	sock = socket(AF_INET, SOCK_STREAM, 0);

	server.sin_family = AF_INET;	//よくわからん
	server.sin_port = htons(80);	//接続先ポート番号
	server.sin_addr.s_addr = inet_addr("192.168.0.41");	//接続先IPアドレス

	/*サーバに接続*/
	connect(sock, (struct sockaddr *)&server, sizeof(server));
 
	printf("conected\n");
 
	send(sock, request_messe, 77,0);	//(ソケット,送信メッセージ,データ文字数,フラグ?)

	printf("data send\n");
 
	/*サーバからデータを受信*/
	memset(buf, 0, sizeof(buf));		//バッフア初期化
	n = read(sock, buf, sizeof(buf));	//受信
	printf("read:%d\n\n %s\n", n, buf);	//表示

	/*ソケットの終了*/
	close(sock);

	return 0;
}

実行結果

conected
data send
read:415

 HTTP/1.1 200 OK
Date: Tue, 05 Apr 2022 00:05:56 GMT
Server: Apache/2.4.38 (Raspbian)
Last-Modified: Mon, 04 Apr 2022 15:06:42 GMT
ETag: "8f-5dbd579d471ae"
Accept-Ranges: bytes
Content-Length: 143
Vary: Accept-Encoding
Connection: close
Content-Type: text/html

<!DOCTYPE html>
<html lang="ja">
 <head>
  <meta charset="UTF-8">
  <title>test</title>
 </head>
 <body>
  <p>hello world</p>
 </body>
</html>