Linuxシステムコール、INETドメイン ソケット
UNIX / Linux システムコール・プログラミング
プロセス間通信(IPC)、ソケット通信/INETドメイン ソケット
INETドメイン ソケットを利用することで実際のネットワーク上のプロトコルであるTCP/IPやUDP/IPを意識したプログラミングが可能になります。
※今回のサンプルコードは、TCP/IPです。
基本的に、INETドメイン ソケットもUnixドメイン ソケットと同様の実装となりますが、異なるノード間での通信を実現するため、ネットワークアドレスやポート番号を意識する必要があります。
それでは、サーバー側のコードを以下に示します。
inet-dimain-server.c
サーバー側のコードについて説明します。
サーバー側のプログラムは引数でポート番号を指定して起動します。
まず、INETドメイン ソケットをsocket()システムコールを使って生成します。
socket(AF_INET, SOCK_STREAM, 0)
AF_INET、生成するソケットはINETドメイン ソケットである
また、IPv4のアドレスファミリーである。
SOCK_STREAM、バイトストリーム(TCP/IP)を指定
0、SOCK_STREAM固有のプロトコルなので0を指定
socket()は、ソケットへのファイルディスクリプタを返します。
このサーバー側のソケットはsocket()システムコールによってIPv4のアドレスファミリーで、TCP/IPプロトコルで動作するバイトストリームとして生成されます。
次にbind()システムコールを使ってソケットに名前を割り当てます。INETドメインの場合、bind()には、sockaddr_in構造体でアドレスファミリー、受け入れアドレス、受け入れポート番号を指定します。
serv.sin_family = AF_INET; ← IPv4ファミリ
serv.sin_addr.s_addr = htonl(INADDR_ANY);
→ INADDR_ANYの場合ホストアドレスを指定しない
→ 指定した場合、そのアドレスからのみ受け入れる
serv.sin_port = htons(portNo); ← ポート番号
bind( sfd, (const struct sockaddr *)&name,
(const struct sockaddr *)&serv,
sizeof(struct sockaddr_in))
sfd、socket()で得られたファイルディスクリプタ
serv、アドレス構造体ポインタ
構造体サイズ
このbind()によって、このソケットがネットワークへの受け入れ準備がととのいました。
次に、このソケットがサーバーとして動作するためにlisten()システムコールを使って、受け入れソケットにします。これは、Unixドメインのときと同じです。
listen(sfd, 10)
sfd、ソケットファイルディスクリプタ
backlog、同時に受け入れ可能なクライアント数
これで、クライアントの受け入れ準備ができました。
今度は、accept()システムコールを使ってクライアントからの要求を待ちます。
ただし、今回は接続してきたクライアントを確認できるようにしました。
struct sockaddr_in addr;
int addrSz = sizeof(addr);
accept(sfd, (struct sockaddr*)&addr, &addrSz)
sfd、受け入れソケットファイルディスクリプタ
addr、sockaddr構造体ポインタ
構造体サイズのポインタ
クライアントからの接続を待ち、接続が完了するとクライアント間ソケットのファイルディスクリプタが帰ります。
そして、sockaddr構造体にはクライアントのIPアドレスを確認することができます。
クライアントの接続ログを採ったり、特定のアドレスからの接続を遮断するなどをプログラミングで実現することが可能になります。
以降、このクライアント間ソケットを利用して双方向の通信が可能になります。
このコードでは、クライアントからのデータを待って表示を行い、クライアントからのデータが"Quit"である場合は終了します。それ以外の場合は"NeXT"をクライアントに送って次のデータを待ちます。
終了時は、クライアントに"QUIT"を送り、クライアント側にも終了を促し、クライアント間のソケット、クライアントの受け入れソケットを閉じて終了します。
Unixドメインのときと同様、このサンプルコードではクライアントとの接続は1つだけしか行っていませんが、複数のクライアントからの要求を処理するようなサーバーとしての動作も可能です。
それでは、今度はクライアント側のコードについて解説します。
inet-domain-client.c
クライアント側のプログラムは、サーバー側を起動したあとで実行します。
クライアント側のプログラムは起動時の引数で、接続先サーバーのIPアドレスとポート番号を指定します。ポート番号は、サーバーを起動したときに引数で指定したものと同じ番号です。
まず、Unixドメイン ソケットをsocket()システムコールを使って生成します。
socket(AF_INET, SOCK_STREAM, 0)
AF_INET、生成するソケットはINETドメイン ソケットである
また、IPv4のアドレスファミリーである。
SOCK_STREAM、バイトストリームを指定
0、SOCK_STREAM固有のプロトコルなので0を指定
socket()は、ソケットへのファイルディスクリプタを返します。
この部分はサーバー側と同じです。
次にconnect()システムコールを使って、サーバーに接続を行います。
serv.sin_family = AF_INET; ← IPv4ファミリ
serv.sin_port = htons(portNo); ← ポート番号(第2引数)
servAddr = gethostbyname(argv[1])
第1引数で指定されたIPアドレスをgethostbyname()を使って変換します。
変換したアドレスは、serv.sin_addr.s_addrに格納します。
serv、接続するサーバーのアドレス構造体ポインタ
構造体サイズ
この時点でサーバー側がaccept()で待っている状態であれば、サーバー側で受けれられて通信が可能となります。
このコードではユーザーからの入力をサーバに送り、サーバーからの応答を待ちます。サーバーからの応答が"QUIT"であったとき(ユーザーが"Quit"を入力したとき)はソケットのファイルディスクリプタを閉じて終了します。
それ以外の応答である場合は、ユーザー入力を続けます。
実行サンプル
ソケットについて
ソケットはもともとBSD系のUnixに実装されたものでした。共有メモリなどはSystem V IPCであると伝えましたが、以前UNIXは、System V系とBSD系の2系統に大きく別れ、APIも異なっていた時代がありました。それがSVR4で統合され、現在に至っています。
APIもいいものは生き残る。
でしょうか?
プロセス間通信(IPC)、ソケット通信/INETドメイン ソケット
Unixドメイン ソケットとは?
Unixドメイン ソケットは、ノード間のネットワーク通信を行うことができ、現代のインターネット環境の基盤技術の1つと言えます。INETドメイン ソケットを利用することで実際のネットワーク上のプロトコルであるTCP/IPやUDP/IPを意識したプログラミングが可能になります。
※今回のサンプルコードは、TCP/IPです。
基本的に、INETドメイン ソケットもUnixドメイン ソケットと同様の実装となりますが、異なるノード間での通信を実現するため、ネットワークアドレスやポート番号を意識する必要があります。
それでは、サーバー側のコードを以下に示します。
inet-dimain-server.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
// Make server socket.
//
int makeServer(int portNo) {
int sfd;
int ret;
struct sockaddr_in serv;
// Create server socket.
if((sfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("ERROR:Server Socket()");
return -1;
}
// Bind socket
memset(&serv, 0, sizeof(struct sockaddr_in));
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = htonl(INADDR_ANY);
serv.sin_port = htons(portNo);
if ((ret = bind(sfd,
(const struct sockaddr *)&serv,
sizeof(struct sockaddr_in))) < 0) {
perror("ERROR:Server bind()");
return -1;
}
// Listen, prepair for accept()
if((ret = listen(sfd, 10)) < 0) {
perror("ERROR:Server listen()");
return -1;
}
return sfd;
}
// Make data socket for client.
//
int acceptClient(int sfd) {
struct sockaddr_in addr;
int addrSz = sizeof(addr);
int dfd;
if((dfd = accept(sfd, (struct sockaddr*)&addr, &addrSz)) < 0) {
perror("ERROR:Server accept()");
return -1;
}
printf("Client Cennected : %s\n", inet_ntoa(addr.sin_addr));
return dfd;
}
// This main()
//
int main(int argc, char** argv) {
int sfd, dfd;
int portNo;
int ret;
char buf[256];
// Check running argument.
if(argc < 2) {
printf("inet-domain-server port-No.\n");
return -1;
}
portNo = atoi(argv[1]);
if((sfd = makeServer(portNo)) < 0) {
return -1;
}
if((dfd = acceptClient(sfd)) < 0) {
close(sfd); // Close server socket.
return -1;
}
// Main loop
while(1) {
if((ret = read(dfd, buf, sizeof(buf))) < 0) {
perror("ERROR:Data read()");
return -1;
}
printf("%s", buf);
if(strncmp(buf, "Quit", 4) == 0) {
printf("Received Quit\n");
break;
}
// Send to client "NeXT".
if((ret = write(dfd, "NeXT", 4)) < 0) {
perror("ERROR:Data write()");
return -1;
}
}
// Send "Quit" for client.
write(dfd, "QUIT", 4);
// Close data socket.
close(dfd);
// Close server socket.
close(sfd);
return 0;
}
サーバー側のコードについて説明します。
サーバー側のプログラムは引数でポート番号を指定して起動します。
まず、INETドメイン ソケットをsocket()システムコールを使って生成します。
socket(AF_INET, SOCK_STREAM, 0)
AF_INET、生成するソケットはINETドメイン ソケットである
また、IPv4のアドレスファミリーである。
SOCK_STREAM、バイトストリーム(TCP/IP)を指定
0、SOCK_STREAM固有のプロトコルなので0を指定
socket()は、ソケットへのファイルディスクリプタを返します。
このサーバー側のソケットはsocket()システムコールによってIPv4のアドレスファミリーで、TCP/IPプロトコルで動作するバイトストリームとして生成されます。
次にbind()システムコールを使ってソケットに名前を割り当てます。INETドメインの場合、bind()には、sockaddr_in構造体でアドレスファミリー、受け入れアドレス、受け入れポート番号を指定します。
serv.sin_family = AF_INET; ← IPv4ファミリ
serv.sin_addr.s_addr = htonl(INADDR_ANY);
→ INADDR_ANYの場合ホストアドレスを指定しない
→ 指定した場合、そのアドレスからのみ受け入れる
serv.sin_port = htons(portNo); ← ポート番号
bind( sfd, (const struct sockaddr *)&name,
(const struct sockaddr *)&serv,
sizeof(struct sockaddr_in))
sfd、socket()で得られたファイルディスクリプタ
serv、アドレス構造体ポインタ
構造体サイズ
このbind()によって、このソケットがネットワークへの受け入れ準備がととのいました。
次に、このソケットがサーバーとして動作するためにlisten()システムコールを使って、受け入れソケットにします。これは、Unixドメインのときと同じです。
listen(sfd, 10)
sfd、ソケットファイルディスクリプタ
backlog、同時に受け入れ可能なクライアント数
これで、クライアントの受け入れ準備ができました。
今度は、accept()システムコールを使ってクライアントからの要求を待ちます。
ただし、今回は接続してきたクライアントを確認できるようにしました。
struct sockaddr_in addr;
int addrSz = sizeof(addr);
accept(sfd, (struct sockaddr*)&addr, &addrSz)
sfd、受け入れソケットファイルディスクリプタ
addr、sockaddr構造体ポインタ
構造体サイズのポインタ
クライアントからの接続を待ち、接続が完了するとクライアント間ソケットのファイルディスクリプタが帰ります。
そして、sockaddr構造体にはクライアントのIPアドレスを確認することができます。
クライアントの接続ログを採ったり、特定のアドレスからの接続を遮断するなどをプログラミングで実現することが可能になります。
以降、このクライアント間ソケットを利用して双方向の通信が可能になります。
このコードでは、クライアントからのデータを待って表示を行い、クライアントからのデータが"Quit"である場合は終了します。それ以外の場合は"NeXT"をクライアントに送って次のデータを待ちます。
終了時は、クライアントに"QUIT"を送り、クライアント側にも終了を促し、クライアント間のソケット、クライアントの受け入れソケットを閉じて終了します。
Unixドメインのときと同様、このサンプルコードではクライアントとの接続は1つだけしか行っていませんが、複数のクライアントからの要求を処理するようなサーバーとしての動作も可能です。
それでは、今度はクライアント側のコードについて解説します。
inet-domain-client.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define HOST_ADDR "192.168.1.16"
int main(int argc, char** argv) {
int dfd;
int portNo;
int ret;
struct sockaddr_in serv;
struct hostent* servAddr;
char buf[256];
char rcv[256];
// Check running argument.
if(argc < 3) {
printf("inet-domain-client server-address port-No.\n");
return -1;
}
portNo = atoi(argv[2]);
// Create socket
if((dfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("ERROR:Client socket()");
return -1;
}
// Connect server
memset(&serv, 0, sizeof(struct sockaddr_in));
serv.sin_family = AF_INET;
serv.sin_port = htons(portNo);
if((servAddr = gethostbyname(argv[1])) == NULL) {
perror("ERROR:Client gethostbyname()");
return -1;
}
bcopy((char*)servAddr->h_addr,
(char*)&serv.sin_addr.s_addr,
servAddr->h_length);
if((ret = connect(dfd,
(const struct sockaddr*)&serv,
sizeof(struct sockaddr))) < 0) {
perror("ERROR:Client connect()");
return -1;
}
while(1) {
// User input to send data.
printf("#");
memset(buf, 0, sizeof(buf));
fflush(stdin);
fgets(buf, 256, stdin);
// Send to server.
if((ret = write(dfd, buf, strlen(buf) + 1)) < 0) {
perror("ERROR:Client read()");
close(dfd);
return -1;
}
// Reveive data from server.
memset(rcv, 0, sizeof(rcv));
if((ret = read(dfd, rcv, sizeof(rcv) - 1)) < 0) {
perror("ERROR:Client read()");
close(dfd);
return -1;
}
printf("%s", rcv);
if(strncmp(rcv, "QUIT", 4) == 0) {
printf("\nReceived QUIT\n");
break;
}
}
// Close data socket.
close(dfd);
return 0;
}
クライアント側のプログラムは、サーバー側を起動したあとで実行します。
クライアント側のプログラムは起動時の引数で、接続先サーバーのIPアドレスとポート番号を指定します。ポート番号は、サーバーを起動したときに引数で指定したものと同じ番号です。
まず、Unixドメイン ソケットをsocket()システムコールを使って生成します。
socket(AF_INET, SOCK_STREAM, 0)
AF_INET、生成するソケットはINETドメイン ソケットである
また、IPv4のアドレスファミリーである。
SOCK_STREAM、バイトストリームを指定
0、SOCK_STREAM固有のプロトコルなので0を指定
socket()は、ソケットへのファイルディスクリプタを返します。
この部分はサーバー側と同じです。
次にconnect()システムコールを使って、サーバーに接続を行います。
serv.sin_family = AF_INET; ← IPv4ファミリ
serv.sin_port = htons(portNo); ← ポート番号(第2引数)
servAddr = gethostbyname(argv[1])
第1引数で指定されたIPアドレスをgethostbyname()を使って変換します。
変換したアドレスは、serv.sin_addr.s_addrに格納します。
connect(dfd, (const struct sockaddr*)&serv, sizeof(struct sockaddr))
dfd、socket()で得られたファイルディスクリプタ
構造体サイズ
この時点でサーバー側がaccept()で待っている状態であれば、サーバー側で受けれられて通信が可能となります。
このコードではユーザーからの入力をサーバに送り、サーバーからの応答を待ちます。サーバーからの応答が"QUIT"であったとき(ユーザーが"Quit"を入力したとき)はソケットのファイルディスクリプタを閉じて終了します。
それ以外の応答である場合は、ユーザー入力を続けます。
実行サンプル
| サーバー側 $ ./inet-domain-server 99999 Client Cennected : 192.168.0.130 HogeHoge Moshi Moshi Quit Received Quit $ |
クライアント側 $ ./inet-domain-client 192.168.0.5 99999 #HogeHoge NeXT#Moshi Moshi NeXT#Quit QUIT Received Quit $ |
ソケットについて
ソケットはもともとBSD系のUnixに実装されたものでした。共有メモリなどはSystem V IPCであると伝えましたが、以前UNIXは、System V系とBSD系の2系統に大きく別れ、APIも異なっていた時代がありました。それがSVR4で統合され、現在に至っています。
APIもいいものは生き残る。
でしょうか?
Have a Happy Hucking!!
Lightning Brains
Lightning Brains
コメント
コメントを投稿