プロが教えるわが家の防犯対策術!

WinSockでの通信プログラムがうまくいきません。

使用言語はC++とDirectXです。

ローカルでの通信(ルータを介したパソコン同士)はうまくいくんですが、別の場所にあるPCとの通信ができません。

以下実際に使用している関数です。
関数はマルチスレッドで動かしています。


WSADATA mWsaData;
SOCKET mSockYou,mSockI;
struct sockaddr_in mAddr;
struct sockaddr_in mServer;
struct sockaddr_in mClient;

void CNetwork::Init(){
int err = WSAStartup( MAKEWORD( 2, 0 ), &mWsaData );
if( err != 0 ){
ERROR_EXIT();
return;
}

//Socket初期化
mSockI = socket( AF_INET, SOCK_STREAM, 0 );
if( mSockI == INVALID_SOCKET ){
ERROR_EXIT();
return;
}
mPort = 0;
memset( mName, 0, sizeof( mName ) );
}

//Server側
void CNetwork::Accept(){
FILE* fp;
fopen_s( &fp, "messageLog.txt", "w" );
mAddr.sin_family = AF_INET;
mAddr.sin_port = htons( mPort );
mAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if( bind( mSockI, (struct sockaddr *)&mAddr, sizeof( mAddr ) ) ) fprintf_s( fp, "bind失敗\n" );

if( listen( mSockI, 10 ) != 0 ) fprintf_s( fp, "listen失敗\n" );

int len = sizeof( mClient );
SOCKET t = accept( mSockI, (struct sockaddr*)&mClient, &len );
if( t == INVALID_SOCKET ) fprintf_s( fp, "Accept失敗\n" );
mSockYou = t;

fprintf_s( fp, "Accept終了\n" );
fclose( fp );
}

void CNetwork::Connect(){
FILE* fp;
fopen_s( &fp, "messageLog.txt", "a" );
fprintf_s( fp, "Connect開始\n" );

//ソケットの設定
mServer.sin_family = AF_INET;
mServer.sin_port = htons( mPort );
mServer.sin_addr.S_un.S_addr = inet_addr( mName );
if (mServer.sin_addr.S_un.S_addr == 0xffffffff) {
fprintf_s( fp, "hostbynameへ\n" );
struct hostent *host;
host = gethostbyname( mName );
if ( host == NULL ) {
return false;
}
mServer.sin_addr.S_un.S_addr = *(unsigned int *)host->h_addr_list[0];
}
fprintf_s( fp, "Socketの設定完了\n" );

while( true ){
fprintf_s( fp, "connect()開始\n" );
if( connect( mSockI, (struct sockaddr *)&mServer, sizeof( mServer ) ) == 0 ){
fprintf_s( fp, "Connect完了\n" );
break;
}
else{
fprintf_s( fp, "Connect失敗\n" );
Sleep(100);
}
}

fprintf_s( fp, "Connect終了\n" );
fclose(fp);
}

また、ログは下のように出ました。

//サーバ側
Accept失敗

//クライアント側
Connect開始
Socketの設定完了
connect()開始
Connect失敗

ご教授お願いします。

A 回答 (10件)

NO.9の者です。



もし複数ルーターで分断されたLAN同士の通信ということでなく、ルーターを一段介しただけのクライアント・サーバー間通信であればNATブレイクは必要ないのでプログラムのバグでしょう。
出来ればネットワーク構成と端末接続構成を知りたいですね。
    • good
    • 0

あなたのプログラムを読んではいないのですが、もし実験しているPC群が複数のルータで分けられた複数LANに散在しているのであればNATブレイクの必要アリかと・・・



P2P通信には必須の方法です。

ルーターR0以下に構成されているLAN内にあるソケット同士は普通にソケット関数をたたいてやれば通信できますが、ルーターR0とルーターR1で別々のLANが構築されていて、どのルーターもブリッジモードにはなっていないという前提(ルートのルーターにカスケード接続されたルーターとして動いている)ならば、R0以下のPCとR1以下のPCがP2P通信するにはまずNATブレイクでしょうね。NATピンホールパンチなどとも呼びますか・・
    • good
    • 0

エラーについて書き忘れです。



