実践Linux                 TOPへ  Cプログラミング目次へ

ネットワークプログラミング(ソケットプログラミング)  2009年7月更新

クライアントがオプションに1をつけて実行すると、サーバーは「answer1」を返し、2では「answer2」を、3では「answer3」を返すという簡単なもの。
@スタンドアローンの常駐サービス型 と Ainetdを利用したもの(常時アクティブではなく接続ごとの起動となる)を作成してみる。

●Makefile共通
# Makefile for test

CC = gcc
TARGETS = test
OBJ = ${SRC:.c=.o}
SRC = test.c

$(TARGETS):${OBJ}
${CC} ${OBJ} -o ${TARGETS}

${OBJ}:${SRC}
${CC} ${SRC} -c -o ${OBJ}

clean:
rm -f *.o $(TARGETS)

●クライアント共通
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>

#define PORT 3000
//#define BUFSIZ 1024

int main(int argc, char *argv[])
{
int sf; //ソケット
struct sockaddr_in addr; //ソケットのアドレス
char buff[BUFSIZ];

if(argc < 2)
{
fprintf(stderr, "usage: %s index\n", basename(argv[0]));
return 1;
}

if((sf = socket(AF_INET, SOCK_STREAM, 0)) == -1) //クライアント用ソケットの作成
{
perror("client: socket");
return 1;
}

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
addr.sin_port = htons(PORT);


if(connect(sf, (struct sockaddr *)&addr, sizeof(addr)) == -1) //クライアントのソケットをサーバーのソケットに接続
{
perror("client: connect");
return 1;
}

if(write(sf, argv[1], strlen(argv[1])+1) == -1)
{
perror("client: wtite");
return 1;
}

if(read(sf, buff, BUFSIZ) == -1)
{
perror("client: read");
return 1;
}

printf("data from server = %s\n", buff);

close(sf);

return 0;
}


@ 常駐型サーバー(INETドメイン ネットワークソケット=AF_INET、TCP=SOCK_STREAM)

●サーバー例1(単純なもの)
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /* struct sockaddr_in */
#include <stdio.h>
#include <string.h> /* strcpy */
#include <stdlib.h> /* atoi */
#include <signal.h> /* ctrl+c */

#define PORT 3000
//#define BUFSIZ 1024

static char *lang[]={“test01”,”test02”,”test03”};
int sf, af; //サーバー用ソケットとクライアントサーバー用ソケット

int main()
{
size_t len;
struct sockaddr_in addr, cl_addr; //サーバー用ソケットとクライアントサーバー用ソケットのアドレス
void sig_fnc();
int msqpro();

printf(“end: please press kye ctrl+c\n”);

if((sf = socket(AF_INET, SOCK_STREAM, 0)) == -1) //サーバー用ソケットの作成
{
perror(“server: socket”);
return 1;
}

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);

if(bind(sf, (struct sockaddr *)&addr, sizeof(addr)) == -1) //サーバー用ソケットの命名(bindでアドレスに関連づけた名前を付ける)
{
perror(“server: bind”);
return 1;
}

if(listen(sf, 5) == -1) //接続キューを作成し、クライアントからの接続を待つ
{
perror(“server: listen”);
return 1;
}


len = sizeof(cl_addr);

signal(SIGINT,sig_fnc); //ctrl+cで終了

while(1)
{
if((af = accept(sf, (struct sockaddr *)&cl_addr, &len)) == -1) //接続の受け入れ
{
perror(“server: accept”);
return 1;
}

msqpro(); //応答

close(af);
}

close(sf);

return 0;
}

//////////////////////////////////////////////////////////

int msqpro() //応答
{
int index;
char buff[BUFSIZ];

if(read(af, buff, BUFSIZ) == -1)
{
perror(“server: read”);
return 1;
}

index = atoi(buff);
if(index == 0 || index > 3) strcpy(buff, “invalid index”);
else strcpy(buff, lang[--index]);

if(write(af, buff, strlen(buff)+1) == -1)
{
perror(“server: wtite”);
return 1;
}

return 0;
}

