UNIXネットワークプログラム
※CとC++がごっちゃになっていますので、真似しない方が良いと思います。
■fork, exec, pipe
■fork, execlp
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
int execlp(const char *file, const char *arg, ...);
//system()関数はコマンド名を引数にもらい、コマンドを実行します。
#include <iostream>
#include <sys/wait.h>
int main(){
int exit_code;
pid_t fork_pid;
char fileName[64], cmd[256];
sprintf(fileName, "./.kakusi%d.temp", getpid() );
sprintf(cmd, "ls -al > %s", fileName);
system(cmd);//別の所で解説をしないと…工事中
fork_pid = fork();
printf("親のPIDは、%d\n", getppid() );
if(fork_pid == -1){
exit(-1);
}else if(fork_pid == 0){
//exit(255);//256まで
//子
execlp("ls", "ls", "-a", "-l", "main.cc" , NULL);//正規表現だめみたい
}else{
//親
int child_stat;
// pid_t child_pid = wait(&child_stat);
if(WIFEXITED(child_stat) ){
printf("child = %d\n", WEXITSTATUS(child_stat) );
}else{
printf("child error.%d\n", WEXITSTATUS(child_stat) );
}
sleep(10);
}
printf("execlpの為、親プロセスではここは表示されません\n");
return 0;
}
/*
親が子の死を看取らない(waitせず)場合、親が生きている間、子はゾンビになります。
これは親がwaitをいつ呼ぶか解らないので、戻り値を保存しておく為です。
逆に親が先に死んでしまった場合は子はご先祖であるinitプロセスの子になります。
initの子になっても、親より先に死ねば、看取ってもらうまでゾンビになります。
プロセステーブルが大きいと、ゾンビをinitが看取るとまで時間がかかります。
pid_t waitpid(pid_t pid, int *status, int options);
waitpid関数で子のpidを指定して待つ事が出来ます。
*/
■popen, pclose
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
工事中
■pipe
int pipe(int desc[2]);
関数成功時は、パイプを作成しファイルディスクリプターを2つ配列にセットして0を返します。
(ファイルではなくディスクリプターなので、freadやfwriteではなく、readとwriteを使います)
プロセスによって使用されているファイルディスクリプターが多すぎたり、システムのファイルテーブルがいっぱいだったりするとエラーになります。
desc[1]に書かれたデータは、desc[0]から読み出しが出来ます。データはFIFO(先入れ先出し)で処理されます。
工事中
■Signal(シグナル)
■signal,kill
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t sighandler);
#include <sys/types.h>
int kill(pid_t pid, int sig);
シグナルを受け取ったときの挙動の定義をsignal関数もしくはsigaction関数で、送信をkill関数で行います。
signal関数は一度処理した後に再設定をしないといけないので、再設定をするまでのラグ問題で使用しないことが推奨されています。(sigaction関数を使うことが推奨されています)
signal関数の第二引数では、通常挙動を示す関数を指定しますが、SIG_IGNで無視を、SIG_DFLでデフォルト動作に戻すを指定する事が出来ます。
■sigaction
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
sigaction関数は第一引数に指定したシグナルに関連づけられた動作を設定します。
第二引数にはsicaction構造体に動作を定義して渡します。
第三引数に指定した構造体に以前の定義が保存されます。
sigaction構造体
struct sigaction {
void (*sa_handler)(int);
sigset_t sa_mask;
int sa_flags;
}
●sa_handlerは動作する関数を指定します。
●sa_maskは指定動作中にブロックするシグナルを指定します。
#include<signal.h>
int
sigemptyset(sigset_t *set);
//シグナルセットを空にします
int
sigfillset(sigset_t *set);
//シグナルセットに全てのシグナルを登録します
int sigaddset(sigset_t *set, int signum); //signumをシグナルセットに追加します
int sigdelset(sigset_t *set, int signum); //signumをシグナルセットから削除します
int sigismember(const sigset_t *set, int signum); //signumがシグナルセットあるかチェックします
これらの関数でシグナルセット(sigset_t型)と呼ばれるシグナル登録をした物に登録します。
●sa_flagsはシグナルの動作を変更するフラグを指定します。
・SA_NOCLDSTOP 子プロセスが停止した時にSIGCHLDを生成しない。
・SA_RESETHAND シグナル受信時にシグナル動作をSIG_DFLにします。
・SA_RESTART いくつかのシステムコールをシグナルの通知の前後で再開できるようにします。
・SA_NODEFER 捕捉した時にシグナルをシグナルマスクに追加しない。
#include <iostream>
#include <sys/wait.h>
using namespace std;
void mySIGABRT(int sig){
cout << "シグナルの番号は、" << sig << endl; //coutは保証されていません。
}
int main(){
pid_t fork_pid = fork();
if(fork_pid == -1){
exit(-1);
}else if(fork_pid == 0){
//子
sleep(3);
kill(getppid(), SIGABRT);
sleep(3);
kill(getppid(), SIGABRT);
sleep(3);
printf("I'm here.\n");
}else{
//親
// signal(SIGABRT, mySIGABRT);
struct sigaction act;
act.sa_handler = mySIGABRT;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_NOCLDSTOP | SA_RESETHAND;
sigaction(SIGABRT, &act, 0);
for(;;){
sleep(1);
cout << "#" << endl;
}
}
return 0;
}
■alarm
#include <unistd.h>
unsigned int alarm(unsigned int sec);
int pause(void);
alarm関数は指定秒数後にSIGALRMを自プロセスに送ります。
pause関数はシグナルを受け取るまでプロセスを一時停止させます。
シグナル処理関数は、処理中に割込みが入って他から呼ばれる事があります。割込み処理から復帰した時も問題なく処理を再開出来るように再入(reentrant)可能になっていなければなりません。
(自分自身を呼び出すのは再帰(recursive)と言います)
再入可能か、シグナルが発生しない事が保証されいる関数以外をシグナル処理内で呼ぶことは安全ではありません。
安全が保証されているX/Open仕様で定められた物を使うようにするか、フラグのセットして別の場所で処理をるようにするのが望ましいです。
■FIFO
※下記の共有メモリー、セマフォ、メッセージキューの3点はIPC(InterProcess Communication)と呼ばれるSystem V.2リリースで導入されたプロセス間通信機能です。
※プロセスの意図しない終了によりipcリソースが残ったままの時があります。ipcsコマンドで確認し、ipcrmコマンドでさじょします。
例:ipcrm -m 131075(shmidが131075の共有メモリーを削除)
ipcrm -M 1234(ipcのkeyが1234の共有メモリーを削除)■Shared Memory(共有メモリー)
■取得
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
共有メモリーを作成します。
keyはプロセス同士がやり取りするための、IDとして一意な物を用意します。
この値にIPC_PRIVATEを設定すると、セマフォはプロセスローカルな物になりますが、通常は使いません。
sizeにはメモリの使用量をバイト単位で記述します。
shmflgには共有メモリーのパーミッションとフラグオプションを論理和で指定します。
フラグは通常IPC_CREATを指定します。既に指定のメッセージキューがあった場合IPC_CREATは無視されますので、常に付けておいても問題ありません。
戻り値が0以外の正の数値だった場合、共有メモリーの識別子となります。エラーの場合は-1が返ります。
■接続
void *shmat(int shmid, const void *shmaddr, int shmflg);
既存の共有メモリに接続して、そのプロセスで利用可能にします。
shmidにはshmgetで取得した識別子を指定します。
shmaddrにはアタッチするアドレスを指定しますが、通常はNULLを指定してシステムに任せます。
shmflgはSHM_RNDかSHM_RDONLY(読み取り専用指定)を指定します。SHM_RNDはshmaddrと組み合わせてアドレスの制御に使います。
この関数が成功すると共有メモリーのアドレスが返ってきます。失敗すると-1が返ります。
■切断
int shmdt(const void *shmaddr);
現在のプロセスから共有メモリーを切り離します。
共有メモリー自体は残ります。
■制御
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
struct shmid_ds {
struct ipc_perm shm_perm; /* 所有権と許可 */
size_t shm_segsz; /* セグメントのサイズ (バイト) */
time_t shm_atime; /* 最後の付加 (attach) の時刻 */
time_t shm_dtime; /* 最後の分離 (detach) の時刻 */
time_t shm_ctime; /* 最後に変更が行われた時刻 */
pid_t
shm_cpid; /* 作成者 (creator) の PID */
pid_t
shm_lpid; /* 最後の shmat()/shmdt() の PID */
shmatt_t shm_nattch; /* 現在付加されている数 */
...
};
struct ipc_perm {
key_t key; /* shmget() に与えられるキー */
uid_t uid; /* 所有者の実効 UID */
gid_t gid; /* 所有者の実効 GID */
uid_t cuid; /* 作成者の実効 UID */
gid_t cgid; /* 作成者の実効 GID */
unsigned short mode; /* 許可 + SHM_DEST と
SHM_LOCKED フラグ */
unsigned short seq; /* シーケンス番号 */
};
shmidにはshmgetで取得した識別子を設定します。
cmdにはIPC_STATか、IPC_SETか、IPC_RMIDを指定します。
IPC_STAT 共有メモリーの情報をshmid_dsにコピーします。
IPC_SET shmid_dsの値を共有メモリーの情報に書き込みます。
IPC_RMID 共有メモリーを削除します。
関数に成功すると0が返り、失敗すると-1が返ります。
アタッチ中のプロセスが存在した状態で削除した時の動作は保証されていませんが、大概デタッチされるまで動くようです。
■Semaphore Arrays(セマフォ)
■取得
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
セマフォセットを作成したり、既存のセマフォセットの識別子を取得したりします。
keyはプロセス同士がやり取りするための、IDとして一意な物を用意します。
この値にIPC_PRIVATEを設定すると、セマフォはプロセスローカルな物になります。
nsems個のセマフォを持ったセマフォセットを作成します。この値はほとんど常に1だそうです。
semflagにはセマフォのパーミッションとフラグオプションを論理和で指定します。
semflagオプションは、IPC_CREATでkey に対応するセマフォ集合が存在しない場合、 nsems 個のセマフォからなる新しい集合が作成されます。
IPC_CREATとIPC_EXCLを併用するとkey に対応するセマフォ集合が存在するとエラー(-1)になります。
戻り値が0以外の正の数値だった場合、セマフォセットの識別子となります。
セマフォセットのメンバーの各セマフォは以下の関連情報を持っています。
unsigned short semval; /* セマフォ値 */
unsigned short semzcnt; /* ゼロを待つプロセス数 */
unsigned short semncnt; /* 増加を待つプロセス数 */
pid_t sempid; /* 最後に操作を行なったプロセス */
■変更
int semop(int semid, struct sembuf *sops, unsigned nsops);
struct sembuf{
unsigned short sem_num; /* セマフォ番号 */
short sem_op; /* セマフォ操作 */
short sem_flg; /* 操作フラグ */
}
semidは、変更したいセマフォセットの識別子を指定します。
sposにはsembuf構造体オブジェクトへのポインターを渡します。
sembuf構造体のsem_numで対象のセマフォセット中のセマフォの番号(0起点です)を指定します。
sem_opに増減させる値を入れます。通常は-1か0か+1が指定されます。
-1の時にセマフォ値が0だった場合、他のプロセスなどによってセマフォ値が1以上になるまで待ちます。
0の時はセマフォ値が0になるまで待ち、0になったら実行を再開してセマフォ値をデクリメントします。
sem_flgにはSEM_UNDO(プロセスアンドゥ数を更新)、IPC_WAIT(待つようにする)、IPC_NOWAIT(待たずにエラーにする)を論理和で指定します。SEM_UNDOによってプロセス終了時にセマフォを自動解放してくるようになります。
■直接制御
int semctl(int semid, int semnum, int cmd, ...);
union semun {
int
val; /* SETVAL の値 */
struct semid_ds *buf; /* IPC_STAT, IPC_SET 用のバッファ */
unsigned short *array; /* GETALL, SETALL 用の配列 */
struct seminfo *__buf; /* IPC_INFO 用のバッファ
(Linux 固有) */
};
semidに制御したいセマフォセットの識別子を指定します。
semnumにはセマフォセット内のセマフォの番号を指定します。
cmdには主に下記の指定がありあます。
SETVAL セマフォをvalで初期化します。セマフォの使用前には必要な処理です。
GETVAL
セマフォのsemvalを返します。
SETALL 集合の全てのセマフォのsemvalにsemun.array
で指定された値を設定する
IPC_RMID セマフォセット識別子を削除します。
■Message Queues(メッセージキュー)
■生成
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
メッセージキューを作成したり、既存のメッセージキューの識別子を取得したりします。
keyはプロセス同士がやり取りするための、IDとして一意な物を用意します。
この値にIPC_PRIVATEを設定すると、メッセージキューはプロセスローカルな物になります。
msgflgはパーミッションとフラグを論理和で指定します。
フラグは通常IPC_CREATを指定します。既に指定のメッセージキューがあった場合IPC_CREATは無視されますので、常に付けておいても問題ありません。
戻り値が0以外の正の数値だった場合、メッセージキューの識別子となります。エラーの場合は-1が返ります。
■送信
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);
メッセージキューにメッセージを送信します。
メッセージのサイズはMSGMAXに定義されているサイズより小さくなければなりません。
msqidにはmsggetで取得したメッセージキュー識別子を指定します。
ユーザーは下記の構造体(第一メンバーは必ずlong)を定義して、構造体オブジェクトのアドレスをmsgbufに渡します。
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
※mtextは配列 (または他の構造体) です。
msgszにはmtypeを除いたmsgbuf構造体オブジェクトのサイズを渡します。
msgflgはキューがいっぱいだたり、システムの制限に達した場合の挙動を指定します。
これにIPC_NOWAITが設定されていれば、メッセージを送信せずにエラーを返します。IPC_NOWAITがクリアされていれば、空が出来るまでメッセージ送信を待ちます。
■受信
ssize_t msgrcv(int msqid, struct msgbuf *msgp, size_t msgsz, long msgtyp, int msgflg);
メッセージキューからメッセージを取得します。
msqidにはmsggetで取得したメッセージキュー識別子を指定します。
msgpには受信したデータを格納するばしょを指定します。
送信同様longメンバーで始まる構造体オブジェクトを指定します。
msgszには受信するデータのサイズを指定します。送信同様longメンバーのサイズは含みません。
msgtypが0ならば、キューの最初にあるメッセージが読み込まれます。
msgtypが0より大きい場合、msgflgにMSG_EXCEPTが指定されていなければ、
msgtyp型のキューの最初のメッセージが読み込まれます。 MSG_EXCEPT が指定された場合は、msgtyp
型以外のキューの最初のメッセージが読み込まれます。
msgtypが0より小さければ、msgtypの絶対値以下で最も小さい型を持つキューの最初のメッセージが読み込まれる。
msgflg 引き数には、以下のフラグを任意の数だけ (0個も可)、これらの OR で指定します。
IPC_NOWAIT キューに要求された型のメッセージがない場合には直ちに返る。システムコールは失敗し、 errno には ENOMSG が設定される。
MSG_EXCEPT 0より大きなmsgtypと一緒に使用して、 msgtyp以外のキューの最初のメッセージを読み込む。
MSG_NOERROR msgszバイトよりも長かった場合はメッセージのテキストを切り詰める。
要求された型のメッセージが存在せず、msgflgに IPC_NOWAITが指定されていなかった場合、呼び出し元プロセスは以下のいずれかの状況になるまで停止 (block) されます。
■制御
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msqid_ds {
struct ipc_perm msg_perm; /* 所有権と許可 */
time_t msg_stime; /* 最後の msgsnd() の時刻 */
time_t msg_rtime; /* 最後の msgrcv() の時刻 */
time_t msg_ctime; /* 最後に変更が行われた時刻 */
unsigned long __msg_cbytes; /* キューにある現在のバイト数
(標準ではない) */
msgqnum_t msg_qnum; /* キューにある現在入っている
メッセージの数 */
msglen_t msg_qbytes; /* キューに許可されている
最大バイト数 */
pid_t
msg_lspid; /* 最後の msgsnd() の PID */
pid_t
msg_lrpid; /* 最後の msgrcv() の PID */
};
struct ipc_perm {
key_t key; /* msgget() に与えるキー */
uid_t uid; /* 所有者の実効 UID */
gid_t gid; /* 所有者の実効 GID */
uid_t cuid; /* 作成者の実効 UID */
gid_t cgid; /* 作成者の実効 GID */
unsigned short mode; /* 許可 */
unsigned short seq; /* シーケンス番号 */
};
msqidにはmsggetで取得した識別子を設定します。
cmdにはIPC_STATか、IPC_SETか、IPC_RMIDを指定します。
IPC_STAT メッセージキューの情報をmsqid_dsにコピーします。
IPC_SET msqid_dsの値をメッセージキューの情報に書き込みます。
IPC_RMID メッセージキューを削除します。
関数に成功すると0が返り、失敗すると-1が返ります。
プロセスがmsgsndやmsgrcvで待っている時に削除すると、msgsndやmsgrcvは失敗します。
■socket
socketはクライアントとサーバーが明確に分けられます。
■socketのドメイン
socket通信で使用するネットワーク媒体を指定するのに使います。
一般的なインターネットを現わすAF_TNET、ローカルマシン内だけで使用するAF_UNIXなどです。
AF_UNIXの場合、socketは通常ファイルと同様に作成され、ls(Fオプションでソケットの最後に=を付けます)で確認すると、
$ ls -lF
srwxr-xr-x 1 mick users 0 2005-03-22 11:15 mySocket=
と先頭にsが付いて表示されます。
■socketタイプ
socketドメインにはいくつかの通信手段があります。
AF_UNIXには問題ないのですが、インターネットの時は信頼性の問題から、ストリームとデータグラムという2つのタイプがあります。
●ストリームソケット
データの重複や順番違いなどの状態にはならず、不整合の時はエラーでそれが解る仕組みになっています。SOCK_STREAMというタイプで指定するこのタイプは動作予測が出来ます。
実装はAF_INETドメインではTCP/IP接続によって実装されています。
AF_UNIXドメインでも良く使われているらしいです。
●データグラムソケット
SOCK_DGRAMというタイプによって指定します。
SOCK_STREAMタイプとは違い、接続の確立と維持をしません。
また、送信できるデータグラムのサイズに制限があります。
データの損失に対する保証が無いタイプですが、簡単に使えて接続確立のオーバーヘッドもなく、高速に通信が出来ます。
実装はAF_INETドメインではUDP/IP接続によって実装されています。
■socketプロトコル
socketタイプで選んだ物で、複数のプロトコルからどれを使うか選べる時に指定します。
大概デフォルト(0を指定)を選びます。
■socketの作成
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
ドメイン、タイプ、プロトコルを指定します。
成功するとディスクリプタを返します。
socketがもう一方のsocketに接続すると、write、read、closeと言ったファイルディスクリプターに使う関数で制御出来ます。
■socketのアドレス
ソケットドメインはそれぞれのアドレス形式を持っています。
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t
sun_family;
/* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t
sin_port;
/* Port number. */
struct in_addr
sin_addr;
/* Internet address. */
/* Pad to size of `struct sockaddr'. */
unsigned char sin_zero[sizeof (struct sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof (in_port_t) -
sizeof (struct in_addr)];
};
struct in_addr
{
in_addr_t s_addr;
};
インターネットではsockaddr_inを使い、sin_portとsin_addrを設定します。
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
inet系の関数であるinet_atonを使ってsin_addrを設定します。
int result = inet_aton("127.0.0.1", &(mySock_addr.sin_addr) );
mySock_addr.sin_port = 1025;
※IPアドレスにINADDR_ANYとしていすると不特定なアドレスから接続出来るようになります。
■socketの命名
socket関数で生成したsocketを他のプロセスから使用できるように名前を付けます。
AF_UNIXの時はファイルパスを関連付け、AF_INETの場合はIPとポート番号に関連付けます。
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
sockfdには、socket関数で作成したディスクリプターの識別子を渡します。
sockadrrにはsocketのアドレスを設定した構造体オブジェクトのアドレスを渡します。
アドレスを渡す時に(struct sockaddr*)にキャストして渡します。
addrlenにはアドレス構造体の長さを渡しますので、sizeof(アドレス構造体オブジェクト)とします。
■socketキューの作成
接続要求を保留するためのキューを作成します。
SOCK_STREAM
型または
SOCK_SEQPACKET
型のソケットのみに適用できます。
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfdにはsocketのファイルディスクリプターを設定します。
backlogがキューの長さになります。
一般的には5を指定するらしいです。
■接続の受け入れ
socketを作成して名前のバインドをしキューを用意したら、接続の受け入れ体制が完了したとシステムに教えます。
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfdにはsocketのディスクリプターを設定します。
addrには接続して来たクライアントのアドレスを取得する場所を構造体アドレスで提供します。必要無ければNULL指定します。
addrlenは構造体の長さを指定します。クライアントのアドレスがこれより大きい場合は切り捨てられます。
保留状態の接続要求が入っているキューから
先頭の接続要求を取り出し、接続済みソケットを新規に生成し、
そのソケットを参照する新しいファイル・ディスクリプタを返します。
新規に生成されたソケットは、接続待ち (listen) 状態ではなく、
もともとのソケットsockfdはこの呼び出しによって影響を受けません。
ファイルディスクリプターを操作するfcntl関数で、O_NONBLOCK指定をされていなければ、クライアントからの接続があるまで待機してまちます。
指定されていた場合にキューが空であればエラーになります。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);
int flags = fcntl(socket, F_GETFL, 0);
fcntl(socket, FSETFL, O_NONBLOCK|flags);
■接続要求
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
クライアント側から接続要求をします。
sockfdにはクライアント側で作成したsocketのディスクリプターを指定します。
serv_addrはサーバのアドレスを設定した構造体オブジェクトのアドレスを指定します。
addrlenは構造体の長さを設定します。
成功すると0を返し、失敗するとEBADF(sockfdが無効)や、EALREADY(
ソケットが非停止 (non-blocking) に設定されており、
前の接続が完了していない)、ETIMEOUT(接続要求がタイムアウト)、ECONNREFUSED(
リモートアドレスで接続を待っているプログラムがない)
などがあります。
接続が確立されるかタイムアウトするまで処理待ちになります。
■socketのクローズ
#include <unistd.h>
int close(int fd);
readやwriteと同じに低水準の関数でファイルディsクリプターと同様に操作できます。
接続が終った時はサーバー、クライアントともにcloseする必要があります。
■バイトオーダー
上記までの知識を使いネットワーク接続を試みて、ネットワークの状態を確認するコマンドを使うと以下の様な出力がされます。
$ netstat
稼働中のインターネット接続 (w/oサーバー)
Proto
受信-Q 送信-Q
内部アドレス
外部アドレス
状態
tcp 1
0 localhost:1574
localhost:1174
TIME_WAIT
アドレスは、IPアドレス:ポート番号で表示されますが、プログラム内で指定したポート番号とは違う番号が表示されています。
クライアント側ではサーバーのlistenソケットと異なるのは、一意のポートをシステムが割り振ります。
サーバー側ではプログラム内で指定した値になるはずですが、CPUの種類(エンディアン)によって数値の表し方(バイトオーダー)が異なるため、違う数値として認識されています。
これはポート番号とアドレスがsocketを介して2進数でやりとりされるためです。
この解決にはネットワークオーダーに変換してくれる関数を使います。
#include <netinet/in.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
htonlは、host to network, longの略で、その名の通りの動作をします。
hostのバイトオーダーとネットワークオーダーが同じだった場合は何もしないこともあます。
前述のinet_addrは、ネットワークバイトオーダーを返すために、アドレスは正常に渡されています。
■ネットワーク情報の取得
/etc/hostsやNIS、DNSからコンピューター名やアドレスなどの情報を取得します。
#include <netdb.h>
#include <sys/socket.h>
struct hostent *gethostbyaddr(const void *addr, int len, int type);
struct hostent *gethostbyname(const char *name);
struct hostent {
char
*h_name; /* official name of
host */
char **h_aliases; /* alias list */
int h_addrtype; /* host
address type */
int
h_length; /* length of address */
char **h_addr_list; /* list of addresses */
}
ホスト名やアドレスから、ホストに関する情報を取得します。
該当データが無い場合は、NULLが返ります。
addrにはアドレス構造体オブジェクトのアドレスを設定し、lenにはその長さを、typeにはAF_INETかAF_INET6(IPv6)を指定します。
nameにはホスト名を指定します。
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
struct hostent host_info = gethostbyname("マシンの名前");
char** addres = host_info->h_addr_list;
while(*addres){
printf("addres is %s\n", inet_ntoa(*(struct in_addr*)*addres));
addres++;
}
アドレスはネットワークオーダーなので、10進表記ドット形式の文字列に変換する関数を使います。
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent {
char
*s_name; /* official service
name */
char **s_aliases; /* alias list */
int
s_port; /* port number
*/
char
*s_proto; /* protocol to use */
}
サービスとポート番号に関する情報を/etc/servicesから取得します。
protoには"tcp"か"udp"を指定します。
nameにはサービス名を指定します。
portにはポート番号を指定します。
■socketのオプション
#include <sys/types.h>
#include <sys/socket.h>
int getsockopt(int s, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
ソケットに関連するoptionsを操作します。オプションは複数のプロトコル層(level)に対して行えます。これらは常に最上位のsocket層へと設定されます。
levelにはsocket層の場合はSOL_SOCKET、TCPなどのプロトコル層の場合は、プロトコル番号を設定します。プロトコル番号はnetinet/in.hファイルに定義されているものを使うか、getprotobyname関数で取得したものを使います。
optnameにはSO_KEEPALIVE(定期的に転送を行って接続をアクティブに保つ)などの設定するオプションを指定します。
optvalにはoptlenバイト分の値を設定します。
ほとんどのソケット層のオプションは optval に int パラメーターを利用しsetsockoptで、二値(boolean)オプションを有効(enable)にするにはゼロ以外を指定し、無効(disable)にするにはゼロを指定します。
■複数のクライアント処理(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システムコールを使うとビジーウェイトではないブロック状態で、複数のディスクリプターへのアクセスを待ち受けする事ができます。
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
■複数のクライアント処理(マルチスレッド版)
selectを使う以外に、マルチプロセスかマルチスレッドを使う平行処理実装が考えられます。
接続分の処理を作るとなるとマルチプロセスは少し厳しい状況になりやすそうで、スレッドによる実装が適していると思えます。
■RPC
戻る