2007年6月16日土曜日

プロセス間通信の方法の一つとしてsocketを使う

概要

プロセス間通信の方法の一つとしてsocketを使う。
通信を行う二つのプロセスのうち、一方をserver、もう一方をclientとする。
serverは通信を行うための口を用意する。
通信の準備

通信を行う口は以下の手順で用意する。これはserverが行う。

* socketを作る。(socket)
* socketに名前を付ける。(bind)
* 受け入れOKを示す。(listen)

これで通信を行う口は用意された。この後、clientは自分で作ったsocketをこの口に結合(connect)し、serverはそれを受け入れる(accept)。serverのsocketに名前を付けるのは、clientがその名前のsocketに結合するのに識別名が必要だからである。
connectとacceptはどちらが先に発行されてもかまわない。お互い結合されるのを待つ。

socketの名前

socketを作るとき、定義域としてAF_UNIXかAF_INETを指定する。それぞれ、UNIXドメイン、INETドメインと呼ばれる。
名前を付けるときはそのドメインの中で一意でなければならない。
UNIXドメインは、ひとつの計算機がその範囲である。この中で一意であると保証するのに簡単な方法としてファイルがある。そこで、名前を作るとその名前のファイルが作られる。こうすれば、そのシステムで一意であることが保証される。ただし、作られたファイルは残ってしまう。通信が終了しても自動的に消してはくれない。
INETドメインでは、ネットワークで結合されたすべてがその範囲である。この中で一意に名前を定めるのは容易ではない。そこで、まず計算機にネットワークで一意な名前を付け、それと組合せて名前を付ける。具体的には、計算機に付けられる名前はIPアドレスである、組み合わされるのはポート番号である。つまり、IPアドレス+ポート番号でINETdドメインで一意な名前を付ける。
通信

お互い結合してしまえば、後は通信するだけである。
送信はsend/writeで、受信はrecv/readで行う。socketを作るとファイルディスクリプタが与えられるので、低レベルのファイルアクセスと大体同じことができるので、read/writeも使える。通信と言ってもそんなに特殊なことをするわけではない。
送信は送りつけるだけだが、受信は相手から何か送られてくるのを待つので、間違えるとデッドロックしてしまう。ただし、結合そのものが切れてしまうと、recv/readで待っていた場合受信データ長0で抜けてきてくれる。
通信の終了

プロセスそのものを終わらせてしまってもいいのだが、一応終了の手順は以下の通りである。

* 結合を切る(shutdown)
* socketをクローズ(close)

UNIXドメインの場合、付けた名前のファイルが残るので、これを消しておく(unlink)。

プログラム例

以下のプログラム例は、serverがclientからのメッセージを受信して表示するものである。

* UNIXドメイン版 server
* UNIXドメイン版 client
* INETドメイン版 server
* INETドメイン版 client
* Makefile

複数のclientと通信するserver

ここまでは基本ということで1対1の通信の話をしてきたが、serverと呼ばれるものであれば複数のclientを通信する場合もある。こういう場合、selectを使うとよい。
これは、待ちたいフィルディスクリプタのリストを渡して、どれか来たら抜けて来てくれる。時間を指定すればその時間を過ぎれば何も来ていなくても抜けて来てくれる。
以下のプログラム例は、これを使って、複数のclientからの要求を処理するserverと、1秒毎にメッセージを送るclientである。UNIXドメイン版のみだが、もちろんINETドメインでも動作する。また、無限ループするプログラムなので、signalで終了を検知した場合終了処理をするようにしてある。

* server
* client
* Makefile

実際に使って気付いたこと

実際使ってみて気付いたことを以下に記すが、いろいろな環境で試したわけではないので、もしかしたら大丈夫かもしれない。

* selectを使って、複数のファイルディスクリプタを扱えるのはいいのだけど、最大で64までしか使えない。これ以上になると落ちる。
後でわかったことだが、一つのプロセスがオープンできるファイルの数は、limits.hのOPEN_MAXで既定されていて、これが64だから。(1998.11.19)
* UNIXドメインやINETドメインでもlocalhostの場合は、送受信はどんなデータでも大丈夫だが、INETドメインで別の計算機と通信する場合は文字列でないとちゃんとデータの受け渡しができない。

プロセス間通信の方法の一つとしてsocketを使う

概要

プロセス間通信の方法の一つとしてsocketを使う。
通信を行う二つのプロセスのうち、一方をserver、もう一方をclientとする。
serverは通信を行うための口を用意する。
通信の準備

通信を行う口は以下の手順で用意する。これはserverが行う。

* socketを作る。(socket)
* socketに名前を付ける。(bind)
* 受け入れOKを示す。(listen)

これで通信を行う口は用意された。この後、clientは自分で作ったsocketをこの口に結合(connect)し、serverはそれを受け入れる(accept)。serverのsocketに名前を付けるのは、clientがその名前のsocketに結合するのに識別名が必要だからである。
connectとacceptはどちらが先に発行されてもかまわない。お互い結合されるのを待つ。

socketの名前

