ホームページへ戻る
 

上へ戻る
 

    ネットワーク入門  3  プログラミング
by H.Y 3月 13 11:22:33 JST 2002

目次

1。パイプ
1ー1。pipe 関数
例題1) pipe を作成し、データを書き込み、それを読みだす。
例題2)親・子 プロセス間でパイプ通信 (その1)
例題3) 親プロセスと、置換された子プロセスとのパイプ通信
1ー2。dup 関数
2。名前付きパイプ FIFO (First IN First Out)
2ー1。FIFO の作成
1)コマンドラインからの作成
2)プログラムで作成する。
2ー2。ファイルの存在のチェック
2ー3。FIFO によるプロセス間通信
1)FIFO のオープン
2)例題
3。ソケット
3ー1。ソケット接続
3ー2。ローカルな クライアント・サーバのソケット通信プログラム例
3ー3。ソケットの属性
1)ソケットのドメイン
2)ソケットのタイプ
3)ソケットのプロトコル
3ー4。ソケットの作成
3ー5。ソケットのアドレス
1)AF_UNIX ドメイン
2)AF_INET ドメイン
3)INADDR_ANY アドレス
3ー6。ソケットの命名
3ー7。ソケットキューを作る
3ー8。接続の受け入れ
3ー9。接続の要求
3ー10。ソケットのクローズ
3ー11。ソケットオプション
4。ソケット通信
4ー1。ネットワークアドレス操作ルーチン
1) inet_addr
2) inet_aton
3) inet_ntoa
4)ネットワークバイトオーダー
5) hton (host to network short)
4ー2。ネットワーク情報
1)ホストデータベース関数
2)サービスとポート番号に関する情報を得る関数
3)現在のホスト名を得る関数
4ー3。プログラム例
1)サーバクライアント通信例
2)ネットワーク情報の取得例
4ー4。複数のクライアントとの通信
4ー5。セレクト( select )
1)実行例1 select による キイ入力待ち
2)実施例2 select による複数クライアントへの対処


============================================================================================

1。パイプ
パイプとは、あるプロセスからのデータフローを別のプロセスに繋ぐこと。一般には
あるプロセスの出力を別のプロセスの入力に接続する。
2つのプログラム間でデータを受け渡すための方法としては、 高水準の関数の popen関数と
pclose関数がある。しかし、この関数はシェルの起動のオーバーヘッドがあるので、より低水準
の関数である pipe 関数を説明する。
パイプを使うと親プロセスと子プロセスの間でメッセージのやりとりができる。このやり取り
のしかたには次の3つの方法がある。
(1) 親プロセスと、fork() でコピーした子プロセス間の通信。
(2) 親プロセスと、exec で置換した子プロセス間の通信。親のフィイルディスクリプタを
子プロセスに引き渡す。
(3)親プロセスと、exec で置換した子プロセス間の通信。dupでファイルディスクリプタを
コピーして、標準入力に割り当て、これを使って通信する。

1ー1。pipe 関数
----------------------------------------
#include <unistd.h>

intp pipe(int file_descriptor[2] );
----------------------------------------
file_descriptor : 2つのファイルディスクリプタを要素に持つ配列
file_descriptor[0] : パイプを読むためのディスクリプタ
file_descriptor[1] : パイプに書くためのディスクリプタ
成功した場合は 0 、失敗した場合は -1 をかえし、errno をセットする。

例題1) pipe を作成し、データを書き込み、それを読みだす。
同一プロセスでのパイプの利用。単にパイプのデモである。
------ pipe1.c ----------------------------------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
int data_processed;
int file_pipes[2];
const char some_data[] = "パイプに書き込みます。";
char buffer[BUFSIZ + 1];

memset(buffer, '\0', sizeof(buffer)); //メモリクリヤ・オール

if (pipe(file_pipes) == 0) {
data_processed = write(file_pipes[1], some_data, strlen(some_data));
printf("%d バイトを書き込みました。\n", data_processed);
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf("読みだし: %d バイト: %s\n", data_processed, buffer);
exit(EXIT_SUCCESS);
}
exit(EXIT_FAILURE);
}

例題2)親・子 プロセス間でパイプ通信 (その1)
pipe を作成する。次にプロセスを fork して、このパイプを通して,親から子プロセスに
データを送る。
fork() で複製した子プロセスは、親プロセスのファイルディスクリプタを、そのまま
引き継ぐ。
--------- pipe2.c -----------------------------------------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
int data_processed;
int file_pipes[2];
const char some_data[] = "パイプメッセージです。";
char buffer[BUFSIZ + 1];
pid_t fork_result;

memset(buffer, '\0', sizeof(buffer));

if (pipe(file_pipes) == 0) {
fork_result = fork();
if (fork_result == -1) {
fprintf(stderr, "Fork 失敗しました。");
close(file_pipes[0]);
close(file_pipes[1]);
exit(EXIT_FAILURE);
}
if (fork_result == 0) { //子プロセス
data_processed = read(file_pipes[0], buffer, BUFSIZ);
printf("[子] 読みだし %d バイト: %s\n", data_processed, buffer);
exit(EXIT_SUCCESS);
}
else { //親プロセス
data_processed = write(file_pipes[1], some_data,strlen(some_data));
printf("[親] 書き込み %d バイト\n", data_processed);
}
}
close(file_pipes[0]);
close(file_pipes[1]);
exit(EXIT_SUCCESS);
}

