メモ > 技術 > プログラミング言語: C/C++ > C: ネットワークを扱うプログラム
C: ネットワークを扱うプログラム
■daytimeクライアントを作る
# yum install xinetd
# vi /etc/xinetd.d/daytime-stream
■httpを処理する
disable = yes
↓
disable = no
# systemctl start xinetd
# netstat --tcp --listen | grep daytime
tcp6 0 0 [::]:daytime [::]:* LISTEN
$ vi daytime.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
static int open_connection(char *host, char *service);
int main(int argc, char *argv[])
{
int sock;
FILE *f;
char buf[1024];
sock = open_connection((argc>1 ? argv[1] : "localhost"), "daytime"); /* 「"daytime"」は「"13"」でも接続できる(13番ポート) */
f = fdopen(sock, "r");
if (!f) {
perror("fdopen(3)");
exit(1);
}
fgets(buf, sizeof buf, f);
fclose(f);
fputs(buf, stdout);
exit(0);
}
static int open_connection(char *host, char *service)
{
int sock;
struct addrinfo hints, *res, *ai;
int err;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
/*
* 接続先とポートを渡すと、1つ以上のaddrinfo構造体を返す
* その構造体には、bindやconnectを呼び出す際に指定できるインターネットアドレスが格納されている
*/
if ((err = getaddrinfo(host, service, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo(3): %s\n", gai_strerror(err));
exit(1);
}
/*
* getaddrinfoの戻り値はアドレス候補のリスト
* 候補に次々に接続を試し、最初に成功したアドレスを試す
* 例えばIPv4のアドレスとIPv6のアドレスを返してきた場合に、接続できる方を使用する
*/
for (ai = res; ai; ai = ai->ai_next) {
sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (sock < 0) {
continue;
}
if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) {
close(sock);
continue;
}
/* 接続に成功 */
freeaddrinfo(res);
return sock;
}
fprintf(stderr, "socket(2)/connect(2) failed");
freeaddrinfo(res);
exit(1);
}
$ gcc -o daytime daytime.c
$ ./daytime
22 FEB 2020 17:21:10 JST
$ ./daytime localhost
22 FEB 2020 17:21:16 JST
$ vi httpd.c
$ vi test.txt
■httpサーバを作る
Hello!
This is a test file.
$ gcc -o httpd httpd.c
$ ./httpd … オプションを指定していないので使い方を表示。
Usage: ./httpd <docroot>
$ ./httpd . … カレントディレクトリをドキュメントルートとする。
GET /test.txt HTTP/1.0 … リクエストを送る。
… 空行を送るとリクエスト終了。
HTTP/1.0 200 OK … レスポンスが返る。
Date: Sat, 22 Feb 2020 09:24:55 GMT
Server: LittleHTTP/1.0
Connection: close
Content-Length: 30
Content-Type: text/plain
Hello!
This is a test file.
$ ./httpd .
GET /xxx.txt HTTP/1.0 … 存在しないファイルを指定してみる。
HTTP/1.0 404 Not Found … 404エラーが返る。
Date: Sat, 22 Feb 2020 09:29:43 GMT
Server: LittleHTTP/1.0
Connection: close
Content-Type: text/html
<html>
<header><title>Not Found</title><header>
<body><p>File not found</p></body>
</html>
$ vi httpd2.c
$ vi test.txt
ブラウザから以下にアクセスすると、テキストファイルの内容が表示される。
http://192.168.33.10:8081/test.txt
デーモンとはバックグラウンドプロセスのことで、厳密には制御端末を持たないプロセスのこと。
サーバはずっと起動させておく必要があるため、プロセスを起動させた人がログアウトしても動き続けるようにする。
デーモンになるには、プログラム内で以下3つの作業を行う(もしくはこれらを行う daemon(3) を使う。)
・ルートディレクトリに chdir() する。
プロセスが動作しているディレクトリはアンマウントできないので、
長時間動作するデーモンはできるだけルートディレクトリに移動させておく。
・標準入出力を /dev/null につなぐ。
プログラムからうっかり標準入出力を使ってしまってエラーが起きないようにする。
・制御端末を切り離す。
fork() してから親プロセスを終了すると、制御端末を持たないプロセスになる。
子プロセスで setsid() で制御端末を持たない新しいセッションを作成する。Hello!
This is a test file.
$ gcc -o httpd2 httpd2.c
$ ./httpd2 … オプションを指定していないので使い方を表示。
Usage: ./httpd2 [--port=n] [--chroot --user=u --group=g] [--debug] <docroot>
$ sudo su -
# ./httpd2 --port=8081 --chroot --user=vagrant --group=vagrant . … カレントディレクトリをドキュメントルートとする。