naobe @ ウィキ
UNIX通信
最終更新:
Bot(ページ名リンク)
-
view
Unix/Linuxへ戻る
ストリーム通信
サーバ
- socketシステムコールを使ってソケット用のファイルデスクリプタ(sockfd)を作成 : fd = socket(AF_INET, SOCK_STREAM, 0)
- socketオプションを設定して、TIMEWAIT状態でのbindを可能にする。: setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))
- sockfdにサーバのIPアドレス、ポート、プロトコルタイプ(internet/unixドメイン)を関連付けする。:bind(fd, (struct sockaddr *) &servaddr, sizeof(servaddr)
- sockfdの受信キューを設定しクライアントから接続可能を宣言:listen(fd, listenq)
- クライアントからの接続を待つ:newfd = accept(fd, (struct sockaddr *) &cliaddr, &i) newfd:通信に使うファイルデスクリプタ、cliaddr:接続したクライアントのアドレス/ポート、i:cliaddrのサイズ
- 子プロセスをforkして処理は子プロセスに任せる。親は次の接続を待機する
if ( (pid = fork()) < 0)
err_sys("fork error");
if (pid > 0) {
close(newfd); /* parent closes connected socket */
WAIT_CHILD(); /* wait for child to output to terminal */
continue; /* and back to for(;;) for another accept() */
} else {
close(fd); /* child closes listening socket */
}
- 子プロセスは、newfdを使ってread(受信),write(送信)してクライアントと通信する。
クライアント
socket
socketの作成
オプション
以下のAPIでoptnameに設定する
int setsockopt(int s, int level, int optname, const void *optval, socklen_t optlen);
【引数】
| 引数 | 説明 |
|---|---|
| s | ソケットを識別する記述子。 |
| level | optionの層。SOL_SOCKET:ソケット層オプション |
| optname | オプション名 |
| optval | オプションの値 |
| optlen | オプションの長さ |
| level | optname | 説明 |
|---|---|---|
| SOL_SOCKET | SO_REUSEADDR | 設定するときは値に&1を設定する。設定解除には&0を設定する。TCP通信で、サーバ側が先に通信を終了したとき接続していたポートはTIMEWAIT状態になる。TIMEWAIT状態になるとbind不可になり接続を再開できない。これは他のプロセスがポートを使うことを制限するためで、使用していたプロセスが再開するには邪魔になる動き。SO_REUSEADDRを指定することで当該のプロセスがbindすることを許可する。(注1) |
| SOL_SOCKET | SO_KEEPALIVE | 設定するときは値に&1を設定する。設定解除には&0を設定する。デフォルトでは、無通信状態からキープアライブパケットを送信するまで2時間。送信間隔は75秒。リトライ回数は9回。リトライ回数を超えると切断する。(注2) |
| SOL_SOCKET | SO_SNDBUF | 設定するときは値に&1を設定する。設定解除には&0を設定する。 |
| SO_RCVBUF | 設定するときは値に&1を設定する。設定解除には&0を設定する。 | |
| SO_LINGER | 値は、linger構造体をセットする。 | |
| IPPROTO_TCP | TCP_NODELAY | 値に&1を設定するとNO_DELAYが有効になる。Nagle アルゴリズムを無効にする(複数のパケットをまとめない。)。遅れのないリクエストが必要なHTTPに有効。(注3) |
注1) http://www.geekpage.jp/programming/winsock/so_reuseaddr.php 参照
注2) http://d.hatena.ne.jp/iww/20081030/setsockopt 参照
注3) http://www.ibm.com/developerworks/jp/linux/library/l-hisock/ 参照
注2) http://d.hatena.ne.jp/iww/20081030/setsockopt 参照
注3) http://www.ibm.com/developerworks/jp/linux/library/l-hisock/ 参照
select
複数のファイルデスクリプタの入出力を管理する。複数のポート、UNIXドメイン、端末を1つのプログラムで管理できる。大規模なネットワークの高性能サーバに使う。
システムコール
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
【引数】
| 引数 | 説明 |
|---|---|
| nfds | 登録したファイルディスクリプタの最大値+1 |
| readfds | readを開始するディスクリプタセット |
| writefds | writeを監視するディスクリプタセット |
| exceptfds | 帯域外データ受信を監視するディスクリプタセット |
| timeout | タイムアウト時間。NULLを指定すると監視対象の状態が変化するまで待つ |
【戻り値】
状態が変化したディスクリプタの数。-1はエラー。
状態が変化したディスクリプタの数。-1はエラー。
プログラム
CentOSのmanページのサンプルプログラム。あるポートの接続を待って、別のポートに転送するサーバ。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
static int forward_port;
#undef max
#define max(x,y) ((x) > (y) ? (x) : (y))
/**
* ポートを指定してソケット作成、listen
*
* @param listen_port ポート番号
*
*/
static int listen_socket(int listen_port) {
struct sockaddr_in a;
int s;
int yes;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {//ソケット作成
perror("socket");
return -1;
}
yes = 1;
//ソケットオプションを指定
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &yes, sizeof(yes)) < 0) {
perror("setsockopt");
close(s);
return -1;
}
memset(&a, 0, sizeof(a));
a.sin_port = htons(listen_port);//ポート番号をネットワークバイトオーダに変換
a.sin_family = AF_INET;//インターネットを指定
if (bind(s, (struct sockaddr *) &a, sizeof(a)) < 0) {
perror("bind");
close(s);
return -1;
}
printf("accepting connections on port %d\n", (int) listen_port);
listen(s, 10);// 受信キューの個数を10にしてlisten
return s;
}
/*
* 転送先へ接続する
*
* @param connect_port 接続ポート
* @param address 転送先アドレス
* @return ソケットファイルディスクリプタ
*/
static int connect_socket(int connect_port, char *address) {
struct sockaddr_in a;
int s;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket");
close(s);
return -1;
}
memset(&a, 0, sizeof(a));
a.sin_port = htons(connect_port);
a.sin_family = AF_INET;
if (!inet_aton(address, (struct in_addr *) &a.sin_addr.s_addr)) {
perror("bad IP address format");
close(s);
return -1;
}
if (connect(s, (struct sockaddr *) &a, sizeof(a)) < 0) {
perror("connect()");
shutdown(s, SHUT_RDWR);
close(s);
return -1;
}
return s;
}
#define SHUT_FD1 { \
if (fd1 >= 0) { \
shutdown (fd1, SHUT_RDWR); \
close (fd1); \
fd1 = -1; \
} \
}
#define SHUT_FD2 { \
if (fd2 >= 0) { \
shutdown (fd2, SHUT_RDWR); \
close (fd2); \
fd2 = -1; \
} \
}
#define BUF_SIZE 1024
/***
* selectを使った転送処理
*
* 引数1 リッスンポート
* 引数2 転送ポート
* 引数3 転送先アドレス
*/
int main(int argc, char **argv) {
int h;
int fd1 = -1, fd2 = -1; // fd1:受信ディスクリプタ、fd2:送信ディスクリプタ
char buf1[BUF_SIZE], buf2[BUF_SIZE];
int buf1_avail, buf1_written;
int buf2_avail, buf2_written;
if (argc != 4) {
fprintf(
stderr,
"Usage\n\tfwd <listen-port> \
<forward-to-port> <forward-to-ip-address>\n");
exit(1);
}
signal(SIGPIPE, SIG_IGN);
forward_port = atoi(argv[2]);
h = listen_socket(atoi(argv[1]));
if (h < 0)
exit(1);
for (;;) {
int r, nfds = 0;
fd_set rd, wr, er;
FD_ZERO(&rd);
FD_ZERO(&wr);
FD_ZERO(&er);
FD_SET(h, &rd);//リッスンポートを読み込みfdとして登録
nfds = max (nfds, h);
if (fd1 > 0 && buf1_avail < BUF_SIZE) {
FD_SET(fd1, &rd);
nfds = max (nfds, fd1);
}
if (fd2 > 0 && buf2_avail < BUF_SIZE) {
FD_SET(fd2, &rd);
nfds = max (nfds, fd2);
}
if (fd1 > 0 && buf2_avail - buf2_written > 0) {
FD_SET(fd1, &wr);
nfds = max (nfds, fd1);
}
if (fd2 > 0 && buf1_avail - buf1_written > 0) {
FD_SET(fd2, &wr);
nfds = max (nfds, fd2);
}
if (fd1 > 0) {
FD_SET(fd1, &er);
nfds = max (nfds, fd1);
}
if (fd2 > 0) {
FD_SET(fd2, &er);
nfds = max (nfds, fd2);
}
//リッスンポートの接続を待つ
r = select(nfds + 1, &rd, &wr, &er, NULL);
//
if (r == -1 && errno == EINTR)
continue;
if (r < 0) {
perror("select()");
exit(1);
}
/*
* 受信ポートが接続されたらacceptして新しいファイルディスクリプタ作成。
* 転送先へ接続する
*
*/
if (FD_ISSET(h, &rd)) {
unsigned int l;
struct sockaddr_in client_address;
memset(&client_address, 0, l = sizeof(client_address));
r = accept(h, (struct sockaddr *) &client_address, &l);
if (r < 0) {
perror("accept()");
} else {
SHUT_FD1;
SHUT_FD2;
buf1_avail = buf1_written = 0;
buf2_avail = buf2_written = 0;
fd1 = r;
fd2 = connect_socket(forward_port, argv[3]);
if (fd2 < 0) {
SHUT_FD1;
} else
printf("connect from %s\n",
inet_ntoa(client_address.sin_addr));
}
}
/* NB: read oob data before normal reads */
if (fd1 > 0)
// 帯域外データ(バッファリングを許されないイベント)が到着したら
// 帯域外データとして転送先に転送する
if (FD_ISSET(fd1, &er)) {
char c;
errno = 0;
r = recv(fd1, &c, 1, MSG_OOB);
//エラーが発生したら受信ポートを閉設
if (r < 1) {
SHUT_FD1;
} else
send(fd2, &c, 1, MSG_OOB);
}
if (fd2 > 0)
if (FD_ISSET(fd2, &er)) {
char c;
errno = 0;
r = recv(fd2, &c, 1, MSG_OOB);
if (r < 1) {
SHUT_FD1;
} else
send(fd1, &c, 1, MSG_OOB);
}
/*
* 端点1から要求があれば、最大、バッファの残りまで読み込む
*/
if (fd1 > 0)
if (FD_ISSET(fd1, &rd)) {
r = read(fd1, buf1 + buf1_avail, BUF_SIZE - buf1_avail);
if (r < 1) {
SHUT_FD1;
} else
buf1_avail += r;
}
/*
* 端点2から要求があれば、最大、バッファの残りまで読み込む
*/
if (fd2 > 0)
if (FD_ISSET(fd2, &rd)) {
r = read(fd2, buf2 + buf2_avail, BUF_SIZE - buf2_avail);
if (r < 1) {
SHUT_FD2;
} else
buf2_avail += r;
}
/*
* 端点1が受信準備できていれば、書きこみの残りを送信する
*/
if (fd1 > 0)
if (FD_ISSET(fd1, &wr)) {
r = write(fd1, buf2 + buf2_written, buf2_avail - buf2_written);
if (r < 1) {
SHUT_FD1;
} else
buf2_written += r;
}
/*
* 端点2が受信準備できていれば、書きこみの残りを送信する
*/
if (fd2 > 0)
if (FD_ISSET(fd2, &wr)) {
r = write(fd2, buf1 + buf1_written, buf1_avail - buf1_written);
if (r < 1) {
SHUT_FD2;
} else
buf1_written += r;
}
//受信量と送信量が一致したら、バッファ位置を初期化する
if (buf1_written == buf1_avail)
buf1_written = buf1_avail = 0;
if (buf2_written == buf2_avail)
buf2_written = buf2_avail = 0;
/* one side has closed the connection, keep
writing to the other side until empty */
if (fd1 < 0 && buf1_avail - buf1_written == 0) {
SHUT_FD2;
}
if (fd2 < 0 && buf2_avail - buf2_written == 0) {
SHUT_FD1;
}
}
return 0;
}
FD_SET
ファイルディスクリプタセットに監視するファイルディスクリプタをセットする。
インタフェース
void FD_SET(int fd, fd_set *set);
| 項目 | IN/OUT | 説明 |
|---|---|---|
| fd | IN | 監視するファイルディスクリプタ |
| set | IN | 監視するファイルディスクリプタの集合 |
注意
追加するファイルディスクリプタの数は、FD_SETSIZE(CentOS 5.5の場合は、1024)未満でなければならない。
FD_ISSET
ファイルディスクリプタセットにセットしたファイルデスクリプタが使用可能か確認する。
インタフェース
int FD_ISSET(int fd, fd_set *set);
| 項目 | IN/OUT | 説明 |
|---|---|---|
| fd | IN | ファイルディスクリプタ |
| set | IN | ファイルディスクリプタセット |
| 戻り値 | OUT | 1:使用可能、0:準備中 |
poll
ファイルディスクリプタセットに登録したファイルディスクリプタが使用可能になるまで待つ。selectの改良版。
インタフェース
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
| 項目 | IN/OUT | 説明 |
|---|---|---|
| fds | IN | ファイルディスクリプタセットの配列。 |
| nfds | IN | 配列の個数 |
| events | IN | 監視するイベントのビットマスク |
| revents | OUT | 実際に起こったイベントのビットマスク |
| timeout | IN | pollがブロックする時間の上限(msec)。負の数を設定すると無限に待つ。 |
| 戻り値 | OUT | 正の数:成功。reventsの数。、0:タイムアウト、-1:エラー |
イベントは、poll.hで定義される。
| イベント | 説明 |
|---|---|
| POLLIN | 読み出し可能なデータがある。 |
| POLLPRI | 読み出し可能な緊急データ (urgent data) がある |
| POLLOUT | 書き込みが停止 (block) しない状態である。書き込み可能である。 |
| POLLRDHUP | Linux 2.6.17 以降。ス トリームソケットの他端が、コネクションを close したか 、コネクションの書き込み側を shutdown した。 |
| POLLERR | エラー状態 (出力の場合のみ)。 |
| POLLHUP | ハングアップした (出力の場合のみ)。 |
| POLLNVAL | 不正な要求: fd がオープンされていない (出力の場合のみ)。 |
epoll
http://alpha.mixi.co.jp/2007/10631/ 参照
pollの一種。非同期なポールが可能?
pollの一種。非同期なポールが可能?
int epoll_create(int size)
epollディスクリプタを作成する。
| 項目 | IN/OUT | 説明 |
|---|---|---|
| size | IN | ディスクリプタの大きさ |
| 戻り値 | OUT | epollディスクリプタ |
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epollディスクリプタを操作(追加、削除)する
| 項目 | IN/OUT | 説明 |
|---|---|---|
| epfd | IN | epollディスクリプタ |
| op | IN | 操作。 |
| fd | IN | 対象となるファイルディスクリプタ |
| event |
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epollディスクリプタのI/Oイベントを待つ
| 項目 | IN/OUT | 説明 |
|---|---|---|
| epfd | IN | epollディスクリプタ |
| events | OUT | 呼出側が利用可能なイベント |
| maxevents | IN | イベントの最大数 |
| timeout | IN | タイムアウト(msec) |
| 戻り値 | OUT | 準備ができているファイルディスクリプタの数。0:タイムアウト。-1:エラー |
getaddrinfo
ノードの名前、IPアドレス、サービスからアドレス情報を取得する
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
| 項目 | IN/OUT | 説明 |
|---|---|---|
| node | IN | ノードの名前、IPアドレス |
| service | IN | |
| hints | IN | |
| res | OUT |
名前つきパイプ(Named pipe)
プロセス間の通信を行うためのファイル。mkfifoコマンドを用いて作成する。作成した名前つきパイプに対し て、write, readすることによってプロセス間通信を行う。4096バイトを超えるwriteを行うとread開始される まで待たされる。
fcntl
インタフェース
int fcntl(int fd, int cmd); int fcntl(int fd, int cmd, long arg); int fcntl(int fd, int cmd, struct flock *lock);
| 項目 | IN/OUT | 説明 |
|---|---|---|
| fd | IN | ファイルディスクリプタ |
| cmd | IN | 操作 |
| arg | IN | 引数 |
| lock | ロック |
| cmd | 説明 |
|---|---|
| F_SETFD | ファイルディスクリプタフラグにargで指定した値を設定する |
| F_GETFD | ファイルディスクリプタフラグを読み出す。戻り値が読み出したファイルディスクリプタフラグ。 |
| F_SETFL | ファイル状態フラグにargで指定した値を設定する |
| F_GETFL | ファイル状態フラグを読み出す。戻り値が読み出したファイル状態フラグ。 |
F_SETFD/F_GETFDで使えるフラグは、FD_CLOEXEC のみ。FD_CLOEXECは、execveしたときに親プロセスが開いたファイルディスクリプタを閉じる。