socketを作るとき、定義域としてAF_UNIXかAF_INETを指定する。それぞれ、UNIXドメイン、INETドメインと呼ばれる。
名前を付けるときはそのドメインの中で一意でなければならない。
UNIXドメインは、ひとつの計算機がその範囲である。この中で一意であると保証するのに簡単な方法としてファイルがある。そこで、名前を作るとその名前のファイルが作られる。こうすれば、そのシステムで一意であることが保証される。ただし、作られたファイルは残ってしまう。通信が終了しても自動的に消してはくれない。
INETドメインでは、ネットワークで結合されたすべてがその範囲である。この中で一意に名前を定めるのは容易ではない。そこで、まず計算機にネットワークで一意な名前を付け、それと組合せて名前を付ける。具体的には、計算機に付けられる名前はIPアドレスである、組み合わされるのはポート番号である。つまり、IPアドレス+ポート番号でINETdドメインで一意な名前を付ける。
通信

お互い結合してしまえば、後は通信するだけである。
送信はsend/writeで、受信はrecv/readで行う。socketを作るとファイルディスクリプタが与えられるので、低レベルのファイルアクセスと大体同じことができるので、read/writeも使える。通信と言ってもそんなに特殊なことをするわけではない。
送信は送りつけるだけだが、受信は相手から何か送られてくるのを待つので、間違えるとデッドロックしてしまう。ただし、結合そのものが切れてしまうと、recv/readで待っていた場合受信データ長0で抜けてきてくれる。
通信の終了

プロセスそのものを終わらせてしまってもいいのだが、一応終了の手順は以下の通りである。

* 結合を切る(shutdown)
* socketをクローズ(close)

UNIXドメインの場合、付けた名前のファイルが残るので、これを消しておく(unlink)。

プログラム例

以下のプログラム例は、serverがclientからのメッセージを受信して表示するものである。

* UNIXドメイン版 server
* UNIXドメイン版 client
* INETドメイン版 server
* INETドメイン版 client
* Makefile

複数のclientと通信するserver

ここまでは基本ということで1対1の通信の話をしてきたが、serverと呼ばれるものであれば複数のclientを通信する場合もある。こういう場合、selectを使うとよい。
これは、待ちたいフィルディスクリプタのリストを渡して、どれか来たら抜けて来てくれる。時間を指定すればその時間を過ぎれば何も来ていなくても抜けて来てくれる。
以下のプログラム例は、これを使って、複数のclientからの要求を処理するserverと、1秒毎にメッセージを送るclientである。UNIXドメイン版のみだが、もちろんINETドメインでも動作する。また、無限ループするプログラムなので、signalで終了を検知した場合終了処理をするようにしてある。

* server
* client
* Makefile

実際に使って気付いたこと

実際使ってみて気付いたことを以下に記すが、いろいろな環境で試したわけではないので、もしかしたら大丈夫かもしれない。

* selectを使って、複数のファイルディスクリプタを扱えるのはいいのだけど、最大で64までしか使えない。これ以上になると落ちる。
後でわかったことだが、一つのプロセスがオープンできるファイルの数は、limits.hのOPEN_MAXで既定されていて、これが64だから。(1998.11.19)
* UNIXドメインやINETドメインでもlocalhostの場合は、送受信はどんなデータでも大丈夫だが、INETドメインで別の計算機と通信する場合は文字列でないとちゃんとデータの受け渡しができない。

プロセス間通信の方法の一つとしてsocketを使う

概要

プロセス間通信の方法の一つとしてsocketを使う。
通信を行う二つのプロセスのうち、一方をserver、もう一方をclientとする。
serverは通信を行うための口を用意する。
通信の準備

通信を行う口は以下の手順で用意する。これはserverが行う。

* socketを作る。(socket)
* socketに名前を付ける。(bind)
* 受け入れOKを示す。(listen)

これで通信を行う口は用意された。この後、clientは自分で作ったsocketをこの口に結合(connect)し、serverはそれを受け入れる(accept)。serverのsocketに名前を付けるのは、clientがその名前のsocketに結合するのに識別名が必要だからである。
connectとacceptはどちらが先に発行されてもかまわない。お互い結合されるのを待つ。

socketの名前

socketを作るとき、定義域としてAF_UNIXかAF_INETを指定する。それぞれ、UNIXドメイン、INETドメインと呼ばれる。
名前を付けるときはそのドメインの中で一意でなければならない。
UNIXドメインは、ひとつの計算機がその範囲である。この中で一意であると保証するのに簡単な方法としてファイルがある。そこで、名前を作るとその名前のファイルが作られる。こうすれば、そのシステムで一意であることが保証される。ただし、作られたファイルは残ってしまう。通信が終了しても自動的に消してはくれない。
INETドメインでは、ネットワークで結合されたすべてがその範囲である。この中で一意に名前を定めるのは容易ではない。そこで、まず計算機にネットワークで一意な名前を付け、それと組合せて名前を付ける。具体的には、計算機に付けられる名前はIPアドレスである、組み合わされるのはポート番号である。つまり、IPアドレス+ポート番号でINETdドメインで一意な名前を付ける。
通信