例題3) 親プロセスと、置換された子プロセスとのパイプ通信
子プロセスを exec で置換すると、親プロセスがオープンしたパイプは、子プロセスから
見えなくなる。この問題を回避するために、exec で起動するプログラムに目的の
ファイルディスクリプタを引数として渡す。
-------------- pipe3.c ----------------------------------------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
int data_processed;
int file_pipes[2];
const char some_data[] = "パイプメッセージです。";
char buffer[BUFSIZ + 1];
pid_t fork_result;

memset(buffer, '\0', sizeof(buffer));

if (pipe(file_pipes) == 0) {
fork_result = fork();
if (fork_result == (pid_t)-1) {
fprintf(stderr, "Fork 失敗ですがな");
close(file_pipes[0]);
close(file_pipes[1]);
exit(EXIT_FAILURE);
}

if (fork_result == 0) { //子プロセス
sprintf(buffer, "%d", file_pipes[0]); //ファイルディスクリプタを渡す
(void)execl("pipe4", "pipe4", buffer, (char *)0); //pipe4 に置換
exit(EXIT_FAILURE);
}
else {
data_processed = write(file_pipes[1], some_data,strlen(some_data));
printf("[親] %d - 書き込み %d バイトでっせ\n", getpid(),
data_processed);
}
}
close(file_pipes[0]);
close(file_pipes[1]);
exit(EXIT_SUCCESS);
}
------------- pipe4.c 置換される子プロセス--------------------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]){
int data_processed;
char buffer[BUFSIZ + 1];
int file_descriptor;

memset(buffer, '\0', sizeof(buffer));
sscanf(argv[1], "%d", &file_descriptor); //親のファイディスクリプタを得る
data_processed = read(file_descriptor, buffer, BUFSIZ);

printf("[子プロセス] %d - 読み込み %d バイトでっせ: %s\n", getpid(),
data_processed, buffer);
exit(EXIT_SUCCESS);
}

1ー2。dup 関数
置換した子プロセスと親プロセス間の、パイプによる通信では、子プロセスを置換する時に
親のファイルディスクリプタを渡さなければならない。これを避ける方法を説明する。
パイプのファイルディスクリプタに、標準入出力の 0 や 1 を用いる。
この場合、親プロセスの処理は複雑になるが、子プロセスの処理が、非常に簡単になる。

dup は old_descriptor の複製を作る。
---------------------------------------------------------------
#include <unistd.h>

int dup(int old_descriptor);
int dup2(int old_descriptor, int new_descriptor);
---------------------------------------------------------------
dup は使用されていない最小の値のディスクリプターを新 し いディスクリプターとして
使用する。

dup2 は oldfd の複製として newfd を作成する。必要ならば最初に newfd をクローズ
(close)する。
使い方)
ファイルディスクリプタ 0 をクローズした後、dup()を呼び出す。すると、dup()が返す
新しいファイルディスクリプタは 0 になる。この新しいファイルディスクリプタは、既存の
ファイルディスクリプタを複製したものである。したがって標準入力を使って、dup() に渡
したファイルディスクリプタを持つファイルやパイプにアクセスできるようになる。
つまり、同じファイルまたはパイプに関連づけられたファイルディスクリプタが2つ作成
され、その内の1つが標準入力になる。
------------------ pipe5.c ----------------------------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main() {
int data_processed;
int file_pipes[2];
const char some_data[] = "パイプメッセージなり";
pid_t fork_result;

if (pipe(file_pipes) == 0) {
fork_result = fork();
if (fork_result == (pid_t)-1) {
fprintf(stderr, "Fork 失敗です。");
exit(EXIT_FAILURE);
}

if (fork_result == 0) { //子プロセス
close(0);
dup(file_pipes[0]); //ファイルディスクリプタ複製する
//新ディスクリプタは 0
close(file_pipes[0]); //旧ディスクリプタは2つ共、閉じる。
close(file_pipes[1]);
execlp("od", "od", "-x", (char *)0); //プロセスを置換
exit(EXIT_FAILURE);
}
else {
close(file_pipes[0]); //読み取りはいらない
data_processed = write(file_pipes[1], some_data,
strlen(some_data));
close(file_pipes[1]); //書き込みももういらない
printf("[親] %d - 書き込み %d バイトですがな\n", (int)getpid(),
data_processed);
}
}
exit(EXIT_SUCCESS);
}
置換された子プロセス od は端末からの入力があるまで待つ。
親プロセスが、パイプに書き込むと、od はこれを読み取る。読み取りが終了すると
(read が 0 を返すと)、od は終了する。

2。名前付きパイプ FIFO (First IN First Out)
名前付きパイプは、名前を付けてパイプをオープンできるので、どのプロセスからでも
(親子関係にないプロセスでも)通信ができる。
2ー1。FIFO の作成
1)コマンドラインからの作成
(1)デバイスファイルを作る場合
mknod <file_name> <major_number> <mimor_number>
(2)FIFO を作る場合
mknod <file_name> p
mkfifo <file_name>
実行例)
(a) mknod fifo_1 p
(b) mkfifo fifo_2
(b) ls -l fifo*
prw-r--r-- 1 root root 0 3月 11 10:15 fifo_1
prw-r--r-- 1 root root 0 3月 11 10:20 fifo_2
(c) echo "これは FIFO のテストでっせ!" > fifo_1
(d) 他のターミナルを起動して、 cat fifo_1
(3) FIFO の削除
rm fifo_1 fifo_2
2)プログラムで作成する。
プログラムで FIFO を作成するには、mknod か mkfifo 関数を用いる。
------------------------------------------------------------------------
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int mkfifo(const char *filename, mode_t mode);
int mknod(const char *filename, mote_t mode | S_IFIFO, (dev_t) 0 );
------------------------------------------------------------------------
mode //sys/stat.h で定義
S_IFREG : 一般ファイル
S_IFCHR : キャラクタ型デバイス
S_IFBLK : ブロック型デバイス
S_IFIFO : FIFO
dev デバイスファイルの場合は major番号とminor番号
FIFO の場合は 0
1)例題1 ---------- fifo1.c -------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
int res = mkfifo("./my_fifo", 0777);
if (res == 0)
printf("FIFO 作成されました。\n");
exit(EXIT_SUCCESS);
}
2)例題2 ------- fifo1a.c ------------------
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {

if ( mknod("my_fifo", 0666 | S_IFIFO, (dev_t) 0) == -1) {
perror("mknod");
return 1;
}
else
printf("FIFO 作成されました。\n");
return 0;
}
2ー2。ファイルの存在のチェック
ファイルの存在をチェックするには、 access システムコールを使う。
---------------------------------------------
#include <unistd.h>

