意識低い系会社員

意識低い系会社員の日常

【スポンサーリンク】

ふつうのLinuxプログラミング 第2版の内容memo(15章)

【スポンサーリンク】

 

こんにちは。

 

最近、ふつうのLinuxプログラミング 第2版を読んでいるので知識の定着のために学んだ内容を要約したメモを書きます。

このエントリは完全な個人のメモです。

 

お勉強のためにこの本を読んでいるので、内容を覚えるためにSummarizing(サマライジング)を行います。

 

14章の内容はこちら。

amistad06-a.hatenablog.com

 

ネットワークプログラミングの基礎

この章で説明すること

IPアドレスやポート番号、TCP/UDP通信、DNSとドメイン、ソケット、ネットワークに関するAPIの説明をします。

 

ネットワークプログラミングの概要

ネットワークプログラミングでもストリームの読み書きをするという点ではファイルの読み書きと同じです。

ストリームはIPアドレスとポート番号で取得します。

 

IPアドレスとポート番号

IPアドレスで接続するホストを特定し、ポート番号でどのアプリケーションに繋ぐかを特定できます。

接続を待ち受けているプロセスのことをサーバプロセス、接続要求を出すプロセスのことをクライアントプロセスといいます。

 

ウェルノウンポート(well-known port)

各サービスのデフォルトのポート番号です。

HTTPなら80、SMTPなら25、など。

 

TCPとUDP

ネットワーク通信では厳密にはストリームは存在せず、データはパケットでやり取りされます。

TCPやUDPプロトコルを使えばストリームと同じような扱いでネットワーク通信を扱えます。

TCPはパケットの到達順序保障や欠損したパケットの再送機能などがあるのでデータの信頼性が高く、UDPはそれらがない代わりに高速な通信が可能です。

 

IPv4とIPv6

IPv4のアドレスは32bit、IPv6のアドレスは128bitです。

できるだけ両バージョンに対応するプログラムを作るのが望ましいです。

 

ホスト名

IPアドレスは数字の羅列ですが、数字の羅列にホスト名を紐づけることが可能です。

hatena.ne.jpなどがホスト名に当たります。

※hatenaの部分だけがホスト名に該当するという人もいる。

 

DNS(Domain Name System)

DNSはホスト名をドメインに分割して階層構造で管理する仕組みです。

hatena.ne.jpの場合、

jpドメイン

→ne.jpドメイン

 →hatena.ne.jpドメイン

といった感じに階層管理できます。

jpやcomなど、文字列の最後に付きがちなドメインのことをトップレベルドメイン(TLDs:Top Level Domains)と言います。

ルートドメインから全てのドメインを指定したドメインのことをFQDN(Fully Qualified Domain Name)と言います。

 

リゾルバ

ホスト名をIPアドレスに変換する機構をリゾルバと言います。

ホスト名をIPアドレスに変換することを名前解決というので、リゾルバ。

 

ソケット

ストリームを接続するための口。

かなり抽象化された概念で、TCPもUDPもIPv4もIPv6も対応可能です。

 

クライアント側のソケットAPI

クライアントからサーバにストリーム接続するためには以下のシステムコール呼び出しが必要です。

1. socket(2)

2. connect(2)

 

サーバ側のソケットAPI

サーバ側でストリーム接続を待ち受ける時は以下のシステムコール呼び出しが必要です。

1. socket(2)

2. bind(2)

3. listen(2)

4. accept(4)

 

名前解決API

getaddrinfo()

getnameinfo()

freeadderinfo()

gai_strerroe()

など

 

daytime

daytimeはテスト用に用意されているプロトコルで、ソケットで接続するとサーバが時刻を返してくれるサービスです。

daytimeは単体のサービスではなくinetdやxinetdの内部プログラムとして提供されています。

inetdはネットワーク接続をしてくれる特殊なサーバで、インターネットスーパーサーバと呼ばれています。

 

daytimeサーバの準備

xinetdをインストールします。

~/prj/study/linux_programming/15$ sudo apt-get install xinetd
[sudo] user のパスワード: 
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下のパッケージが自動でインストールされましたが、もう必要とされていません:
  libllvm7
これを削除するには 'sudo apt autoremove' を利用してください。
以下のパッケージが新たにインストールされます:
  xinetd
アップグレード: 0 個、新規インストール: 1 個、削除: 0 個、保留: 6 個。
108 kB のアーカイブを取得する必要があります。
この操作後に追加で 310 kB のディスク容量が消費されます。
取得:1 http://jp.archive.ubuntu.com/ubuntu bionic/universe amd64 xinetd amd64 1:2.3.15.3-1 [108 kB]
108 kB を 0秒 で取得しました (1,057 kB/s)
以前に未選択のパッケージ xinetd を選択しています。
(データベースを読み込んでいます ... 現在 189839 個のファイルとディレクトリがインストールされています。)
.../xinetd_1%3a2.3.15.3-1_amd64.deb を展開する準備をしています ...
xinetd (1:2.3.15.3-1) を展開しています...
xinetd (1:2.3.15.3-1) を設定しています ...
systemd (237-3ubuntu10.33) のトリガを処理しています ...
man-db (2.8.3-2ubuntu0.1) のトリガを処理しています ...
ureadahead (0.100.0-21) のトリガを処理しています ...
~/prj/study/linux_programming/15$

 

daytimeの設定を変更します。

/etc/xinetd.d/daytimeのdisableをnoにします。

~/prj/study/linux_programming/15$ cat /etc/xinetd.d/daytime
# default: off
# description: An internal xinetd service which gets the current system time
# then prints it out in a format like this: "Wed Nov 13 22:30:27 EST 2002".
# This is the tcp version.
service daytime
{
	disable		= no
	type		= INTERNAL
	id		= daytime-stream
	socket_type	= stream
	protocol	= tcp
	user		= root
	wait		= no
}