お互い結合してしまえば、後は通信するだけである。
送信はsend/writeで、受信はrecv/readで行う。socketを作るとファイルディスクリプタが与えられるので、低レベルのファイルアクセスと大体同じことができるので、read/writeも使える。通信と言ってもそんなに特殊なことをするわけではない。
送信は送りつけるだけだが、受信は相手から何か送られてくるのを待つので、間違えるとデッドロックしてしまう。ただし、結合そのものが切れてしまうと、recv/readで待っていた場合受信データ長0で抜けてきてくれる。
通信の終了

プロセスそのものを終わらせてしまってもいいのだが、一応終了の手順は以下の通りである。

* 結合を切る(shutdown)
* socketをクローズ(close)

UNIXドメインの場合、付けた名前のファイルが残るので、これを消しておく(unlink)。

プログラム例

以下のプログラム例は、serverがclientからのメッセージを受信して表示するものである。

* UNIXドメイン版 server
* UNIXドメイン版 client
* INETドメイン版 server
* INETドメイン版 client
* Makefile

複数のclientと通信するserver

ここまでは基本ということで1対1の通信の話をしてきたが、serverと呼ばれるものであれば複数のclientを通信する場合もある。こういう場合、selectを使うとよい。
これは、待ちたいフィルディスクリプタのリストを渡して、どれか来たら抜けて来てくれる。時間を指定すればその時間を過ぎれば何も来ていなくても抜けて来てくれる。
以下のプログラム例は、これを使って、複数のclientからの要求を処理するserverと、1秒毎にメッセージを送るclientである。UNIXドメイン版のみだが、もちろんINETドメインでも動作する。また、無限ループするプログラムなので、signalで終了を検知した場合終了処理をするようにしてある。

* server
* client
* Makefile

実際に使って気付いたこと

実際使ってみて気付いたことを以下に記すが、いろいろな環境で試したわけではないので、もしかしたら大丈夫かもしれない。

* selectを使って、複数のファイルディスクリプタを扱えるのはいいのだけど、最大で64までしか使えない。これ以上になると落ちる。
後でわかったことだが、一つのプロセスがオープンできるファイルの数は、limits.hのOPEN_MAXで既定されていて、これが64だから。(1998.11.19)
* UNIXドメインやINETドメインでもlocalhostの場合は、送受信はどんなデータでも大丈夫だが、INETドメインで別の計算機と通信する場合は文字列でないとちゃんとデータの受け渡しができない。

selectを使う

ここでは、selectを使って複数のソケットからデータを受け取る方法を説明したいと思います。
select

普通の状態では、recvやrecvfromはデータが受信できるまでブロッキングします。ソケットを一つしか利用していない場合にはブロッキングは非常に便利なのですが、ソケットが複数になると困ってしまいます。複数のソケットを扱うとき、片方のソケットでブロッキングしたままになってしまうと他のソケットにデータが到着しても受信が出来なくなってしまいます。そのため、複数のソケットを扱っていると、どのソケットからデータが受信可能か知りたくなります。

ブロッキングとは、関数が返ってこない事を表します。例えば、recvはデータを受信して関数が戻ってきます。言い方を変えると、データを受信するまでブロックしています。 recvやrecvfromをブロッキングしないノンブロッキング方式で使う事も可能ですが、ここではブロッキング方式のまま使う方法を説明します。

そのような機能を提供するのがselectです。 selectを使うと、データが受信可能なソケットでのみrecvやrecvfromを実行することができます。 selectは登録したソケットをブロッキング状態で監視し、どれかがデータ受信するとブロッキング状態を解除します。
selectを使うサンプルコード

selectは受信可能かどうかだけではなく、送信可能かどうか、エラーがあるかなどもチェックできますがここでは受信可能かどうかをチェックする方法のみをとりあげます。

下記サンプルコードでは、UDPソケットを2つ作成しています。作成した2つのUDPソケットは、selectに登録してselectはブロッキング状態に入ります。 UDPソケットにデータが到着するとrecvを行い、受信した内容を表示します。


#include
#include

int
main()
{
SOCKET sock1, sock2;
struct sockaddr_in addr1, addr2;
fd_set fds, readfds;
char buf[2048];
WSADATA wsaData;

WSAStartup(MAKEWORD(2,0), &wsaData);

// 受信ソケットを2つ作ります
sock1 = socket(AF_INET, SOCK_DGRAM, 0);
sock2 = socket(AF_INET, SOCK_DGRAM, 0);

addr1.sin_family = AF_INET;
addr2.sin_family = AF_INET;

addr1.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
addr2.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

// 2つの別々のポートで待つために別のポート番号をそれぞれ設定します
addr1.sin_port = htons(11111);
addr2.sin_port = htons(22222);

// 2つの別々のポートで待つようにbindします
bind(sock1, (struct sockaddr *)&addr1, sizeof(addr1));
bind(sock2, (struct sockaddr *)&addr2, sizeof(addr2));

// fd_setの初期化します
FD_ZERO(&readfds);

// selectで待つ読み込みソケットとしてsock1を登録します
FD_SET(sock1, &readfds);
// selectで待つ読み込みソケットとしてsock2を登録します
FD_SET(sock2, &readfds);

// 無限ループです
// このサンプルでは、この無限ループを抜けません
while (1) {
// 読み込み用fd_setの初期化
// selectが毎回内容を上書きしてしまうので、毎回初期化します
memcpy(&fds, &readfds, sizeof(fd_set));

// fdsに設定されたソケットが読み込み可能になるまで待ちます
select(0, &fds, NULL, NULL, NULL);

// sock1に読み込み可能データがある場合
if (FD_ISSET(sock1, &fds)) {
// sock1からデータを受信して表示します
memset(buf, 0, sizeof(buf));
recv(sock1, buf, sizeof(buf), 0);
printf("%s\n", buf);
}

// sock2に読み込み可能データがある場合
if (FD_ISSET(sock2, &fds)) {
// sock2からデータを受信して表示します
memset(buf, 0, sizeof(buf));
recv(sock2, buf, sizeof(buf), 0);
printf("%s\n", buf);
}
}

// このサンプルでは、ここへは到達しません
closesocket(sock1);
closesocket(sock2);

WSACleanup();

return 0;
}

