Unixソケットプログラミング/HTTP通信

『WinSock/外部通信の方法』で概要を説明してある。webブラウザなどのよるホームページなどの表示の仕組みは、原理的にはコレ。
Unixの場合、下記のコードで、ローカルホスト上のApacheにアップロードしたhtmlファイルに、コマンドラインなどからソケット通信でアクセスできる。
あらかじめ、アップロード用のhtmlファイルを作成しておいて、Apacheのドキュメントルートフォルダに入れること。(ドキュメントルートが分からなければ、wikibooks『PHP/確実に動作させるまで』を参照するか、ググること。)

解説は、WinSock/HTTP通信 とほぼ同じなので、そちらを参照されたし。

なお、アップロードしたhtmlファイルは、上記コードの場合
という内容である。(ファイル名は、デタラメhtmlのつもり)
意味的には
のつもり。
なお、Linuxの場合は日本語の全角文字列でも送受信できる(※ Fedora32 で 2020年7月9日に確認ずみ )。Windowsの場合などは文字コードなどの理由で日本語の設定が難しい場合があるので、本wikiでは説明の簡単化のため英語にしてある。
コードの注意点として、もしWinSockのコードをコピーしたとき、Linuxでは strcpy_s を使えないので、strcpy に置き換える必要があり、引数なども調整する必要がある。
また、send()関数ではなくwrite()関数にすること。Linuxにも互換性のためかsend()関数が用意されているようだが、若干、WinSockとは仕様が違うらしく、WinSockのコードそのままでは動かない。ならば、いっそwrite()関数に置き換えたほうがLinuxではラクである。
上記コードを実行したら無限にループになる場合、Apacheの立ち上げを忘れている可能性があります。初日は本節を読んでいてApacheを立ち上げるように説明しているので忘れなくても、2日目以降によく立ち上げを忘れるでしょう。
コード改修で立ち上げ忘れを考慮したコードに改修する事も可能ですが(for文に置き換えるなど)、しかし本節では初学者への分かりやすさの観点から、上記コードはそのまま、ループ箇所をwhile文のままにしておきます。

いわゆる外部ネットを含む、ウェブサイトとのHTTP通信を行う場合、ポート番号を「80」番に設定する必要があります。この理由は、そういうふうにHTTPの国際規格やTCP/IPの国際規格などがそう決まっているからです。
一般に、プロトコルの種類や通信の種類などによって、ポート番号が決まっています。
メール通信になると、なぜか上記とは別のポート番号を使います(読者の混同を防ぐため、メール通信のポート番号については、このページでは紹介しません)。
読者は「なぜ、プロトコルの種類に応じてポート番号が違うのか?」とか疑問はわくでしょうが、結局は単に昔の人がそう決めてしまっただけです。
「ポート」と聞くと、あたかも、ハードウェアの何かの端子の番号かのように思えるかもしれませんが、しかしハードウェア端子は一切、ポート番号とは無関係です。
「ポート番号」とは、その名に反して、実質的な意味はプロトコル番号です。
場合によっては複数のクライアント側コンピュータが1つのホストにアクセスする事もよくあるので、同じポート番号を使う場合が多々あり(たとえば、どのクライアントでもHTTPなら80番を使う)、ポート番号だけでは通信先を特定できないです。
よって、通信先の特定のために、ポート番号とIPアドレスの少なくとも2つ以上の関連付け(「アソシエーション」といいます)されたデータセットによって、通信が管理されています。
コードの送信メッセージの
でGETメッセージを送っている。
なお、1回だけの
は改行の意味である。ホスト情報など追加の情報を送る場合、1回だけ「\r\n」と入力し、追加情報を1行ずつ入力していく。
また
と2個改行が続くことで、送信メッセージ全体がいったん終了する事を意味している(これはUnixソケット通信でも同様)。
なお、Bad Request の結果の表示は、
のようになる。

Windowsでは、write()関数ではなくsend()関数でGETメッセージを送るのだが、Unixの場合、send関数で送っていも、recv()関数で受け取る事ができる。
Unixでもrecv() 関数の仕様は、正常な終了時には 0を返し、エラー時に -1 を返すという実装のようである。

実は、recvのブロックのif文は、
だけでも、とりあえず上記のコードは動くし、正常に終了する。
一方、
とか
だと、無限ループになってしまい、そのままでは終了しなくなるので、閉じるにはコマンドプロンプトのウィンドウ右上の「X」ボタンで閉じるしかない。

write(destSocket, SendMsg, strlen(SendMsg) ); の strlen の長さは、ピッタリと、 strlen(SendMsg)でなければなりません。
もし、「多いほうが安全かな?」と誤解して strlen(SendMsg)+1 のように大きくしてしまうと、2行目以降の送信メッセージが1文字ずれてしまうためか、もし、次のHostメッセージ
で送信しようと思っても、送信に失敗します。
HTTP1.0ではHostメッセージは不要ですが、HTTP1.1ではHostメッセージが必ず必要なので、strlenはピッタリと過不足なくするように注意してください。