>これは、関数呼び出しの間に割り込みがあったとのことですが、
>そのような処理の原因として何か考えられるものはあるでしょうか?
>マルチスレッドにしたことが原因でしょうか?
>(一定時間ごとに呼び出されるため、いちいち中断している可能性もありますし)
WSAEINTRはWinsock1.1の停止型ソケットが
WSACancelBlockingCallで取り消されたときに
発生するエラーです。


この関数は、現在非推奨になっていて、
一般にWinsock2では外部公開されない関数です。

MSDNにはGUIスレッドから内部で呼び出される
可能性があると記載がありますので、
GUIを使用すると発生するという可能性があります。

対応策としては、非同期ソケットにする必要が
あるかもしれません。
    • good
    • 1

反応が遅くなりましたが。

。。
ちょこっと見てて気になったことを
書いておきます。


・socketの作成
プロトコルが0で指定されていますが、
環境によって値が変わってしまうので、
TCPならIPPROTO_TCPを指定した方がよさそう。
ちなみに手持ちの環境で、winsock1なら
0はIPPROTO_IP、IPPROTO_TCPは6に
なっています。
#VC2008環境。


・Bind対象のアドレス
一番気になったところですが、
サーバ側のソケットは意図した
アドレスにbindされていますか?
ListenSocketのアドレス情報が、
mAddr.sin_addr.S_un.S_addr = INADDR_ANY;
となっている為、意図したアドレスに
bindされていないような気がします。
#ちなみにInAddrを使用する方法は
#既に一般的ではありません。


・マルチスレッド
標準ライブラリ関数と、_beginthread
CreateThreadなどを併用すると
メモリリークが発生するという問題が
確か数年前にMSから出ていたと思います。
Windows上で標準関数とマルチスレッドを
使用する場合は_beginthreadex()
_endthreadex()を使いましょう。


・クライアントについて
クライアントで使用されているOSは何でしょうか?
WindowsVista以降の場合、IPv6のアドレスが
割り当てられている可能性もありますが、
上記のコードはIPv4環境でしか使用できません。
#IPv6とIPv4が両方割り当てられていれば大丈夫かも。
    • good
    • 0

返却されるエラーについては不明になってしまいますな…。



より低レベルな部分で確認が必要なのかも知れません。

パーソナルファイヤーウォールで拒否はしていませんよね?
# 同一ネットワークの場合は無条件で受け入れ…とかになっているのかも知れませんし。
Wiresharkなどで接続要求が来ているか確認してみるのも必要かも知れません。
    • good
    • 0

>クラス内の関数をマルチスレッドで動かすようにしています。

Connectも同様です。

CNetwork::AcceptStart()の呼び出し元でインスタンスの破棄とかしていませんか?
# デストラクタのshutdown()とか走ったから、「WSACancelBlockingCall()でキャンセルを…」というエラーが返却されている…とか考えられませんかね?

というか…スレッドにしてもインスタンスを破棄する前に起動したスレッドを停止させないとなりませんよね?
accept()が動作しないと、TerminateThread()で無理矢理殺すことになるかと思われますが…。
# _beginthread()で生成したモノをTerminateThread()で…だと、リソースリークとかしそうな気がします。

>一定時間main関数(またはメッセージ処理)を行わないとフリーズして止まってしまうので、このような形でサブスレッドで実行するようにしてます。

select()または、WSAAsyncSelect()やWSAEventSelect()を使うべき…かと。


とはいえ、ローカルネットワークでは問題ない(表面化していないだけの可能性もありますが)が、別の場所(外部ネットワーク経由?)だとダメ…というのも……。
# ルータのポート転送関係はチェック済みなんですよね?当然。

この回答への補足

>インスタンスの破棄
インスタンスの破棄は、アプリケーション自体を閉じたときにしか呼び出していないのでおそらくないとは思います・・・

>ルータのポート転送関係
ポートの開放のことでしょうか?ポートの開放でしたら、お互いに開放は行っています。

補足日時:2011/03/19 17:25
    • good
    • 0

>accept()は接続要求されるまでそこで待機されると思っていたので、connect()とのタイミングなどとの関係はあるのですか?



