listen(int socket, int backlog); のbacklogの値と実際のconnect()可能数

工藤@NCPLと申します。
Armadilloと言うよりもソケット通信全般の話になりますが、
表題の「int listen(int socket, int backlog); のbacklogの値と実際のconnect()可能数」についてお伺いします。

Armadillo側でソケット通信サーバを動かして、外部から接続して通信を行おうと考えております。
1つのクライアントと接続中に別クライアントからconnect()が試みられた場合
 別クライアント側で connect()にエラーを返したい
と思いまして、listen()の backlogで接続可能数がコントロールできるのではと考えて
listen( socket, 1 );
とか
listen( socket, 0 );
とやってみたのですが、1つ目のクライアントと通信中に別のクライアントでconnect()を試みると成功します。
(最初のクライアントがソケット閉じると再度待ち受けに戻って次のクライアントと通信が始まります。)

そもそもこういう目的(接続数を制限する)には listen()は使えないのでしょうか?

実用的には connectできてもサーバ側は複数接続対応に作ってないため
クライアントからWriteしてレスポンス返送待てばタイムアウトでつながってないのは検出できるので
通信できない状態のままブロックしてしまうことを回避する手はあるのですが
なんとなくすっきりしないので投稿しました。

どなたかこの辺について詳しい方に、何処が間違ってるのかのご指摘をいただけると幸甚です。

製品: 
Armadillo-440

工藤様

オニコスの中山と申します。

> そもそもこういう目的(接続数を制限する)には listen()は使えないのでしょうか?

listen()のbacklog引数は、全体の接続数を制限するものではなく、
一時的な接続要求の待ち行列の大きさを指定するものです。
コードの中で、accept()を呼んでいるところがあるとおもいますが、
一度accept()が成功したら、その後は、そのコネクションが切断されるまで、
accept()を呼ばないようにするのがよいと思います。
切断されたら、再度accept()を呼んで待機します。

> 工藤@NCPLと申します。
> Armadilloと言うよりもソケット通信全般の話になりますが、
> 表題の「int listen(int socket, int backlog); のbacklogの値と実際のconnect()可能数」についてお伺いします。
>
> Armadillo側でソケット通信サーバを動かして、外部から接続して通信を行おうと考えております。
> 1つのクライアントと接続中に別クライアントからconnect()が試みられた場合
> 別クライアント側で connect()にエラーを返したい
> と思いまして、listen()の backlogで接続可能数がコントロールできるのではと考えて
> listen( socket, 1 );
> とか
> listen( socket, 0 );
> とやってみたのですが、1つ目のクライアントと通信中に別のクライアントでconnect()を試みると成功します。
> (最初のクライアントがソケット閉じると再度待ち受けに戻って次のクライアントと通信が始まります。)
>
> そもそもこういう目的(接続数を制限する)には listen()は使えないのでしょうか?
>
> 実用的には connectできてもサーバ側は複数接続対応に作ってないため
> クライアントからWriteしてレスポンス返送待てばタイムアウトでつながってないのは検出できるので
> 通信できない状態のままブロックしてしまうことを回避する手はあるのですが
> なんとなくすっきりしないので投稿しました。
>
> どなたかこの辺について詳しい方に、何処が間違ってるのかのご指摘をいただけると幸甚です。
>

中村です。

> 一度accept()が成功したら、その後は、そのコネクションが切断されるまで、
> accept()を呼ばないようにするのがよいと思います。
> 切断されたら、再度accept()を呼んで待機します。

質問者さんは、この1度目のaccept()の処理中に
別のクライアントのconnect()が成功してしまうので、
それをbacklog設定でエラーにできないか?と
言っているのでは?

--
なかむら

オニコスの中山です。

> 質問者さんは、この1度目のaccept()の処理中に
> 別のクライアントのconnect()が成功してしまうので、
> それをbacklog設定でエラーにできないか?と
> 言っているのでは?

失礼いたしました。
私のほうで大きな勘違いをしていたようです。
サーバ側でaccept()しなくても、listen()していると、
クライアント側のconnect()は成功するようですね。
accept()成功直後に、サーバ側ソケットをclose()するというのはどうでしょうか?
なかなか難しい問題だと思います。

> 中村です。
>
> > 一度accept()が成功したら、その後は、そのコネクションが切断されるまで、
> > accept()を呼ばないようにするのがよいと思います。
> > 切断されたら、再度accept()を呼んで待機します。
>
> 質問者さんは、この1度目のaccept()の処理中に
> 別のクライアントのconnect()が成功してしまうので、
> それをbacklog設定でエラーにできないか?と
> 言っているのでは?
>
> --
> なかむら
>

中村です。

> 1つのクライアントと接続中に別クライアントからconnect()が試みられた場合
> 別クライアント側で connect()にエラーを返したい
> と思いまして、listen()の backlogで接続可能数がコントロールできるのではと考えて
...
> 実用的には connectできてもサーバ側は複数接続対応に作ってないため

そういうこと、ありますよね。
私も何度か同じことを考えたことがあります。
サーバ側の都合で同時に2つを相手にできないようなときです。
で、相手を待たせたくもない、と。

backlogを私もいろいろ調べたのですけど、backlogは結局あきらめました。