//////////////////////////////////////////////////////////

void sig_fnc() //ctrl+cで終了
{
close(sf);
exit(0);
}

●サーバー例2(forkで同時に複数のクライアントを処理する)
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /* struct sockaddr_in */
#include <stdio.h>
#include <string.h> /* strcpy */
#include <stdlib.h> /* atoi */
#include <signal.h> /* ctrl+c */

#define PORT 3000
//#define BUFSIZ 1024

static char *lang[]={“test01”,”test02”,”test03”};
int sf, af; //サーバー用ソケットとクライアントサーバー用ソケット

int main()
{
size_t len;
struct sockaddr_in addr, cl_addr; //サーバー用ソケットとクライアントサーバー用ソケットのアドレス
void sig_fnc();
int msqpro();

printf(“end: please press kye ctrl+c\n”);

if((sf = socket(AF_INET, SOCK_STREAM, 0)) == -1) //サーバー用ソケットの作成
{
perror(“server: socket”);
return 1;
}

addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT);

if(bind(sf, (struct sockaddr *)&addr, sizeof(addr)) == -1) //サーバー用ソケットの命名(bindでアドレスに関連づけた名前を付ける)
{
perror(“server: bind”);
return 1;
}

if(listen(sf, 5) == -1) //接続キューを作成し、クライアントからの接続を待つ
{
perror(“server: listen”);
return 1;
}


len = sizeof(cl_addr);

signal(SIGINT, sig_fnc); //ctrl+cで終了
signal(SIGCHLD, SIG_IGN); //SIGCHLDシグナル(子プロセスの終了)を無視し、ゾンビが発生しないようにする。

while(1)
{
if((af = accept(sf, (struct sockaddr *)&cl_addr, &len)) == -1) //接続の受け入れ
{
perror(“server: accept”);
return 1;
}

if(fork() == 0) //クライアント用の子プロセスを作成
{
msqpro(); //応答
close(af);
exit(0);
}
else //親プロセス。クライアントの接続に対して行う処理はなにもない。
{
close(af);
}
}

close(sf);

return 0;
}

//////////////////////////////////////////////////////////

int msqpro() //応答
{
int index;
char buff[BUFSIZ];

if(read(af, buff, BUFSIZ) == -1)
{
perror(“server: read”);
return 1;
}
sleep(5); //サーバー側で計算やデータベースへのアクセスを行う場合の遅延を考慮。forkでは子プロセスだけでの5秒sleepとなるので全体への影響はでない。

index = atoi(buff);
if(index == 0 || index > 3) strcpy(buff, “invalid index”);
else strcpy(buff, lang[--index]);

if(write(af, buff, strlen(buff)+1) == -1)
{
perror(“server: wtite”);
return 1;
}

return 0;
}

//////////////////////////////////////////////////////////

void sig_fnc() //ctrl+cで終了
{
close(sf);
exit(0);
}

●サーバー例3(selecで同時に複数のクライアントを処理する)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
selectは、指定した集合に含まれるファイルディスクリプタを対象に、読み取りまたは書き込みの準備ができているかどうか、あるいはエラーが発生しているかどうかを調べ、その総数を返す。
nfdsは調べるファイルディスクリプタの数。
readfds、writefds,、errorfdsにヌルポインタが指定されたときは、対応するテストは実行されない。
これらの条件のいずれにも該当しない場合、timeoutで指定された時間が経過したあと呼び出しに戻る。

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> /* struct sockaddr_in */
#include <stdio.h>
#include <string.h> /* strcpy */
#include <stdlib.h> /* atoi */
#include <signal.h> /* ctrl+c */
#include <sys/time.h>
#include <sys/ioctl.h>

#define PORT 3000
//#define BUFSIZ 1024

static char *lang[]={"test01","test02","test03"};
int sf, af; //サーバー用ソケットとクライアントサーバー用ソケット