selectを使うサンプルコードにデータを送信するコード

selectのサンプルコードは、UDPの11111番と22222番のポートでデータを待っています。 UDPを使って11111番と22222番ポートにデータを送信するサンプルを以下に示します。


#include
#include

int
main()
{
SOCKET sock;
struct sockaddr_in dest1, dest2;
char buf[1024];
WSADATA wsaData;

WSAStartup(MAKEWORD(2,0), &wsaData);

sock = socket(AF_INET, SOCK_DGRAM, 0);

dest1.sin_family = AF_INET;
dest2.sin_family = AF_INET;

dest1.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
dest2.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

dest1.sin_port = htons(11111);
dest2.sin_port = htons(22222);

memset(buf, 0, sizeof(buf));
_snprintf(buf, sizeof(buf), "data to port 11111");
sendto(sock,
buf, strlen(buf), 0, (struct sockaddr *)&dest1, sizeof(dest1));

memset(buf, 0, sizeof(buf));
_snprintf(buf, sizeof(buf), "data to port 22222");
sendto(sock,
buf, strlen(buf), 0, (struct sockaddr *)&dest2, sizeof(dest2));

closesocket(sock);

WSACleanup();

return 0;
}

最後に

ここでは、UDPを使ったサンプルを示しましたが、TCPを使っても同様の事が出来ます。 TCPの場合は、acceptした後のソケットだけではなく、listenしているソケットに対するselectも可能です。また、winsockにはWindowsイベント方式でイベントが飛んでくるselectもあります。

SOCKETプログラミング

このページではBerkeley SOCKET、Winsock及びJava Socketのプログラミングについて紹介する。SOCKETも好きだな。

 記述例およびサンプルに含まれるファイルの全部、または一部を使用したことによる損害等について、一切の責任を負いません。また、サンプルの文字コードはS-JISで提供しますので、ご使用の際はWindowsからFTPするなどして適切な文字コードに変換してください。尚、サンプル中には説明の簡略化のため意味のないコードや、実用上問題のあるコードも含まれていますのでご注意ください。

1. [UNIX]スレッドでrecv
2. [UNIX]selectを使う
3. [Winsock]スレッドでrecv
4. [Winsock]selectを使う
5. [Winsock]イベントを使う
6. [Java]Socket(クライアント)
7. [Java]ServerSocket(サーバー)

[UNIX]スレッドでrecv
 SOCKETを使う上で考慮しなければならないのが受信の実現方法だ。データを受信する関数であるrecvがブロッキングするからだ。僕が一番好きなのはスレッドを使用する方法だ。受信専用のスレッドを新たに生成すればrecvがブロッキングしても影響はそのスレッドのみに限定されるので、例えばキー入力などは平行して処理されるので問題はなくなる。

 今までスレッドを使用したことがない方は、スレッドと聞くだけで躊躇されるかもしれないが、実際にやってみればそれほど難しいものではない。スレッドに関しては別にページを用意しているのでそちらも参照して欲しい。

<受信スレッドの生成>

/* 受信スレッド生成 */
pthread_create(&tid,NULL,waitReceiveThread,NULL);
pthread_join(tid,NULL); /* 受信スレッド終了待ち */

<受信スレッドルーチン>

void*
waitReceiveThread(void* pParam)
{
char buf[1024];
int recvSize;

while(1)
{
/* データ受信待ち */
recvSize = recv(fd,buf,1024,0);

printf("Received %d bytes\n",recvSize);

if(recvSize == 0) /* 相手CLOSE? */
{
printf("check\n");
close(fd);
break;
}
}

return NULL;
}

 受信スレッドルーチンは単にrecvのループになっている。recvはデータを受信するまで戻ってこないのでCPU時間は消費しない。SOCKETがクローズされるとrecvは戻り値=0で戻ってくるので、その場合にはループをbreakする処理を入れている。受信スレッドの生成元スレッドは pthread_joinで新たに生成したスレッドの終了を待機しているので、受信スレッドルーチンが終了すれば生成元スレッドも終了する。
[UNIX]selectを使う
 正直言って僕はselectはあまり好きではない。でも、とりあえず載せておく。まず、FD_ZEROマクロでfd_setを初期化します。次にFD_SETマクロでselectで待ちたいSOCKETの fdを設定する。このfd_setを使ってselectを呼び出すと、データを受信するまでselectはブロックするがCPU時間は消費しない。 selectから戻ったところでFD_ISSETマクロを用いて該当fdの読み込みデータがあるかどうかを調べ、データがある場合はrecvを呼び出す。