# This is the udp version.
service daytime
{
	disable		= no
	type		= INTERNAL
	id		= daytime-dgram
	socket_type	= dgram
	protocol	= udp
	user		= root
	wait		= yes
}
~/prj/study/linux_programming/15$ 

 

変更した設定の反映します。

~/prj/study/linux_programming/15$ sudo systemctl reload xinetd
~/prj/study/linux_programming/15$ 

 

daytimeが動いているかどうかを確認します。

~/prj/study/linux_programming/15$ netstat --tcp --listen
稼働中のインターネット接続 (サーバのみ)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態      
tcp        0      0 0.0.0.0:netbios-ssn     0.0.0.0:*               LISTEN     
tcp        0      0 localhost:domain        0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:microsoft-ds    0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:netbios-ssn        [::]:*                  LISTEN     
tcp6       0      0 [::]:daytime            [::]:*                  LISTEN     
tcp6       0      0 [::]:microsoft-ds       [::]:*                  LISTEN     
~/prj/study/linux_programming/15$ 

 

daytimeクライアントを作る

準備ができたのでdaytimeに接続するクライアントプログラムを作成します。

 

以下、本の中のプログラムの写経です。

解説は本を参照。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.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");
    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;
    if((err = getaddrinfo(host, service, &hints, &res)) != 0){
        fprintf(stderr, "getaddrinfo(3): %s\n", gai_strerror(err));
        exit(1);
    }

    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;
        }

        // success
        freeaddrinfo(res);
        return sock;
    }
    fprintf(stderr, "socket(2)/connect(2) failed.\n");
    freeaddrinfo(res);
    exit(1);
}

 

実行結果は以下。

~/prj/study/linux_programming/15$ ./a.out localhost
02 DEC 2019 20:45:28 JST
~/prj/study/linux_programming/15$ ./a.out 
02 DEC 2019 20:45:30 JST 

 

演習問題

1. telnetコマンドの使い方を調べて、daytimeサーバに接続してみなさい。

 

telnet ホスト名 サービス名またはポート番号で接続可能。 

実行結果は以下。

~/prj/study/linux_programming/15$ telnet localhost daytime
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
02 DEC 2019 19:30:38 JST
Connection closed by foreign host.
~/prj/study/linux_programming/15$ telnet localhost 13
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
02 DEC 2019 19:31:20 JST
Connection closed by foreign host.
~/prj/study/linux_programming/15$ 

 

2. echoプロトコルは、こちらからソケットに書き込んだ内容をそのまま返してくるテスト用プロトコルです。echoプロトコルのクライアントを書きなさい。echoサーバもdaytimeと同じくxinetdに組み込まれているので同じ方法で起動できます。

 

まずはechoサーバを立ち上げます。

/etc/xinetd.d/echoのdisableをnoに書き換えます。

~/prj/study/linux_programming/15$ sudo vi /etc/xinetd.d/echo
[sudo] user のパスワード: 
~/prj/study/linux_programming/15$ cat /etc/xinetd.d/echo
# default: off
# description: An xinetd internal service which echo's characters back to
# clients.
# This is the tcp version.
service echo
{
	disable		= no
	type		= INTERNAL
	id		= echo-stream
	socket_type	= stream
	protocol	= tcp
	user		= root
	wait		= no
}

# This is the udp version.
service echo
{
	disable		= no
	type		= INTERNAL
	id		= echo-dgram
	socket_type	= dgram
	protocol	= udp
	user		= root
	wait		= yes
}

 

 xinetdの設定をリロードして、設定を反映させます。

設定反映後、echoサーバが立ち上がっているかを確認します。

~/prj/study/linux_programming/15$ sudo systemctl reload xinetd
~/prj/study/linux_programming/15$ netstat --tcp --listen
稼働中のインターネット接続 (サーバのみ)
Proto 受信-Q 送信-Q 内部アドレス            外部アドレス            状態      
tcp        0      0 0.0.0.0:netbios-ssn     0.0.0.0:*               LISTEN     
tcp        0      0 localhost:domain        0.0.0.0:*               LISTEN     
tcp        0      0 0.0.0.0:microsoft-ds    0.0.0.0:*               LISTEN     
tcp6       0      0 [::]:echo               [::]:*                  LISTEN     
tcp6       0      0 [::]:netbios-ssn        [::]:*                  LISTEN     
tcp6       0      0 [::]:daytime            [::]:*                  LISTEN     
tcp6       0      0 [::]:microsoft-ds       [::]:*                  LISTEN     
~/prj/study/linux_programming/15$ 

 

作ったプログラムは以下。

daytimeとほぼ同じです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.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"), "echo");
    f = fdopen(sock, "r+");
    if(!f){
        perror("fdopen(3)");
        exit(1);
    }

    fputs("test echo\n", f);
    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;
    if((err = getaddrinfo(host, service, &hints, &res)) != 0){
        fprintf(stderr, "getaddrinfo(3): %s\n", gai_strerror(err));
        exit(1);
    }

    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;
        }

        // success
        freeaddrinfo(res);
        return sock;
    }
    fprintf(stderr, "socket(2)/connect(2) failed.\n");
    freeaddrinfo(res);
    exit(1);
}

 

実行結果は以下。

~/prj/study/linux_programming/15$ ./a.out 
test echo
~/prj/study/linux_programming/15$  

 

まとめ

15章の内容はこんな感じです。

16章はまた今度。

 

おわり。

 

【スポンサーリンク】