int access(const char *filename, int mode);
---------------------------------------------
mode パーミッションのビットパターン unistd.hで定義
R_OK : 読み込み可 (4)
W_OK : 書き込み可 (2)
X_OK : 実行 可 (1)
F_OK : ファイルの存在のチェック (0)
戻り値
許可されている場合 : 0
許可されていない、エラー : -1 errno セット
-------------- fifo_acc.c ----------------------
#include <unistd.h>
#include <sys/stat.h> //S_IFIFO
#include <sys/types.h>
#include <stdio.h>

#define NAME "fifo-3"


int main(){
if(access(NAME,F_OK) == -1){ //NAME の存在チェック
printf("FIFO をつくりまっせ。\n");
if(mknod(NAME,0666 | S_IFIFO,(dev_t)0 ) == -1){
printf("FIFO つくれまへん。");
return 1;
}
else{
//getchar();
//unlink(NAME);
return 0;
}
}
else
printf("%s はすでにおます。\n",NAME);
return 0;
}
2ー3。FIFO によるプロセス間通信
1)FIFO のオープン
FIFOのデータの流れは、1方法である。それゆえに、FIFOを読み書きモードでオープン
することは出来ない。2つのプロセスで双方向にメッセージをやり取りする場合には、
FIFO を2つ使用して通信すればよい。
FIFO と通常ファイルとの違いのもう1つの点は、open 関数の第2パラメータ
open_flag で指定する O_NONBLOCK オプションに関するものである。

open 時の open_flag である O_RDONLY, O_WRONLY, O_NONBLOCK の組み合わせについて
説明する。
(1) open(const char *name, O_RDONLY);
この場合、open はブロックされる。なんらかのプロセスが同じFIFO を書き込み用に
オープンするまで、戻らない。
(2) open(const char *name, O_RDONLY | O_NONBLOCK);
この場合、FIFO が他のプロセスで、書き込み用にオープンされていなくても、
直ちに戻る。
(3) open(const char *name, O_WRONLY);
この場合、何らかのプロセスが同じ FIFO を読み取り用にオープンするまで、
open の呼び出しはブロックされます。
(4) open(const char *name, O_WRONLY | O_ONOBLOCK);
この場合、open の呼び出しは直ちに戻る。但し、読み取り用にFIFOをオープン
しているプロセスがない場合は、open は エラー(-1)を返し、FIFO はオープンされない。

2)例題
受信プロセスで fifo-1 がない場合は作成し、メッセージがくるまで待つ。
送信プロセスはこの fifo-1 にメッセージを1行毎書き込む。
Crtl+D の入力で終了する。
----------------- fifo-tx1.c -----------------
#include <unistd.h>
//#include <sys/types.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>

char *FIFO ="fifo-1";

int main(){
int ff, nbyte;
char buff[BUFSIZ];

if( (ff = open(FIFO, O_WRONLY)) == -1){
perror("tx: open error");
return 1;
}

printf("FIFO = %d \n",ff);

//----input data from stdin until eof(C-d)--
while((nbyte = read(0, buff, BUFSIZ)) > 0){
if((write(ff, buff, nbyte)) != nbyte){
perror("tx:write error");
close(ff);
return 1;
}
}
if(close(ff) == -1){
perror("tx:close error");
return 1;
}

return 0;
}
----------------- fifo-rx1.c ----------------
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h> //S_IFOFO
#include <fcntl.h> //O_RDONLY
#include <stdio.h>

char *FIFO="fifo-1";

int main(){
int ff, nbyte;
char buff[BUFSIZ];
int i=0;

if(access(FIFO, F_OK) == -1){
if(mknod(FIFO,0666 |S_IFIFO,(dev_t)0 ) == -1){
perror("reciever:mknod error");
return 1;
}
}

printf("Rx:Waiting data \n");
if((ff = open(FIFO,O_RDONLY)) == -1){
perror("reciever:open error");
unlink(FIFO); //FIFO を削除する
return 1;
}

printf("RX:FIFO = %d open succeeded!\n",ff);

while((nbyte = read(ff,buff,BUFSIZ)) > 0)
write(1, buff,nbyte);

close(ff);
unlink(FIFO); //FIFO を削除する
if(nbyte == -1){
perror("reciever:write error");
return 1;
}

return 0;
}
-----------------------------------------------
実行方法
(1) ./fifo-rx1
(2) 別のターミナル(B)で ./fifo-tx2
(3) ターミナルBで、 メッセージ入力
(4) ターミナルBで、Ctrl-D
---------- 他の方法 ファイルの内容を送る--
(2) cat ファイル名 > fifo-1
---------- もう1つの方法 -----------------
(2) ./fifo-tx1 < ファイル名
課題
fifo-tx1 を先に起動するとエラーで終了する。
その理由を open 関数のフラグより説明せよ。
エラー終了しない方法も考えること。

