2008年5月22日
C#のTcpListenerでハマるorz
まぁ、わたしの書いたプログラムじゃないのだが…と前もって言い訳けしておく
Windows CEマシンからActiveSyncで接続されたWindows XP/Vistaマシンに対して、TCPで通信するプログラムを本格的に書く前に簡単なプログラムで動作確認を実施しているわけなんですが…このサーバ側のプログラムをXPで動かしている時には問題なく接続できていたのですが、Vistaに移行して確認したら全く動かない

要するに図の上の構成では動くけど、下の構成では同じプログラムなのに動かない状態。
なんで
Windows Mobile デバイスセンター(VistaはActiveSyncじゃなくコレ)の問題、セキュリティ強化などなど…思考しながら設定を弄っては同じところをグルグル回り、ネタも尽きてきた。
わからない時には、Telnetでつないで見るってのは王道なのでtelnet locahost 9000とかしてみたり、telnet 192.168.1.3 9000(192.168.1.3はXPマシンの正式なIPアドレス)とかしてみる。ありゃ、XPのマシンでもlocalhostならつながるけど…192.168.1.3とした場合はつながらない…なんじゃこりゃ
こうなるとサーバのプログラムが断然怪しくなってくるので…コードを見てみる。すると以下のようなコードが…。
Int32 port = 9000; IPAddress ipAddress = Dns.GetHostEntry("localhost").AddressList[0]; TcpListener listener = new TcpListener(ipAddress, port);
どうも、このコードはMSDNとかのサンプルに書かれている以下のコードに基づくモノっぽい。
IPAddress ipAddress = Dns.Resolve("localhost").AddressList[0]; try { TcpListener tcpListener = new TcpListener(ipAddress, 13); } catch ( Exception e){ Console.WriteLine( e.ToString()); }
Dns.Resolveは古いメソッドなので、それをGetHostEntryに変えたと…で、TcpListenerクラスのコンストラクタの説明には以下のように書かれてる。
TcpListener コンストラクタ (IPAddress, Int32)指定したローカル IP アドレスとポート番号で受信接続の試行を待機する、TcpListener クラスの新しいインスタンスを初期化します。
この、ローカル IPをどうやらlocalhostと言うか自身のIPアドレスと勘違いしているようです
実際、この勘違いコードが検索しても結構引っかかるのが曲者ですね
つまり、TcpListenerのコンストラクタ引数IPAddressは、待ち受けるローカルIPアドレスになるので例のようなコードを書くと…localhostからの接続しか受け付けなくなるわけですね
う~ん、わかりにくいですね。
で、通常のサーバのように待ち受けるにはどうするか…以下のようにするんですね。
Int32 port = 9000; TcpListener listener = new TcpListener(IPAddress.Any, port);
簡単ですね~ようするにIPAddress.Anyを指定すれば何の問題もないわけです
ちなみに以下のようなコンストラクタもあるんですが…古い形式だと警告されるので、使わない方がよいですね。
TcpListener listener = new TcpListener(port);
でも、これだけでは一番最初の問題の答えになってませんね~このコードでもXPでは動いていたわけですから。Windows CEが接続にくると、127.0.0.1つまりlocalhostから接続されたように見えるようになっているので問題なく動作していたわけです。
なら、Vistaでも同じじゃないの
はい。確かに127.0.0.1から接続されたように見えるのは同じなのですが…以下のコードが返してくるものが異なるのが問題です
IPAddress ipAddress = Dns.GetHostEntry("localhost").AddressList[0];
どこまで、VistaがIPv6に対応しているかは良く知らないのですが…上記でAddressListの0番目をVista上で取得するとIPv6対応のアドレスを返してくるようです。なので、127.0.0.1からの接続は受け付けないというオチのようです。
と言うことで、原因はわかったのですが…Vistaの挙動はイマイチ納得できないなぁ
あと、ActiveSync/DeviceCenterで使われてるアドレス192.168.55.100が固定なのかを知りたくて、MSKKのサポート(ちゃんとしたMSDNのサポートへ)に電話したんですけど…ActiveSync/DeviceCenterなどのダウンロードソフトはサポートの範囲外とかで結局なんの回答も得られませんでしたとさ
非常にお役所的な対応ありがとうございました~
追記 2008-05-27 10:55:23
どうも、gethostbyname(“ppp_peer”)で母艦のIPアドレス(192.168.55.100)が取得できるみたいですね
TrackBack URL :
Comments(8)