※ 解説はWinSockとほぼ同じ。
sockaddr_in構造体はIPv4用の構造体なので、IPv6では使えません。
ですが、ほぼ同じ使い方のできるIPv6用の構造体 sockaddr_in6 がありますので、これを使うと IPv6対応のソケットプログラミングが簡単です。なお LinuxでもWindowsでも同様に sockaddr_in6 に置き換える方法で、IPv6対応できます。
さて、Linuxでも、 AF_INET を AF_INET6 に変える必要があります。
また、sin_portもsin6_portに、sin_family も sin6_family に変えるなど、 構造体のメンバもIpv6用に更新する必要があります。
s_addr も s6_addr に更新します。なお Linuxでも同様です。
手作業でやると大変なので、テキストエディタの一括変換の機能をつかうとラクだし、 更新し忘れによるエラーも防げるでしょう。
また、Ipv6のローカルホストは::1です。6ケタでなく3ケタなのは不思議に感じるかもしれませんが、国際規格(RFC 4291 など)でそう決まっています。(なお RFCとはw:Request for Commentsのこと)
なお、「::」というふうにコロンが2個続いた記号の意味は、のこりすべて「0」と言う意味です。
つまり、「::1」とは、一番右だけが1で、残りは0の、
という意味です。なお、(2進数ではなく)16進数です。なお、IPv6の4個の数字の並びは合計8グループです(6グループではないです)。(つまり、4×8=32個の数字がある。)
Ipv6のvとはバージョンの事です。数字のグループの個数とは無関係です。IPv4の数字が「127.0.0.1」のように4グループなのは、偶然の一致です。
ともかく、Unixの場合、IPv6対応のコードは、たとえば下記のようになります。

HTTPは1.0でも1.1でも、どちらでも、IPv6対応が可能です。
HTTP1.1では、Hostメッセージが必ず必要です。
Apache のローカルホスト名はそのまま「localhost」ですので、ポート番号の80番とあわせてメッセージを
のようにHostメッセージを加えて送信することになります。


外部サイトにアクセスする場合、Hostメッセージは
のように入力する事になる。
Hostに入れるのは、アクセス先のドメイン名とポート番号である。まちがえて、localhostを入れても、単なるエラーになる。
gethostbyname という、サイト名をもとにIPアドレスを検索できる関数がソケット通信にはあるが(なおWindowsでは、IPv6対応などの事情により、2020年現在では他の関数( getaddrinfo など)に置き換わっている)、
しかし、この gethostbyname の問い合わせ先は DNSサーバ であるので、HTTP通信とはあまり関係ない。(IPアドレス先サーバーに接続はしない関数である。単にサイト名からIPアドレス情報をDNSサーバに問い合わせて受け取るだけである。)
しかも、企業のホームページの場合、たとえばどこかの企業の何らかのコンテンツをブラウザで閲覧した場合、アドレス欄に
のように各ページのURLが表示されるわけだが、
しかし通常、DNSサーバーに登録されているのは
の部分までである。
また、けっして gethostbyname は目的の企業サイトなどと接続するわけではない。下記コードのように、bind() や connect() などが無くても、 gethostbyname の結果を表示できる。

構造体であらかじめ hostent 型というのがOSなどにより用意されている。なので、この構造体の「hostent」という部分の名前は変えてはいけない。
*host は単なるポインタ変数名なので、他のポインタ変数名に変えてもいい。
IPアドレスを表示したい場合は、さらに
というコードを付けたす。

つまり、
のようになる。
struct in_addr *addr; のポインタ変数名 *addr は別にほかの変数名でもよいのだが(変えても動作することをLinuxのFedora32で2020年に確認ずみ)、慣習的に「*addr」という変数名をつかう場合が多い。
余談だが、gethostbyname で自機にホスト名やIPアドレスなどを問い合わせるとき、わざわざサーバー側のプログラムを立てる必要は無い。
このことからも、最終目的のサーバーに問い合わせているのではなく、別のところ(一般にはDNSサーバー)に問い合わせている様子が分かるだろう。
まず、コマンドラインで
と入れると、そのドメインのIPアドレスが出て来る。
たとえば、
みたいにコマンドを入れる。(yaahuleは架空の企業名。)
もし後述コードで getaddrinfo によるソケット通信をした結果、nslookupで表示された結果と同じIPv6アドレスまたはIPv6アドレスが表示されれば、そのコードはDNSサーバとの通信に成功している。
Ipv4用のコードだが、getaddrinfoが次のコードで、Linux上で動く。(※ 2020年7月17日に、Fedora32上で動くことを確認ずみ。)
なお、下記コードの企業名、ドメイン名はデタラメなので、実験の際には、実在する検索エンジンのドメインに変えること。(下記のままだと、./a.out などで実行しても、何も表示されない。)
telnet (テルネット)という国際規格に基づいたネット通信の方法が古くから(インターネット黎明期から)あり、Linuxでは、コマンドラインで telnet を利用できる。
Fedora Linux (Fedora 32) の場合、最初からtelnetはインストールされているので新規インストール作業は不要だが、もし入っていなかったら、
でインストールできる。
コマンドラインで
と入力すれば、コマンドラインがtelnet に移行する。 コマンド入力が成功すれば、
のような画面になる。
telnet をやめたい場合、
または単に
と入力すればいい。
Linuxの場合、telnetの文字表示の設定が最初から済んでいるので、特に設定をいじる必要は無い。(なおWindowsは、初期設定ではtelnetが文字表示されない。)
Linux の telnet で、ローカルホストに接続するには、Apacheを立ち上げたあと、コマンド入力
で接続できる。書式は
の書式である。
コマンド入力が成功すれば、
のような画面になる。
IPアドレスでもオープンできるが、
と書くと、数字が続いて見づらいので、なるべく「localhost」でオープンしたほうが良いだろう。
そして、Escape character なんとかの次の行にカーソルが移るので、
のように入力する。(リターン)とは、その位置でリターンキーを押すこと。けっして実際に「(リターン)」と文字入力してはいけない。
この結果、見た目は
のようになる。
成功すれば、
(長いので冒頭を抜粋)のような返事が返ってきて、以下のようになる。