int main()
{
size_t len;
struct sockaddr_in sv_addr, cl_addr; //サーバー用ソケットとクライアントサーバー用ソケットのアドレス
void sig_fnc();
int msqpro();
int result;
fd_set readfds, tmpfds; //selectで使用する(操作の対象となる)データ構造体。オープンされたファイルディスクリプタの集合を表す。

printf("end: please press kye ctrl+c\n");

if((sf = socket(AF_INET, SOCK_STREAM, 0)) == -1) //サーバー用ソケットの作成
{
perror("server: socket");
return 1;
}

sv_addr.sin_family = AF_INET;
sv_addr.sin_addr.s_addr = INADDR_ANY;
sv_addr.sin_port = htons(PORT);

if(bind(sf, (struct sockaddr *)&sv_addr, sizeof(sv_addr)) == -1) //サーバー用ソケットの命名(bindでアドレスに関連づけた名前を付ける)
{
perror("server: bind");
return 1;
}

if(listen(sf, 5) == -1) //接続キューを作成し、クライアントからの接続を待つ
{
perror("server: listen");
return 1;
}

FD_ZERO(&readfds); //readfdsを初期化
FD_SET(sf, &readfds); //sfをディスクリプタの集合readfdsに加える。

len = sizeof(cl_addr);

signal(SIGINT, sig_fnc); //ctrl+cで終了

while(1)
{
int fd;
int nread;

tmpfds = readfds;

result = select(FD_SETSIZE, &tmpfds, (fd_set *)0, (fd_set *)0, (struct timeval *)0); //FD_SETSIZEはfd_set構造体に保持できるディスクリプタ数の最大値

if(result < 1) {
perror("server: select");
return 1;
}

for(fd = 0; fd < FD_SETSIZE; fd++) { //FD_SETSIZEはfd_set構造体に保持できるディスクリプタ数の最大値
if(FD_ISSET(fd, &tmpfds)) { //ディスクリプタがtmpfdsの要素のとき
if(fd == sf) { //ディスクリプタがsfの場合、新しい接続要求なのでacceptを呼び出す
if((af = accept(sf, (struct sockaddr *)&cl_addr, &len)) == -1) //接続の受け入れ
{
perror("server: accept");
return 1;
}
FD_SET(af, &readfds); //afをディスクリプタの集合readfdsに加える。
}
else { //ディスクリプタがsfでない場合、クライアントからの要求なので応答
ioctl(fd, FIONREAD, &nread); //FIONREADは入力バッファにあるバイト数を取得する(nread)。

if(nread == 0) { //読み取るべきデータが何もないときは終了しているので、集合からディスクリプタを削除
close(fd);
FD_CLR(fd, &readfds); //集合からディスクリプタを削除
}
else {
msqpro(fd); //応答
}
}
}
}
}
close(sf);

return 0;
}

//////////////////////////////////////////////////////////

int msqpro(int fd) //応答
{
int index;
char buff[BUFSIZ];

if(read(fd, buff, BUFSIZ) == -1)
{
perror("server: read");
return 1;
}
sleep(5); //サーバー側で計算やデータベースへのアクセスを行う場合の遅延を考慮。

index = atoi(buff);
if(index == 0 || index > 3) strcpy(buff, "invalid index");
else strcpy(buff, lang[--index]);

if(write(fd, buff, strlen(buff)+1) == -1)
{
perror("server: wtite");
return 1;
}

return 0;
}

//////////////////////////////////////////////////////////

void sig_fnc() //ctrl+cで終了
{
close(sf);
exit(0);
}

●起動スクリプトをつくる
/etc/rc.d/init.dの下に起動スクリプトをつくる。
killproc関数は/etc/rc.d/init.d/function 内に定義されています。functionファイルでは、その他daemon関数など、起動スクリプト内でよく使われている関数が記述されています。

【サンプル】
#!/bin/bash
#
# Version: 0.1
#
# chkconfig: 345 99 01
# description: Runs the testProgram
# processname: /usr/local/bin/test
# config: /etc/test.conf

progname=test
lockfile=/var/lock/subsys/test
prog=/usr/local/bin/test
conffile=/etc/test.conf
[ -f $conffile ] || exit $?