int
waitReceive(void)
{
fd_set fds;
char buf[1024];
int recvSize;
/* バッチ共通初期処理 */
while(1) /* メッセージループ */
{
FD_ZERO(&fds); /* fd_set初期化 */
FD_SET(fd,&fds); /* fd設定 */
select(fd + 1,&fds,NULL,NULL,NULL); /* データ受信待ち */

if(FD_ISSET(fd,&fds)) /* 該当fd読み込みデータあり? */
{
/* データ受信 */
recvSize = recv(fd,buf,1024,0);

printf("Received %d bytes\n",recvSize);

if(recvSize == 0) /* 相手CLOSE? */
{
printf("check\n");
close(fd);
break;
}
}
}

return 0;
}

[Winsock]スレッドでrecv
 Win32でもスレッドが使用できるので、新たにスレッドを生成して通常の処理は平行して行いながらrecvで待つことができる。Win32のスレッドモデルはpthreadに比べるとやや煩雑になっており、加えてWinsockでは様々な処理方式を選択可能なのだが、スレッドを使用した方式が最もシンプルであろう。この例ではCのライブラリを使用するために、_beginthreadexを使用しているが、CreateThreadでも同じだ。スレッドに関しては別にページを用意しているのでそちらも参照して欲しい。

<受信スレッドの生成>

/* 受信スレッド生成 */
hThread = (HANDLE)_beginthreadex(NULL,0,waitReceiveThread,NULL,0,&threadId);

WaitForSingleObject(hThread,INFINITE); /* 受信スレッド終了待ち */
CloseHandle(hThread); /* ハンドルクローズ */

<受信スレッドルーチン>

DWORD WINAPI
waitReceiveThread(LPVOID pParam)
{
char buf[1024];
int recvSize;

while(1)
{
/* データ受信待ち */
recvSize = recv(fd,buf,1024,0);

printf("Received %d bytes\n",recvSize);

if(recvSize == 0) /* 相手CLOSE? */
{
printf("check\n");
closesocket(fd); /* CLOSE */
break;
}
}

return 0;
}

 この例でも受信スレッドルーチンの生成元スレッドは、pthreadのときと同様に新たに生成したスレッドの終了を待機している。Win32ではスレッドのハンドルがシグナル状態になるのをWaitForSingleObjectで待機する。受信スレッドルーチンが終了すれば生成元スレッドも終了する。

スレッドrecvのサンプル(ソースはLinuxでもコンパイルできるようになっている。UNIXとWindowsの違いを見てみて欲しい。)
[Winsock]selectを使う
 selectを使う場合はUNIXのSOCKETとまったく同じだ。よりWindowsっぽく書くなら、WSAAsyncSelectを使うという手があるが、こちらはメッセージを使用する。メッセージを使用するにはウィンドウが必要になる僕はメッセージの仕組みをあまり詳しくないので専らイベントを使用する。
[Winsock]イベントを使う
 Winsock2.0ではWin32のイベントオブジェクトを使用することができる。メッセージを利用しないのでウィンドウが必要なく、GUIがないプログラムで使いやすい。Anpsockもこの方式を使っている。ところが、イベントを使ったWinsockの解説書はあまりない。そもそも、Winsock2.0の解説書が少ない。

 イベントに関しては別にページを用意しているのでそちらも参照して欲しい。

int
waitReceive(void)
{
char buf[1024];
int recvSize;
int nRet;
HANDLE hEvent = WSACreateEvent();
WSANETWORKEVENTS events;

WSAEventSelect(fd,hEvent,FD_READ|FD_CLOSE);

while(1)
{
/* イベント待ち */
nRet = WSAWaitForMultipleEvents(1,&hEvent,FALSE,WSA_INFINITE,FALSE);

if(nRet == WSA_WAIT_FAILED)
{
printf("!!!!!!!!!!!!!!!!!!!!1\n");
break;
}

/* イベントの調査 */
if(WSAEnumNetworkEvents(fd,hEvent,&events) == SOCKET_ERROR)
{
printf("???\n");
}
else
{
/* CLOSE */
if(events.lNetworkEvents & FD_CLOSE)
{
printf("close\n");
closesocket(fd);
break;
}
/* READ */
if(events.lNetworkEvents & FD_READ)
{
recvSize = recv(fd,buf,1024,0);
printf("Received %d bytes\n",recvSize);
}
}
}

WSACloseEvent(hEvent); /* イベントクローズ */

return 0;
}

イベントrecvのサンプル
[Java]Socket(クライアント)
 Javaでクライアントのソケットを作成するにはホストとポートを指定してSocketをnewするだけである。たったのこれだけでCで言うところのsocketからconnectの呼び出しまで済んでしまうから驚きである。

// クライアントソケット作成
Socket conn = new Socket("localhost",port);
System.out.println("Connecting to port " + port);
// データ受信スレッド生成
RecvData rd = new RecvData(conn.getInputStream());
rd.start();

 データの受信は他の言語と同様スレッドを用いている。実際にサンプルを見ていただければお分かりになると思うが、わずか5、60行でクライアントのプログラムが書けるのである。素晴らしい!