古い実装の情報になりますが、「UNIXネットワークプログラミング〈Vol.1〉」
http://www.amazon.co.jp/dp/4810186121
の"4.5 listen"にbacklogの話が詳しく出ています。
いろいろなUNIX系OSのbacklogの挙動を調べていて、それよると、
実装依存なところがあるようです。
この本はかなり古いので(作者もお亡くなりになってますし...)Linuxの
バージョンはまだ2.0.xのころですが、その当時、backlog=0にすると
キューイングされるコネクションは0だったようです。
ですが、BSDとかHP-UXとかSunOSとかSoralisとかは、backlog=0でも
1つはキューイングしていたようです。
(キューイングするとしないとでは大違いなので、backlog=0は使うな!と
書いてあります。その当時の話としてですが...)
で、linuxは2.2のあたりでbacklogの実装が変わりました。
どう変わったのか知りませんが、そのときにキューイングがゼロというのが
ゼロじゃなくなったのかも。。。

これじゃぁ、ぜんぜん回答になってませんね。スミマセン。

> クライアントからWriteしてレスポンス返送待てばタイムアウトでつながってないのは検出できるので
> 通信できない状態のままブロックしてしまうことを回避する手はあるのですが
> なんとなくすっきりしないので投稿しました。

これがあまり気持ち良くない、というのもわかります。

私の逃げ方は、サーバが同時に2つを相手にすることができないときでも、
並行サーバにして接続してきた相手にはすぐにaccept()してあげて、
"BUSY"(を意味する適当な)応答を返すようにしてます。
"BUSY"を返して、すぐに接続を切ってしまいます。
こうすればクライアントは待たずに「あ、今忙しいのね」とわかります。

--
なかむら

中村です。

長々と書いていたら中山さんの投稿が先に入ってしまいましたが、

中山さんの
> accept()成功直後に、サーバ側ソケットをclose()するというのはどうでしょうか?
> なかなか難しい問題だと思います。

と、私が1つ前の最後で書いた「"BUSY"を返してすぐに切ってしまう」は
同じ考えですね。

--
なかむら

中山様
ありがとうございます。

> コードの中で、accept()を呼んでいるところがあるとおもいますが、
> 一度accept()が成功したら、その後は、そのコネクションが切断されるまで、
> accept()を呼ばないようにするのがよいと思います。
> 切断されたら、再度accept()を呼んで待機します。
のようにはしてるんですが、
クライアント側はそれでも複数のクライアントがconnect()はできてしまうので
connectはしてる(ように見える)けど、実際には動けない状況が発生してしまうので、
それを避ける手があるといいなということなのですが、どうもそいつは難しそうです。

中村様
ありがとうございます。

> 私の逃げ方は、サーバが同時に2つを相手にすることができないときでも、
> 並行サーバにして接続してきた相手にはすぐにaccept()してあげて、
> "BUSY"(を意味する適当な)応答を返すようにしてます。
> "BUSY"を返して、すぐに接続を切ってしまいます。
> こうすればクライアントは待たずに「あ、今忙しいのね」とわかります。
>

同じような手に思い当たってはいたのですが
「マルチスレッドめんどくさい」という理由で横着して他の手を捜してました(^^;
やはりこれが一番スマートに行きそうですね。

オニコスの中山です。

> > accept()成功直後に、サーバ側ソケットをclose()するというのはどうでしょうか?
> > なかなか難しい問題だと思います。
> と、私が1つ前の最後で書いた「"BUSY"を返してすぐに切ってしまう」は
> 同じ考えですね。

私の書き方が良くなかったようです。
私が意図していたのは、中村さんのものとは少し違います。
まず、サーバ側でbacklog=0でlisten()し、accept()を呼びます。
クライアントがconnect()してきて、accept()から戻ったら、
そこでlisten()しているソケットのほうをclose()するということです。

このaccept()とclose()の間で、もう一つのプロセスがconnect()してくると困りますが、
マルチスレッド化する必要はありません。

中山様
 ありがとうございます。
 これもひとつの手ですね。
 これも考えて見ます。

中村です。

> このaccept()とclose()の間で、もう一つのプロセスがconnect()してくると困りますが、

accept()とclose()の間もありますが、close()と次のlisten()の間に
connect()してくる相手に対しての問題もあります。

ポートが開いていてlistenしていないところにSYN送ると
即座にRSTが返されるのでは?

クライアントとしてはすぐにエラーが帰ってきますが、
あまりお勧めはできないと思います。

--
なかむら

> そもそもこういう目的(接続数を制限する)には listen()は使えないのでしょうか?

使えなさそうです。0を指定しても、カーネル内部で最低数のエントリーを持つ
ような実装です。

net/core/request_sock.c::reqsk_queue_alloc() で、
SYNC_RECV を保持する数を決めているようです。

int sysctl_max_syn_backlog = 256;
 
int reqsk_queue_alloc(struct request_sock_queue *queue,
                      unsigned int nr_table_entries)
{
        size_t lopt_size = sizeof(struct listen_sock);
        struct listen_sock *lopt;
 
        nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
        nr_table_entries = max_t(u32, nr_table_entries, 8);
        nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
        lopt_size += nr_table_entries * sizeof(struct request_sock *);
        :
        :

at_yashi様
 ありがとうございます。
 なんらかアプリ側の設計で手を考えることにします。