3。ソケット
ソケットはパイプの概念を拡張したもので、ソケットインターフェイスと言う通信手段を
提供する。ソケットインターフェイスは次の特徴を持つ。
(1)親子関係にないプロセスの間で、通信できる。
(2)ネットワーク間での通信が出来る。
(3)1つのソケットで、双方向通信ができる。
(4)ローカルな、あるいはネットワーク間で、クライアント・サーバシステムでの通信ができる。
3ー1。ソケット接続
クライアント・サーバ間での、ソケット接続のための手順を説明する。
---------- サーバ側の手順 --------------------------
(1)サーバが socket システムコールでソケットを作成する。
(2)サーバは bind システムコールをしようして、
(2-1)ローカルソケットの場合は、ソケットに名前を付ける。
(2-2)ネットワークソケットの場合は、クライアントから接続可能な特定のネットワーク
に応じたサービス識別子 (ポート番号/アクセス番号) をつける。
(3)サーバは、名前をつけたソケットに、クライアントが接続してくるのを待つ。
(4)同時に、複数のクライアントが接続してきた時のために、listenシステムコールによって
接続用のキューを作成する。
(5)サーバは accept システムコールによって接続をうけいれることができる。
(6)サーバが accept を呼びだすと、名前のついたソケットとは別に、新しいソケットが
作成される。
(7)この新しいソケットは、接続してきたクライアントと通信するためにだけ使われる。
(8)名前の付いたソケットは、そのまま残っており、他のクライアントからの接続に利用
することができる。
(9)あとから接続してきたクライアントは、サーバが対応できるようになるまで、
リスンキューで待機する。
---------- クライアント側の手順 ----------------------
(1)socket を呼び出して、名前のないソケットを作成する。
(2)connect を呼びだして、名前の付いたソケットをアドレスに使ってサーバとの接続を
確立する。
(3)接続の確立後は、ソケットをファイルディスクリプタのように扱い、双方向の通信が
できる。
3ー2。ローカルな クライアント・サーバのソケット通信プログラム例
つぎにソケットを使った、ローカルな クライアント・サーバ 通信の例を示す。
------------------ client1.c ローカルクライアント ----------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main() {
int sockfd;
int len;
struct sockaddr_un address;
int result;
char ch = 'A';
char chr;

// クライアントのソケットを作る
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

// サーバ側と同じ名前でソケットの名前を指定
address.sun_family = AF_UNIX;
strcpy(address.sun_path, "server_socket");
len = sizeof(address);

//このソケットをサーバのソケットに接続する
result = connect(sockfd, (struct sockaddr *)&address, len);

if(result == -1) {
perror("oops: client1");
exit(1);
}

// sockfd を使って読み書きができる。
write(sockfd, &ch, 1);
read(sockfd, &chr, 1);
printf("サーバへ送った文字は = %c\n", ch);
printf("サーバからの文字は = %c\n", chr);
close(sockfd);
exit(0);
}

---------- server1.c ローカルクサーバ ----------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>

int main() {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_un server_address;
struct sockaddr_un client_address;

//古いソケットを削除し、名前のないソケットを作成する。
unlink("server_socket"); //server_socket を削除する
server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

//bind でソケットに名前を付ける。
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "server_socket");
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

//接続キュウーを作成し、クライアントの接続を待つ。
listen(server_sockfd, 5);
while(1) {
char ch;

printf(" サーバは接続を待ってますねん。\n");

//接続を受け付ける
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);

//接続確立。client_sockfd に対して読み書きができる。
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
-------------------------------------------------------------
実行方法
(1) ./server1
(2) ls -l server_socket
srwxr-xr-x 1 root root 0 3月 11 16:58 server_socket
(3) 別のターミナルで ./client1
(4) sever1 の終了は Ctrl+C

3ー3。ソケットの属性
ソケットは、ドメイン(domain), タイプ(type)、プロトコル(protocol) の3つの属性を持つ。
またソケットはアドレスをもち、このアドレスがソケットの名前として使われる。
1)ソケットのドメイン
(1) AF_UNIX
ネットワークに接続されていない、1台の PC上でも使用できるドメイン。
そのプロトコルは、ファイル入出力であり、アドレスは絶対ファイル名である。
(2) AF_INET
同一マシン、 またはネットワーク上の異るマシン間の通信に使用する。
アドレスは IPアドレスと、ポート番号であらわす。
2)ソケットのタイプ
(1)ストリーム(stream) SOCK_STREAM
信頼性の高い、順次双方向のバイトストリームに基ずく接続。
フラグメント化されたメッセージも正しくもとに戻して受信される。
AF_INET ドメインでは TCP/IP接続で実装されている。
(2)データグラム(datagram) SOCK_DGRAM
接続の確立と維持を実行しない。データグラムのサイズに制限もある。
また、データグラムメッセージは単独のメッセージとして送信され、途中で喪失したり、
重複したり、順序がいれかわったりすることもある。
AF_INET ドメインでは UDP/IP 接続で実装されている。
情報サービスに対する1度だけの問い合わせや、ステータス情報の定期的な提供、
プライオリティの低いログの収集などに適している。
接続の確立が必要ないので、高速である。またサーバが死んだ場合にクライアントを
立ち上げ直す必要もない。
3)ソケットのプロトコル
AF_INET と AF_UNIX ドメインのみを使用する限りは、デフォルト以外のプロトコルを選択
する必要はない。

3ー4。ソケットの作成
----------------------------------------------------
#include <sys/types.h>
#include <sys/socket.h>