class RecvData extends Thread {
InputStream st;

public RecvData(InputStream st) {
this.st = st;
}

public void run() {
int data;

try {
// データ読み込み
while((data = st.read()) != -1) {
System.out.print((char)data);
}
} catch(IOException e) {
System.out.println(e);
}
}
}

[Java]ServerSocket(サーバー)
 Javaでサーバーソケットを作成する場合はポートを指定してServerSocketをnewする。

// サーバーソケット作成
ServerSocket server = new ServerSocket(port);
System.out.println("Listening on port " + port);

 作成したサーバーソケットがAcceptするとクライアントの項で既出のSocketを返すので、以降同様に処理してやれば良い。サンプルでは受信スレッドのクラスはクライアントと共有している。

// Accept
Socket conn = server.accept();
System.out.println("Accept");
// データ受信スレッド生成
RecvData rd = new RecvData(conn.getInputStream());
rd.start();

ソケットのサンプル(サーバー、クライアント)

2007年6月14日木曜日

ocket编程中select的使用

Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、accept、recv或recvfrom这样的阻塞程序(所谓阻塞方式 block,顾名思义,就是进程或是线程执行到这些函数时必须等待某个事件的发生,如果事件没有发生,进程或线程就被阻塞,函数不能立即返回)。可是使用 Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。下面详细介绍一下!

Select的函数格式(我所说的是Unix系统下的伯克利socket编程,和windows下的有区别,一会儿说明):

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);

先说明两个结构体:

第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO (fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set *),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。一会儿举例说明。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

具体解释select的参数:

int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。

fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。

struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

返回值:

负值:select错误 正值:某些文件可读写或出错 0:等待超时,没有可读写或错误的文件

在有了select后可以写出像样的网络程序来!举个简单的例子,就是从网络上接受数据写入一个文件中。

例子:

main()

{

int sock;

FILE *fp;

struct fd_set fds;

struct timeval timeout={3,0}; //select等待3秒,3秒轮询,要非阻塞就置0

char buffer[256]={0}; //256字节的接收缓冲区

/* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开

sock=socket(...);

bind(...);

fp=fopen(...); */

while(1)

{

FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化

FD_SET(sock,&fds); //添加描述符

FD_SET(fp,&fds); //同上

maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1

switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用

{

case -1: exit(-1);break; //select错误,退出程序

case 0:break; //再次轮询

default:

if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据

{

recvfrom(sock,buffer,256,.....);//接受网络数据

if(FD_ISSET(fp,&fds)) //测试文件是否可写

fwrite(fp,buffer...);//写入文件

buffer清空;

}// end if break;

}// end switch

}//end while

}//end main

TCP的socket连接示例

Made In Zeal 转载请保留原始链接:http://www.zeali.net/entry/13
标签 ( Tags ): socket , tcp , timeout , 超时 , 异步 , ioctl , 源代码
用C实现的TCP socket连接/读/写操作。采用fcntl设置非阻塞式连接以实现connect超时处理;采用select方法来设置socket读写超时。此示例可被编译运行于Windows/unix系统。

源文件connector.c

原来的代码在windows下编译不通过,今天qzj问起才发现。因为加了异步的处理,没有对这部分代码进行兼容性处理。本着做学问一丝不苟嘀精神,重新修改了一下源代码。以下代码在VC++6和linux下编译执行通过 :)

view plainprint?