ブロッキングモードですので接続されるまで待つでしょうから、とりあえずは問題なさそうです。
# accept()がタイムアウト…なんてことはたぶん無いと思われますが。

サーバ側のマルチスレッド…はどのような動作になっているのでしょう?
呼び出し元のCNetwork::Accept()は接続されるまで戻ってきませんが、問題ない…んですよね?
# 戻らない…ので、そのクラスを別のスレッドから破棄することもできない…ハズです。

accept()で、エラーがWSAEINTRとなると…WSACancelBlockingCall()でキャンセルしろ…と?

この回答への補足

サーバ側の動作は以下のようにしています。
クラス内の関数をマルチスレッドで動かすようにしています。Connectも同様です。
void CNetwork::AcceptStart(){
 mThread = (HANDLE) _beginthread(
     &CNetwork::AcceptLauncher,
     0,
     this);
}
static void AcceptLauncher(void* args){
 reinterpret_cast<CNetwork*>(args)->Accept();
}


DirectXの仕様なのか、一定時間main関数(またはメッセージ処理)を行わないとフリーズして止まってしまうので、このような形でサブスレッドで実行するようにしてます。

補足日時:2011/03/16 00:38
    • good
    • 0

>accept関数の後、調べてみた結果WSAEINTRというエラーが発生していました。



listen()の直後にaccept()している様ですが…
ねそのタイミングでクライアントがconnect()していますか?
# ノンブロッキングでしょうから、タイムアウトまでaccept()で止まっているとは思われますが…。

複数接続できるようには作られていないっぽいので、listen()で10接続受け付ける様にするのは止めた方がいいかもしれません。
# まぁ、初回以外はaccept()されないのでクライアントはタイムアウトするでしょうが。
ということで、マルチスレッドにしても接続できるのは1クライアントだけです。
テストに使用した環境が接続保持状態のまま正しく開放できていなかったりしませんか?

CNetworkクラスがどうなっているのか、同じポートで複数のインスタンスを作っていないか(その場合、bind()失敗ですが)、デストラクタで正しくSocket()の開放が行われているか…等々確認されてはどうでしょうか?

参考URL:http://www.kt.rim.or.jp/~ksk/wskfaq-ja/

この回答への補足

>listen()の直後にaccept()している
accept()は接続要求されるまでそこで待機されると思っていたので、connect()とのタイミングなどとの関係はあるのですか?

>複数接続できるようには作られていない
はい、このプログラムでは1つだけ接続が確立できればいいです。その場合、listen()では1つだけ受け付けるようにすればいいのですか?

>テストに使用した環境が接続保持状態のまま正しく開放できていなかったりしませんか?
一応毎回CNetwork内のデストラクタで解放処理は行っています。
以下ソースコードです。
void CNetwork::Destroy(){
 if( mType == TCP_SERVER ){
  shutdown( mSockI, SD_BOTH );
  closesocket( mSockYou );
 }
 //WinSock解放
 WSACleanup();
 ThreadClose();
 DeleteCriticalSection( &mCSThread );
}

補足日時:2011/03/15 11:49
    • good
    • 0

通信周りの関数で失敗した直後に


WSAGetLastError関数を使用して
エラーの詳細コードを取るとセッションが
確率できない理由がわかるかも知れません。

大抵の場合、ローカルエリアでつながるけど
インターネットでつなげないのは、
ポートの解放がうまく行ってない場合が多いです。

ポートの設定はお使いのルータによって異なるので
確認してみてください。

この回答への補足

accept関数の後、調べてみた結果WSAEINTRというエラーが発生していました。
これは、関数呼び出しの間に割り込みがあったとのことですが、そのような処理の原因として
何か考えられるものはあるでしょうか?
マルチスレッドにしたことが原因でしょうか?(一定時間ごとに呼び出されるため、いちいち中断している可能性もありますし)

補足日時:2011/03/14 19:42
    • good
    • 0

ログの状況から見て、サーバー側のAccept失敗が問題だと感じます。


Acceptの失敗コードをとるのが良いのではないでしょうか?

ちなみにAcceptが失敗する理由は、
http://software.aufheben.info/kouza/senior/kouza …
を参照されると、良いかもしれません。
    • good
    • 0

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!