int socket(int domain, int type, int protocol);
-----------------------------------------------------
帰り値は ディスクリプタ。このディスクリプタに対して、read、write, close できる。

例) int socket_fd;
socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);

3ー5。ソケットのアドレス
各、ソケットドメインには独自のアドレス形式がある。
1)AF_UNIX ドメイン
AF_UNIX ドメインの場合は sys/un.h で定義された構造体sockaddr_un で表現する。
-------- sockaddr_un -----------------------
struct sockaddr_un {
sa_family_t sun_family; //AF_UNIX sa_family_t は short
char sun_path; //path name
};
--------------------------------------------
2)AF_INET ドメイン
AF_INET ドメインでは netinet.h で定義される sockaddr_in で表現される。
---------------------------------------------------
struct sockaddr_in {
short int sin_family; //AF_INET
unsigned short int sin_port; //Port Number
struct in_addr sin_adr; //IP Address
};

IP Address 構造体は
sturuct in_addr {
unsigned long int s_addr; //1つの 32 ビット値で表す
};
---------------------------------------------------
3)INADDR_ANY アドレス
この定数は /usr/include/linux/in.h で定義されていて、次の通りである。
#define INADDR_ANY ((unsigned long int )0x00000000)
このアドレスを指定すると、全ての IPアドレスから接続できる。

3ー6。ソケットの命名
他のプロセスからソケットを利用できるようにするには、サーバプロセスがソケットに
名前を付ける必要がある。
AF_UNIX ドメインの場合は、ファイルのパス名をソケットに関連づける。
AF_INET ドメインの場合は、IPアドレスとポート番号で、ソケットに関連づける。
-------------------------------------------------------------------------
#include <sys/socket.h>

int bind(int socket, const struct sockaddr *address, size_t address_len);
---------------------------------------------------------------------------
bind システムコールと使って、名前のないソケット socket に
address で指定されたアドレスを割り当てる。
アドレスの長さと形式は、アドレスファミリで異るので、bind を呼び出す時は、
汎用のポインタ型 ( struct sockaddr *) にキャストする必要がある。
bind は成功すると 0 、失敗すると -1 を返し errno をセットする。
3ー7。ソケットキューを作る
ソケットで接続を受け付けるためには、保留中の要求を格納するめのキューを、
サーバプログラムで作成する。
-----------------------------------------
#include <sys/socket.h>

int listen(int socket, int backlog);
-----------------------------------------
socket : ソケットのディスクリプタ
backlog : 同時に接続を受け付ける接続数。
帰り値は成功すると 0 、失敗すると -1 で errno をセットする。
3ー8。接続の受け入れ
サーバは、ソケットを作成して名前を付けた後、accept システムコールを使って、
該当するソケットへの接続を待つことが出来る。
-----------------------------------------------------------------------
#include <sys/socket.h>

int accept(int socket, struct sockaddr *address, size_t *address_len);
-----------------------------------------------------------------------
accept は socket で指定したソケットに、クライアントが接続を試みると戻る。
逆に、該当 socket で待機しているクライアントがないと、accept システムコールは、
クライアントが接続してくるまでブロックされる。このブロックは動作は fcntl 関数で
変更することができる。

address : クライアントのアドレス。address が必要ない場合は ヌルポインタでも良い。
address_len : 受け取る、クライアントのアドレスの長さを指定する。指定した長さが
実際のクライアントのアドレスより短い場合は、越えた部分は切り捨てられる。
accept 実行後は取得したアドレスの長さが設定される。
address がヌルポインタの場合は address_len もヌルポインタを指定する。

accept が戻った時のクライアントは、ソケットのキューの内で保留中の最初の接続である。
accept はクライアントと接続するための、新しいソケットを作成し、そのディスクリプタ
を返す。

3ー9。接続の要求
クライアントプログラムは、connect システムコールを呼び出し、名前のないソケットと、
サーバのリスンソケットとの間の接続を確立することにより、サーバと接続する。
-----------------------------------------------------------------------------
#include <sys/socket.h>

int connect(int socket, const struct sockaddr *address, size_t address_len);
-----------------------------------------------------------------------------
connetct は、socket で指定した名前のないソケットを、address で指定した address_len
の長さのアドレスを持つサーバソケットに接続する。
成功すると 0 を返す。失敗すると -1 を返し errno をセットする。
接続をただちにセットアップ出来ない場合、connect はタイムアウト期間ブロックされる。
このタイムアウト期間を経過すると、以上終了する。
このブロック動作は、O_NONBLOCK フラグで変更できる。 O_NONBLOC フラグを設定すると、
接続がすぐにセットアップ出来ない場合は、connect は失敗する。errno には EINPROGRESS
がセットされ、非同期で接続がセットアップされる。非同期の接続にはselect の助けを
かりる。

3ー10。ソケットのクローズ
close を呼び出すと、サーバとクライアントのソケット接続を終了する。ソケットは常に
両側でクローズする必要がある。
サーバ側では、read が 0 を返したときに close する必要があるが、close がロックされる
場合がある。それは、
(1)転送されていないデータがソケットにあり、
(2)ソケットが接続指向型で
(3)SOCK_LINGER オプションが設定されている場合。setsockopt を参照せよ。
3ー11。ソケットオプション
ソケット接続の動作を制御するためのオプション。非常に多いので、その一部を説明する。
ソケットオプションを操作するには、次の関数を使う。
--------------------------------------------------------------
#include <sys/socket.h>

