TCP プログラミングあれこれ

ICFPT Design Competition の委員をやっているので、ホストになるコードをごりごり書いているわけですが、GTK とか Qt とかでユーザインタフェイス作るのだるいなー、と思って HTML+JavaScript でやることにしまいた。最近は nmny 作ったりしてけっこう JavaScript 書いてた (きれいなコードじゃないと怒られそうですが) ので、まあいけるかなー、と。
ブラウザで表示させる部分から AJAX でデータを取りにいけばいいわけですが、そうすると何が問題かというと、HTTP のところを自分で書かないといけないところです。今回の場合、会場でプロジェクタに接続するマシン一台だけで見られればいいので、複数のブラウザから同時にセッションを張られることは考えていません。
なんとかわりとちゃんと動くようになったのですが、引っかかったポイントを記録しておきます。なにしろ、MPI とかじゃなくて、直接 TCP で通信するプログラムをまともに書くのは、自分が学生だったときの学生実験以来なので…

Address already in use

デバッグ中はプログラムを走らせて止めて、修正してはすぐ起動するわけですが、すぐ同じポートを使うプログラムを走らせると Address already in use になって bind() できないことがあります。
これは、ソケットが close されてもしばらく再利用できないため (再利用できるまでの時間はTCP/IP stackの実装依存っぽい) で、setsockopt() を使って解決します。FreeBSD/MacOS X では SO_REUSEPORT も指定しますが、Linux では SO_REUSEADDR だけで動くみたい、というか、SO_REUSEPORT がありませんでした。
[code:cpp]
  setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
#if defined (__FreeBSD__)  || (__APPLE__)
  setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
#endif
[/code]

ブラウザへデータを送信するタイミング

いまのところ複数のクライアントから接続されることはないので、何回目のアクセスではこれを返す、というのが静的に決まっています。そこで、accept() で接続が成立したらすぐにブラウザにデータを送信すればいいや、と思ったのですが、ブラウザによってはブラウザがリクエストを送信するまえにデータが届くとエラーになるようです。
ちゃんと GET がくるまで待ちましょう。あたりまえか…

Socket を close() する前に

BSD 系のシステムでは問題が起きなかったのですが、Linux ではデータがブラウザ側に届く前に、サーバ側から RST が送信されて接続が切れてしまう問題がおきました (これは最初全然わからなくて、wireshark で通信の様子をのぞいてみてはじめて気づきました)。RST が突然送られるので、ブラウザにどこまでデータが送れるかは、毎回違う感じです。
最初は socket を fdopen() して fgets() や fprintf() でアクセスするのが悪いのかな、と思って、最後に fflush() してみたりしたのですが、状況は改善しません。それで、まじめに send() と recv() を使うようにもしてみましたが、やはりうまく動作しません。
で、いろいろ調べた結果、受信バッファにデータが残った状態で socket を close() すると RST が送信されるということがわかりました。つまり、GET がきただけで返事を送っていたのですが、最後の空行まで受信してから返事をすれば OK ということです。
受信バッファが空でないと close() のときに RST が送信されるという挙動、もしかしたら Linux だけなのかもしれませんが、気をつけないといけませんね。

コメントを残す