# Source function library.
. /etc/init.d/functions

RETVAL=0

start() {
echo -n $"Starting $progname: "
daemon $prog -c $conffile
RETVAL=$?
echo
[ $RETVAL = 0 ] && touch $lockfile
return $RETVAL
}

stop() {
echo -n $"Stopping $progname: "
killproc $prog
RETVAL=$?
echo
[ $RETVAL = 0 ] && rm -f $lockfile
return $RETVAL
}

# See how we were called.
case "$1" in
start)
start
;;
stop)
stop
;;
status)
status $prog
RETVAL=$?
;;
restart)
stop
start
;;
condrestart)
if [ -f $lockfile ]; then
stop
start
fi
;;
reload)
action $"Reloading $progname:" killall -HUP $prog
;;
*)
echo $"Usage: $0 {start|stop|restart|reload|condrestart|status}"
exit 1
esac

exit $RETVAL

【説明】
# description: Runs the testProgram
# processname: /usr/loca/bin/testprog
# config: /etc/testprog.conf
linuxconfに情報を伝えるもの。config:で示されたファイルが 更新されたら、linuxconfは、このプロセスを再起動しようとする。

progname=testprog
lockfile=/var/lock/subsys/testprog
prog=/usr/local/bin/testprog
conffile=/etc/testprog.conf
ファイルの名前や、conifgファイルの名前をlockファイルの名前を定義する。
Kファイルの実行では、まず/var/lock/subsys以下のlockファイル(サイズゼロのファイル)のチェックが行なわれます。このlockファイルは,すでに該当のスクリプトのstart処理が行なわれているかどうかを示しており、ロックファイルが存在しているもののみに対して Kファイルが実行されます。

# Source function library.
. /etc/init.d/functions
用意してあるfunctionライブラリ(daemon,killproc,status等)を includeする。

daemon $prog -c $conffile
$progを -c $conffileの引数で起動する。
daemonファンクションは、後ろにそのまま、起動したいプロセスと引数を書けばよい。


A inetdを使ったネットワークプログラミング

クライアントからのアクセスが頻繁でなく、常駐の必要がない場合はinetdを使う。
inetd(あるいはxinetd)を使うとサーバーのプログラミングは格段に楽になる。
実際上、クライアントからのコネクトを受け付け、子プロセスを作成するところまでinetd側でやってくれるので、子プロセスでのプログラミングだけでよくなる。

●サーバー

#include <sys/socket.h>
#include <stdio.h>
#include <string.h> /* strcpy */
#include <stdlib.h> /* atoi */

static char *lang[]={"ganswer1","hanswer2","hanswer3"};

int main()
{
size_t len;
struct sockaddr cl_addr;
int msqpro();

len=sizeof(cl_addr);
getpeername(0, &cl_addr, &len);
msqpro();
close(0);
exit(0);
}
//////////////////////////////////////////////////////////
int msqpro()
{
int index;
char buff[BUFSIZ];

if(read(0,buff,BUFSIZ) == -1)
{
perror("gserver: read");
return 1;
}

index=atoi(buff);
if(index == 0 || index > 3) strcpy(buff,"hinvalid index");
else strcpy(buff,lang[--index]);

if(write(0,buff,strlen(buff)+1) == -1)
{
perror("gserver: wtite");
return 1;
}

return 0;
}


inetdの設定
 /etc/servicesに test 3000/tcp  を追加(サービス名とポート/プロトコル。サービス名とポートは適宜)。
 /etc/inetd.conf に test stream tcp nowait root /test/test test を追加(/test/testがプログラムだとする)。
 /etc/hosts.allowに  test: 192.168.7. (アクセスを許可するアドレスを適宜)。

xinetdの場合
 /etc/xinetd.d/に以下のようなファイルを作って、xinetdサービスを再起動するだけでよい。
 service test
 {
 id = test
 socket_type = stream
 protocol = tcp
 wait = no
 user = root
 port = 3000
 server = /test/test
 only_from = 192.168.7.0/24 localhost
 disable = no
 }


TOPへ  Cプログラミング目次へ