int setsockopt(int socket, int level, int option_name,
const void *option_value, size_t option_len);
--------------------------------------------------------------
level = SOL_SOCKET : ソケットレベルの設定
プロトコル番号 : TCP,UDP などのプロトコルレベルの設定
プロトコル番号は netinet/in.h をみるか
getprotbyname で値を取得する
option_name : 設定するオプションを指定する
option_value: option_lenバイトの長さをもつ値で、下位のプロトコルにそのまま渡される
ソケットレベルのオプションは sys/socket.h で定義されていて、次の様なものがある。
SO_DEBUG :デバッグ情報を有効にする。
SO_KEEPALIVE:定期的に転送を行って、接続をアクティブに保つ。
SO_LINGER :転送を終えてから、クローズする。
SO_REUSERADDR:ソケットの最利用をすぐできるように設定
SO_DEBUG,SO_KEEPALIVE では、整数値の option_value を使ってオプションを有効/無効にする。
1 の場合は有効で、0 の場合は無効になる。
SO_LINGER では sys/socket.h で定義されている linger 構造体を使ってオプションの状態と
遅延時間を指定する。
setsockopt は成功すると 0 を返す。失敗すると -1 を返す。
-------------------- 実施例 ---------------------------------------------------
ソケットの再利用は、デフォルトでは2分間できないので,これをすぐ再利用できる様に
変更する。
int val = 1;
setsockopt(fd_socket,SOL_SOCKET,SO_REUSEADDR, &val, sizeof(val) );
------------------------------------------------------------------------------

4。ソケット通信
4ー1。ネットワークアドレス操作ルーチン
--------------------------------------------
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

unsigned long int inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
------------------------------------------------------
#include <netinet/in.h>

unsigned short int htons(unsigned short int hostshort);
-------------------------------------------------------
1) inet_addr
inet_addr() 関数は、インターネットホストのアドレス cp をドット付き 10進記法から
ネットワークバイトオーダでのバイナリ値へ変換して返す。
例: address.sin_addrs_addr = inet_addr("192.168.10.1");
2) inet_aton
inet_aton() は、インターネットホストのアドレス cp をドット付き 10進記法から
バイナリ値へ変換する。結果は inp が指している構造体に代入される。
返り値は、指定したアドレスが正当ならば 0以外、不当なら 0 である。
3) inet_ntoa
inet_ntoa() 関数は、ネットワークバイトオーダでのインターネットホストアドレス in を
ドット付き 10進記法へ変換する。 文字列は静的に割当てられたバッファへ返るので、もう
1 回この関数を呼ぶと文字列は上書きされる。
4)ネットワークバイトオーダー
INET プロトコルでは、データの並びは ビッグエンディアンである。
インテル系 CPU はリトルエンディアンを採用している。
[リトルエンディアンの例]
-------------------------------------------
ポートアドレス = 3000 = 0x0BB8
メモリ内では アドレス 100000 : B8
100001 : 0B
------------------------------------------------------------
IP address = 192.168.10.1 = 0xC0.0xA8.0x0A.0x01 = 0xC0A80A01
memory address 100000 : 01
100001 : 0A
100002 : A8
100003 : C0
5) hton (host to network short)
CPU 固有のバイトオーダーをネットワークバイトオーダに変換する。
例: addr.sin_port = hton(3000);

4ー2。ネットワーク情報
1)ホストデータベース関数
コンピュータ名から、IP アドレスを得る関数である。この関数は/etc/hosts や DNS
を参照して必要な処理を行う。引数の値をセットしてコールする。
----------------------------------------------------------------------
#include <netdb.h>

struct hostent *gethostbyaddr(const void *addr, size_t len, int type);
struct hostent *gethostbyname(const char *name);

-------------- hostent 構造体の一部 ----------------------------------
struct hostent {
char *h_name; //name of the host
char **h_alises; //list of aliases
int h_addrtype; //address type
int h_length; //length in bytes of the address
char **h_addr_list; //list of address (network order)
};

2)サービスとポート番号に関する情報を得る関数
-------------------------------------------------------------------
#include <netdb.h>

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
------------------ -------------------------------------------------
proto : SOCK_STREAM の場合は "tcp" を指定
SOCK_DGRAM の場合は "udp" を指定
----------- servent 構造体の一部 ------------------------
struct servent {
char *s_name; //name of the service
char **s_aliases; //list of aliases
int s_port; //The IP port number
char *proto; //The service type ex. "udp", "tcp"
};
3)現在のホスト名を得る関数
--------------------------------------------------
#include <unistd.h>

int gethostname(char *name, int namelength);
-------------------------------------------------

4ー3。プログラム例
1)サーバクライアント通信例

-------------- client3.c --------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch = 'A';

//クライアント用のソケットを作成
sockfd = socket(AF_INET, SOCK_STREAM, 0);

//ソケットに、サーバと同じ名前をつける
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(5000);
len = sizeof(address);

//サーバに接続する
result = connect(sockfd, (struct sockaddr *)&address, len);

if(result == -1) {
perror("oops: client3");
exit(1);
}

//接続後は 読み書き可能になる
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
--------------- server3.c --------------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;

//名前のないソケットを作成する。
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

//ソケットに名前を付ける
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(5000);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

//接続キューを作成する。接続は 10 まで
listen(server_sockfd, 10);
while(1) {
char ch;

printf("server waiting\n");

//接続を待つ
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);

//接続後は、クライアントのたいして読み書きできる
read(client_sockfd, &ch, 1);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
}
}
----------------------------------------------------------------
[実行方法] server1 / client1 と同様である。
(1) ./server1 &
(2) ls -l server_socket
srwxr-xr-x 1 root root 0 3月 11 16:58 server_socket
(3) netstat -a | grep 5000
tcp4 0 0 localhost.localdom:5000 localhost.localdo:32777 TIME_WAIT
(4) ./client1
(5) fg
(6) Ctrl+C
2)ネットワーク情報の取得例
----------------- getname.c -----------------------------------------
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>