貴兄の書き方ではTcpListener(Address.Any, port)のパラメーターはコネクトしてくるクライアントのIPAddress、portですが、これは間違いでは?
GetHostEntry()で取得したAddressListには複数のIPAddressがふくまれている場合があるが、その内のどれが実際に使用すべきIPAddressか判断しにくい場合がある。
TcpListener listener = new TcpListener(IPAddress.Any, port);の様に、IPAddress.Anyを指定すれば、AddressList内のどのIPAddressにコネクトしてきても受け付けれくれる。
以上が正解ではないのですか?
はじめまして,yuyamaさん。
確かにコンストラクタ部分でないクラスのサンプルは以下のようになってますね。
http://msdn.microsoft.com/ja-jp/library/system.net.sockets.tcplistener(VS.80).aspx
TcpListener server=null;
try
{
// Set the TcpListener on port 13000.
Int32 port = 13000;
IPAddress localAddr = IPAddress.Parse(“127.0.0.1″);
// TcpListener server = new TcpListener(port);
server = new TcpListener(localAddr, port);
// Start listening for client requests.
server.Start();
(以下略)
う~ん,とにかくこれを少し試してから再度回答させてください。
う~ん,やはり上記のMSDNサンプルでも13000ポートに接続できるのは自分(localhost:127.0.0.1)からだけですね。VSがあればコンソールアプリケーションとして,簡単に実行できるので試していただければと思います。
このサンプルが,他のクライアントからの接続を受け入れない理由を解説願えないでしょうか?
さらに,このサンプルではAddressListを使用していないので「複数のIPAddressがふくまれている場合があるが、その内のどれが実際に使用すべきIPAddressか判断しにくい場合がある。」というのは成り立たないと思いますがいかがでしょうか?
ListenIPの指定なんだから、Localhostで問題ないと思うんだが・・・
netstatでどこのIP AddressでListenしてるか確認してみてください・・・
通りすがりさん,コメントありがとうございます。
localhostで問題ない気が確かにするわけですが…少なくともウチの環境では上記の通りの現象が発生すると書いているだけです。
何か根本的な勘違いをわたしがしている可能性もあるので,ぜひ自分で確認いただき。説明いただけるとうれしいです。
netstatでみただけでは,ちゃんとListenしてるようにみえるんですけどね。
1年前の記事ですが通りすがったので他のコメントを補足してみます。
TcpListener(ipaddr, port)のipaddr部分はMSDNの記述のとおり、自ホストのIPアドレスでOKのはずです。
自ホストに複数のNIC-A/B/Cを挿していてAのネットワークにだけ耳を澄ます場合はNIC-AのIPアドレスを指定し、「A/B/Cのどこからでも来いや」という場合はIPAddress.Anyを指定することになるかと。
「じゃあ、localhostの指定はいつ使うのよ」というと、単一ホスト内にサーバプロセスとクライアントプロセスがいて、それらがホスト内で内緒話をする場合に使います。MSDNもそれを意図しているのではないかと。
自分の場合は適当に作ったサーバアプリに管理コンソールをくっつける場合などに使っています。基本的には同じマシンからでないと管理操作ができないけれど、実装を変えずにipaddrの設定だけ変えればリモートでも管理できるようになるとか。
補足の補足です。
MSDNがlocalhostでサンプルを書いているのは、セキュリティ的な配慮があるのかもしれません。
サンプルというのは説明しようとしている箇所以外はできるだけ簡素に書くものですが、それによってセキュリティ的に脆弱なコードをみんながお勉強がてらに量産し、いろんなところにポートが開きまくったホストが転がる事態が発生する気もします。
ソケット通信のサンプルは実際にネットワークを通さなくてもプロセス間通信で示せるため、最も限定されたスコープであるlocalhostを使っているのかもしれません。
t_yamoさん,こんばんわ。
親切な解説ありがとうございます。
MSDNの例がセキュリティを配慮してるってのは非常によくわかりますし,MSDNのサンプルを責めているわけではないんです(明記してありますし)。
そのMSDNのコードが,引用されてあたかもどこからでも来い的なサーバ解説用のコードとして説明されているページが結構あるんです(今もあるかは知りませんが…)。
あくまで個人的な見解ですが,まずIPAddress.Anyを説明してから制限をかける方を説明してくれたらわかりやすいのにと思っている次第です。