1. /*
2.
3. * on Unix:
4.
5. * cc -c connector.c
6.
7. * cc -o connector connector.o
8.
9. *
10.
11. * on Windows NT:
12.
13. * open connector.c in Visual Studio
14.
15. * press 'F7' to link -- a project to be created
16.
17. * add wsock32.lib to the link section under project setting
18.
19. * press 'F7' again
20.
21. *
22.
23. * running:
24.
25. * type 'connector' for usage
26.
27. */
28.
29.
30. #include
31.
32. #include
33.
34. #include
35.
36. #include
37.
38. #include
39.
40. #include
41.
42. #ifdef WIN32
43.
44. #include
45.
46. #else
47.
48. #include
49.
50. #include
51.
52. #include
53.
54. #include
55.
56. #include
57.
58. #include
59.
60. #include
61.
62. #endif
63.
64.
65. #ifndef INADDR_NONE
66.
67. #define INADDR_NONE 0xffffffff
68.
69. #endif
70.
71. #define MAX_STRING_LEN 1024
72.
73. #define BUFSIZE 2048
74.
75.
76. #ifndef WIN32
77.
78. #define SOCKET int
79.
80. #else
81.
82. #define errno WSAGetLastError()
83.
84. #define close(a) closesocket(a)
85.
86. #define write(a, b, c) send(a, b, c, 0)
87.
88. #define read(a, b, c) recv(a, b, c, 0)
89.
90. #endif
91.
92.
93. char buf[BUFSIZE];
94.
95.
96. static char i_host[MAX_STRING_LEN]; /* site name */
97.
98. static char i_port[MAX_STRING_LEN]; /* port number */
99.
100.
101. void err_doit(int errnoflag, const char *fmt, va_list ap);
102.
103. void err_quit(const char *fmt, ...);
104.
105. int tcp_connect(const char *host, const unsigned short port);
106.
107. void print_usage();
108.
109.
110. //xnet_select x defines
111.
112. #define READ_STATUS 0
113.
114. #define WRITE_STATUS 1
115.
116. #define EXCPT_STATUS 2
117.
118.
119. /*
120.
121. s - SOCKET
122.
123. sec - timeout seconds
124.
125. usec - timeout microseconds
126.
127. x - select status
128.
129. */
130.
131. SOCKET xnet_select(SOCKET s, int sec, int usec, short x)
132.
133. {
134.
135. int st = errno;
136.
137. struct timeval to;
138.
139. fd_set fs;
140.
141. to.tv_sec = sec;
142.
143. to.tv_usec = usec;
144.
145. FD_ZERO(&fs);
146.
147. FD_SET(s, &fs);
148.
149. switch(x){
150.
151. case READ_STATUS:
152.
153. st = select(s+1, &fs, 0, 0, &to);
154.
155. break;
156.
157. case WRITE_STATUS:
158.
159. st = select(s+1, 0, &fs, 0, &to);
160.
161. break;
162.
163. case EXCPT_STATUS:
164.
165. st = select(s+1, 0, 0, &fs, &to);
166.
167. break;
168.
169. }
170.
171. return(st);
172.
173. }
174.
175.
176. int tcp_connect(const char *host, const unsigned short port)
177.
178. {
179.
180. unsigned long non_blocking = 1;
181.
182. unsigned long blocking = 0;
183.
184. int ret = 0;
185.
186. char * transport = "tcp";
187.
188. struct hostent *phe; /* pointer to host information entry */
189.
190. struct protoent *ppe; /* pointer to protocol information entry*/
191.
192. struct sockaddr_in sin; /* an Internet endpoint address */
193.
194. SOCKET s; /* socket descriptor and socket type */
195.
196. int error;
197.
198.
199. #ifdef WIN32
200.
201. {
202.
203. WORD wVersionRequested;
204.
205. WSADATA wsaData;
206.
207. int err;
208.
209.
210.
211. wVersionRequested = MAKEWORD( 2, 0 );
212.
213.
214.
215. err = WSAStartup( wVersionRequested, &wsaData );
216.
217. if ( err != 0 ) {
218.
219. /* Tell the user that we couldn't find a usable */
220.
221. /* WinSock DLL. */
222.
223. printf("can't initialize socket library\n");
224.
225. exit(0);
226.
227. }
228.
229. }
230.
231. #endif
232.
233.
234.
235. memset(&sin, 0, sizeof(sin));
236.
237. sin.sin_family = AF_INET;
238.
239.
240.
241. if ((sin.sin_port = htons(port)) == 0)
242.
243. err_quit("invalid port \"%d\"\n", port);
244.
245.
246.
247. /* Map host name to IP address, allowing for dotted decimal */
248.
249. if ( phe = gethostbyname(host) )
250.
251. memcpy(&sin.sin_addr, phe->h_addr, phe->h_length);
252.
253. else if ( (sin.sin_addr.s_addr = inet_addr(host)) == INADDR_NONE )
254.
255. err_quit("can't get \"%s\" host entry\n", host);
256.
257.
258.
259. /* Map transport protocol name to protocol number */
260.
261. if ( (ppe = getprotobyname(transport)) == 0)
262.
263. err_quit("can't get \"%s\" protocol entry\n", transport);
264.
265.
266.
267. /* Allocate a socket */
268.
269. s = socket(PF_INET, SOCK_STREAM, ppe->p_proto);
270.
271. if (s < 0)
272.
273. err_quit("can't create socket: %s\n", strerror(errno));
274.
275.
276.
277. /* Connect the socket with timeout */
278.
279. #ifdef WIN32
280.
281. ioctlsocket(s,FIONBIO,&non_blocking);
282.
283. #else
284.
285. ioctl(s,FIONBIO,&non_blocking);
286.
287. #endif
288.
289. //fcntl(s,F_SETFL, O_NONBLOCK);
290.
291. if (connect(s, (struct sockaddr *)&sin, sizeof(sin)) == -1){
292.
293. struct timeval tv;
294.
295. fd_set writefds;
296.
297. // 设置连接超时时间
298.
299. tv.tv_sec = 10; // 秒数
300.
301. tv.tv_usec = 0; // 毫秒
302.
303. FD_ZERO(&writefds);
304.
305. FD_SET(s, &writefds);
306.
307. if(select(s+1,NULL,&writefds,NULL,&tv) != 0){
308.
309. if(FD_ISSET(s,&writefds)){
310.
311. int len=sizeof(error);
312.
313. //下面的一句一定要,主要针对防火墙
314.
315. if(getsockopt(s, SOL_SOCKET, SO_ERROR, (char *)&error, &len) < 0)
316.
317. goto error_ret;
318.
319. if(error != 0)
320.
321. goto error_ret;
322.
323. }
324.
325. else
326.
327. goto error_ret; //timeout or error happen
328.
329. }
330.
331. else goto error_ret; ;
332.
333.
334. #ifdef WIN32
335.
336. ioctlsocket(s,FIONBIO,&blocking);
337.
338. #else
339.
340. ioctl(s,FIONBIO,&blocking);
341.
342. #endif
343.
344.
345. }
346.
347. else{
348.
349. error_ret:
350.
351. close(s);
352.
353. err_quit("can't connect to %s:%d\n", host, port);
354.
355. }
356.
357. return s;
358.
359. }
360.
361.
362. void err_doit(int errnoflag, const char *fmt, va_list ap)
363.
364. {
365.
366. int errno_save;
367.
368. char buf[MAX_STRING_LEN];
369.
370.
371. errno_save = errno;
372.
373. vsprintf(buf, fmt, ap);
374.
375. if (errnoflag)
376.
377. sprintf(buf + strlen(buf), ": %s", strerror(errno_save));
378.
379. strcat(buf, "\n");
380.
381. fflush(stdout);
382.
383. fputs(buf, stderr);
384.
385. fflush(NULL);
386.
387. return;
388.
389. }
390.
391.
392. /* Print a message and terminate. */
393.
394. void err_quit(const char *fmt, ...)
395.
396. {
397.
398. va_list ap;
399.
400. va_start(ap, fmt);
401.
402. err_doit(0, fmt, ap);
403.
404. va_end(ap);
405.
406. exit(1);
407.
408. }
409.
410.
411. #ifdef WIN32
412.
413. char *optarg;
414.
415.
416. char getopt(int c, char *v[], char *opts)
417.
418. {
419.
420. static int now = 1;
421.
422. char *p;
423.
424.
425. if (now >= c) return EOF;
426.
427.
428. if (v[now][0] == '-' && (p = strchr(opts, v[now][1]))) {
429.
430. optarg = v[now+1];
431.
432. now +=2;
433.
434. return *p;
435.
436. }
437.
438.
439. return EOF;
440.
441. }
442.
443.
444. #else
445.
446. extern char *optarg;
447.
448. #endif
449.
450.
451. #define required(a) if (!a) { return -1; }
452.
453.
454. int init(int argc, char *argv[])
455.
456. {
457.
458. char c;
459.
460. //int i,optlen;
461.
462. //int slashcnt;
463.
464.
465. i_host[0] = '\0';
466.
467. i_port[0] = '\0';
468.
469.
470. while ((c = getopt(argc, argv, "h:p:?")) != EOF) {
471.
472. if (c == '?')
473.
474. return -1;
475.
476. switch (c) {
477.
478. case 'h':
479.
480. required(optarg);
481.
482. strcpy(i_host, optarg);
483.
484. break;
485.
486. case 'p':
487.
488. required(optarg);
489.
490. strcpy(i_port, optarg);
491.
492. break;
493.
494. default:
495.
496. return -1;
497.
498. }
499.
500. }
501.
502.
503. /*
504.
505. * there is no default value for hostname, port number,
506.
507. * password or uri
508.
509. */
510.
511. if (i_host[0] == '\0' || i_port[0] == '\0')
512.
513. return -1;
514.
515.
516. return 1;
517.
518. }
519.
520.
521. void print_usage()
522.
523. {
524.
525. char *usage[] =
526.
527. {
528.
529. "Usage:",
530.
531. " -h host name",
532.
533. " -p port",
534.
535. "example:",
536.
537. " -h 127.0.0.1 -p 4001",
538.
539. };
540.
541. int i;
542.
543.
544. for (i = 0; i < sizeof(usage) / sizeof(char*); i++)
545.
546. printf("%s\n", usage[i]);
547.
548.
549.
550. return;
551.
552. }
553.
554.
555. int main(int argc, char *argv[])
556.
557. {
558.
559. SOCKET fd;
560.
561. int n;
562.
563.
564. /* parse command line etc ... */
565.
566. if (init(argc, argv) < 0) {
567.
568. print_usage();
569.
570. exit(1);
571.
572. }
573.
574.
575. buf[0] = '\0';
576.
577.
578. /* pack the info into the buffer */
579.
580. strcpy(buf, "HelloWorld");
581.
582.
583. /* make connection to the server */
584.
585. fd = tcp_connect(i_host, (unsigned short)atoi(i_port));
586.
587.
588. if(xnet_select(fd, 0, 500, WRITE_STATUS)>0){
589.
590. /* send off the message */
591.
592. write(fd, buf, strlen(buf));
593.
594. }
595.
596. else{
597.
598. err_quit("Socket I/O Write Timeout %s:%s\n", i_host, i_port);
599.
600. }
601.
602.
603. if(xnet_select(fd, 3, 0, READ_STATUS)>0){
604.
605. /* display the server response */
606.
607. printf("Server response:\n");
608.
609. n = read(fd, buf, BUFSIZE);
610.
611. buf[n] = '\0';
612.
613. printf("%s\n", buf);
614.
615. }
616.
617. else{
618.
619. err_quit("Socket I/O Read Timeout %s:%s\n", i_host, i_port);
620.
621. }
622.
623. close(fd);
624.
625.
626. #ifdef WIN32
627.
628. WSACleanup();
629.
630. #endif
631.
632.
633. return 0;
634.
635. }