int main(int argc, char *argv[]) {
char *host, **names, **addrs;
struct hostent *hostinfo;

//コマンドの引数がない時は $HOTNAME を host で用いる
if(argc == 1) {
char myname[256];
gethostname(myname, 255);
host = myname;
}
else
host = argv[1]; //引数のホスト名を用いる

//ホスト名からホスト情報を得る
hostinfo = gethostbyname(host);
if(!hostinfo) {
fprintf(stderr, "cannot get info for host: %s\n", host);
exit(1);
}

//ホスト情報のプリント
printf("ホスト %s の結果は: \n", host);
printf("ホスト名 : %s\n", hostinfo -> h_name);
printf(" 別名 :");
names = hostinfo -> h_aliases;
while(*names) {
printf(" %s", *names);
names++;
}
printf("\n");

//ホスト が IPアドレスを持っていない時はエラー
if(hostinfo -> h_addrtype != AF_INET) {
fprintf(stderr, "IP アドレスのないホストです。\n");
exit(1);
}

//ホストの IPアドレスのプリント
addrs = hostinfo -> h_addr_list;
while(*addrs) {
printf("IP アドレス: %s", inet_ntoa(*(struct in_addr *)*addrs));
addrs++;
}
printf("\n");
exit(0);
}
-----------------------------------------------------------------------
実行方法
(1) ./getname //$HOSTNAME が用いられる
(2) ./getname <ホスト名>

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

int main(int argc, char *argv[]) {
char *host;
int sockfd;
int len, result;
struct sockaddr_in address;
struct hostent *hostinfo;
struct servent *servinfo;
char buffer[128];

//引数がないときは localhost を用いる
if(argc == 1)
host = "localhost";
else
host = argv[1];

//ホスト名からホスト情報を得る
hostinfo = gethostbyname(host);
if(!hostinfo) {
fprintf(stderr, "no host: %s\n", host);
exit(1);
}

//サービス名 daytime が有効かをチェック
servinfo = getservbyname("daytime", "tcp");
if(!servinfo) {
fprintf(stderr,"no daytime service\n");
exit(1);
}
printf("daytime port is %d\n", ntohs(servinfo -> s_port));

//ソケットを作成
sockfd = socket(AF_INET, SOCK_STREAM, 0);

//connect 関数のために アドレスを構築
address.sin_family = AF_INET;
address.sin_port = servinfo -> s_port;
address.sin_addr = *(struct in_addr *)*hostinfo -> h_addr_list;
len = sizeof(address);

//サーバに接続し、情報をえる
result = connect(sockfd, (struct sockaddr *)&address, len);
if(result == -1) {
perror("oops: getdate");
exit(1);
}

result = read(sockfd, buffer, sizeof(buffer));
buffer[result] = '\0';
printf("read %d bytes: %s", result, buffer);

close(sockfd);
exit(0);
}
--------------------------------------------------
実行方法:
(1)/etc/services で daytime サービスを確認する。
(2)また /etc/inetd.con または xinetd.d/daytime でサービスが有効になっていることを
確認する。
(3) ./getdate //この場合は自ホストの daytime サービスを利用
(4) ./getdate <他のホスト名>
この場合他のホストが daytime サービスポートをとじていると、情報は得られない。

4ー4。複数のクライアントとの通信
これまでのサンプルプログラムでは、サーバに接続したクライアントの処理は、同時には
1つにかぎられてた。これを同時に複数のクライアントの接続を処理することが出来るように
変更する。

具体的には、サーバ側で fork を呼びだす。サーバが fork で自分自身のコピーを作成する
と、オープンされたソケットは新しい子プロセスに受け継がれる。子プロセスは接続してきた
クライアントと通信し、サーバは他のクライアントからの接続を待つ。
子プロセスを作成しても、子プロセスの終了を待つわけではないので、サーバ側では
SIGCHLD シグナルを無視し、ゾンビが発生しないようにする必要がある。
------------- server4.c --------------------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <signal.h> //new
#include <unistd.h>

int main() {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;

//名前のないソケットを作成する。
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

//ソケットに名前を付ける
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(5000);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

//接続キューを作成する。接続は 10 まで
listen(server_sockfd, 10);
signal(SIGCHLD, SIG_IGN); //new シグナルを設定
while(1) {
char ch; //new

printf("server waiting\n");

//接続を待つ
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);

//プロセスのコピー(子プロセス)を作り、子プロセスをチェックする
if(fork() == 0) { //子プロセスナラバ
read(client_sockfd, &ch, 1);
sleep(5);
ch++;
write(client_sockfd, &ch, 1);
close(client_sockfd);
exit(0);
}
// 親プロセスの時は、ソケットを閉じる。親プロセスはなにもしない。
else
close(client_sockfd);
}
}
-------------------------------------------------------------------
クライアントから接続があると、サーバは fork で子プロセスを作成して、この子プロセス
が処理をはじめる。接続のたびに fork が実行されるので、子プロセスが巨大である場合
や、協調が必要になる場合もある。したがって、この方法は最適な方法とは言えない。
[実行方法]
(1) ./server4 &
(2) ./client3 & ./client3 & ./client3 & ps
(3) しばらくしてから ps
(4) fg
(5) Ctrl+C

4ー5。セレクト( select )
select は、いくつかのファイル・ディスクリプターの状態が,変化するのを待つ。
オープンされた複数のファイルディスクリプタを、同時に扱う手段を提供する。
select を使うと、同時に複数の低水準ファイルディスクリプタを対象に、入力データの到着、
または出力の完了、を待つことが出来る。
-----------------------------------------------------------------------
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int n, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
-----------------------------------------------------------------------
select は、三つの独立したディスクリプターの集合を監視す る。
n には 監視するファイルディスクリプタの数を設定する。
readfds, writefds, execptfds には ヌルポインタを設定できる。この場合は、
対応するテストは実行されない。
timeout にヌルポインタを指定していて、ファイルディスクリプタの変化がない場合は
select の呼び出しは永久にブロックされる。
タイムアウトの場合は全てのディスクリプタ集合は空になる。
select の帰り値は、次の値になる。
失敗すると -1 errno がセットされる
タイムアウトで 0
その他で ファイルディスクリプタ
返されたファイルディスクリプタの集合を調べるには、次で説明する FD_ISSET マクロ
をつかう。

readfds にリストされたものは読み込みが可能かどうかを監視する。より正確には、
停止(block)せずに読むこができるかを調べる。 特に、ファイル・ディスクリプターが
ファイルの終端(end-of-file)の場合も含まれる。
writefds にリストされたものは停止せずに書き込みが可能かどうかを監視する。
exceptfds にあるものは例外の監視を行なう。
終了する時に、実際にどのディスクリプターの状態が変化したか示すために集合が
変更される。
select システムコールの操作の対象は、fd_set 構造体で、これはオープンされた
ファイルディスクリプタテーブルの集合を表す。この集合を操作するために、次のマクロが
定義されている。
--------- fd_set構造体 を操作する マクロ ----------------------------------------
(1) FD_CLR(int fd, fd_set *fdset);
fdset を空に初期化する。
(2) FD_ISSET(int fd, fd_set *fdset);
fd で指定されたファイルディスクリプタが、fdset で示されるfd_setの要素であれば、
0 以外の値を返す。 1つの fd_set 構造体に保持できるファイルディスクリプタの
最大数は、定数 FD_SETSIZE で定義される。
(3) FD_SET(int fd, fd_set *fdset);
fd で指定されたファイルディスクリプタを fdset に追加する。
(4) FD_ZERO(fd_set *fdset);
fd で指定されたファイルディスクリプタを fdset から削除する。
------------------------------------------------------------------------------
select では、無期限にブロックされるのを防ぐ為に、タイムアウト値を指定できる。
この指定は、 struct timeval 構造体を使う。これは sys/time.h で定義されている。
------------- timeval 構造体 -------------------------------
struct timeval {
time_t tv_sec; //seconds (int 型 sys/types.h 参照)
long tv_usec; //micorseconds
-----------------------------------------------------------
1)実行例1 select による キイ入力待ち
----------------- select1.c ----------------------------
#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main() {
char buffer[128];
int result, nread;

fd_set inputs, testfds;
struct timeval timeout;

FD_ZERO(&inputs); //fd_set構造体 inputs の初期化
FD_SET(0,&inputs); //標準入力を inputs 構造体に追加

// 5.5秒のタイムアウト設定で、キイ入力を待つ
while(1) {
testfds = inputs;
timeout.tv_sec = 5;
timeout.tv_usec = 500000;

result = select(FD_SETSIZE, &testfds, (fd_set *)0,
(fd_set *)0, &timeout);
//タイムアウトの場合は、ループする。エラーで終了。
switch(result) {
case 0:
printf("タイムアウトです。\n");
break;
case -1:
perror("select");
exit(1);

//入力の処理 Ctrl+D で プログラ終了
default:
if(FD_ISSET(0,&testfds)) {
ioctl(0,FIONREAD,&nread); //受信バッファの文字数を得る
if(nread == 0) {
printf("\n プログラム終了です。\n");
exit(0);
}
nread = read(0,buffer,nread);
buffer[nread] = 0;
printf("文字数 %d キイ入力は : %s", nread, buffer);
}
break;
}
}
}

2)実施例2 select による複数クライアントへの対処
------------ server5.c -----------------------------------------------------------
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main() {
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result;
fd_set readfds, testfds;

server_sockfd = socket(AF_INET, SOCK_STREAM, 0);

server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(5000);
server_len = sizeof(server_address);

bind(server_sockfd, (struct sockaddr *)&server_address, server_len);

listen(server_sockfd, 5);

FD_ZERO(&readfds);
FD_SET(server_sockfd, &readfds);

//クライアントの接続を待つ。timeval にヌルポインタを設定した。
while(1) {
char ch;
int fd;
int nread;

testfds = readfds;

printf("サーバは待ってます。\n");
result = select(FD_SETSIZE, &testfds, (fd_set *)0,
(fd_set *)0, (struct timeval *) 0);

if(result < 1) {
perror("server5");
exit(1);
}

//ディスクリプタ fd を マクロ FD_ISSET でチェックする。
for(fd = 0; fd < FD_SETSIZE; fd++) {
if(FD_ISSET(fd,&testfds)) {

//変化した fd が server_sockfd なら新しい接続である
//client_sockfd を ディスクリプタ集合に追加する
if(fd == server_sockfd) {
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);
printf("クライアントを fd に追加 %d\n", client_sockfd);
}
// -----クライアントからの要求----------------------
//クライアントが終了した時は、fd を閉じて集合から削除
else {
ioctl(fd, FIONREAD, &nread);

if(nread == 0) {
close(fd);
FD_CLR(fd, &readfds);
printf("クライアントを fd から削除 %d\n", fd);
}
else {
read(fd, &ch, 1);
sleep(5);
printf("fd のクライアントに応答 %d\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
}
----------------------------------------------------------------------------