回答編集履歴

3 まだおかしかった

ikedas

ikedas score 2942

2016/10/22 16:10  投稿

元ねたは[こちら](http://www.yamatyuu.net/computer/program/vc2013/wsftplist/)でしょうかね。それはともかく、回答します。
---
```
   //   コントロールコネクション用のソケット作成
```最初のgethostbyname()からconnect()までは、これまではサーバに接続する際の定番だった処理ですが、今後は[getaddrinfo()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms738520.aspx)を使うようにしてもいいと思います。特に、今後IPv6対応の可能性があるのならgetaddrinfo()は必須です。Windows XP以降で対応しています。
```
   recv(s, szStrRcv, sizeof(szStrRcv)-1, 0);   //   サーバーからのメッセージを取得
```[recv()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740121.aspx)の一度の呼び出しでサーバからの応答をすべて読み込めるとは限りません。これにはふたつの理由があります。
- 応答はバッファリングされます。recv()の返値が正の値である間は、読みとられて空になったバッファに残りのデータが入ってくるかもしれないので、再びrecv()を呼び出して読み取ります。最終的にrecv()が0を返せば、送られてくるデータは本当におしまいです。(**回答後追記**) これはソケットがノンブロッキングモードのときの動作です。ブロッキングモードだとrecv()はなにかデータが来るかエラーになるまで返ってきません。デフォルトはブロッキングモードで、ノンブロッキングモードにするにはWinSockでは[ioctlsocket()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms738573.aspx)をFIONBIOを渡して呼び出します。
- [RFC 959](https://tools.ietf.org/html/rfc959)によれば、FTPサーバからの応答は複数行になりえます (4.2. FTP REPLIESを参照)。recv()が0を返しても、サーバがまだ複数行応答の途中までしか送ってきていないのであれば、さらに待って読み取らなければなりません (具体的にいうと、「数字3桁と空白で始まる行の改行まで」を読み取るまでは、応答は完了していません)。
プロトコルの実装では、相互運用性の問題が生じないよう、プロトコルの定義をしている元の文書も確認されることをお勧めします。FTPでは、上述のRFC 959ですね。
```
//   ユーザー名を送信
```[send()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149.aspx)も、一度の呼び出しでバッファの内容すべてを送れるとは限りません。send()の返値は実際に送れたバッファの先頭からのバイト数ですので、途中までなら残りを送りなおさなければなりません (ちなみに、実際に「途中までしか送れない」といったことが起きるかどうか、私は確かめたことはないです。ただ、ドキュメントにそう書いてあるので)。
```
   //BIND設定
```[bind()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550.aspx)の説明によれば、第2の引数に渡されるsockaddr構造体について「For TCP/IP, if the port is specified as zero, the service provider assigns a unique port to the application from the dynamic client port range.」とのことです。ですからsin_portを0にしてbind()したあとでgetsockname()を使えばバインドしたポート番号がわかりますね。
ご質問のコードでもそうしようとしているようです。ただ、sockaddr構造体の内容を初期化していません。そのため、bind()を呼んだときにsin_portに0が入っている保証がありません。自動変数も含め、新たに確保したメモリは必ず初期化しましょう。
```
sockmine.sin_addr.S_un.S_addr = inet_addr(::szFtpServer) ;
```これは何をしているのかちょっとわかりませんでした。lesten()、connect()が成功すれば相手先アドレスは自動的に得られるはずです。
(**nagaettyさんの指摘により追記**)
```
//   データーコネクションサーバーへ接続
```これなんですが、前のPORTコマンドの送信も含め、正しく処理できていないと思います。次のようにしないといけないです。
0. socket()で新しいソケットを作成。※コントロールコネクションのソケットとは別です。
0. sockaddr構造体を初期化。ソケットをbind()。
0. getsockname()を使って、バインドしたポート番号を取得。
0. [listen()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms739168.aspx)でソケットを接続受付可能に。
0. PORTコマンドをコントロールコネクションで送信。サーバからの応答を受信してコマンドが成功したことを確認。
0. PORTコマンドをコントロールコネクションで送信。
0. [accept()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526.aspx)で接続待ち (接続するまでブロック)。
0. コントロールコネクションでサーバからの応答を受信してコマンドが成功したことを確認。  
(追記おわり)
---
あと、全般的なこととして、Windows Socketsや標準ライブラリの関数を呼び出したときは、必ず返値をチェックし、エラーのときは対処するようにコードを書くべきだと思います。
とりあえず気づいた点は以上です。コードを修正してみてまた問題が起きるようなら、「質問2」とでも見出しをつけて追記していただければいいと思います。また、エラーメッセージについては表示されたまま (エラーコードなども含めて) を知らせてください。
2 nagaettyさんの指摘により追記

ikedas

ikedas score 2942

2016/10/22 16:07  投稿

元ねたは[こちら](http://www.yamatyuu.net/computer/program/vc2013/wsftplist/)でしょうかね。それはともかく、回答します。
---
```
   //   コントロールコネクション用のソケット作成
```最初のgethostbyname()からconnect()までは、これまではサーバに接続する際の定番だった処理ですが、今後は[getaddrinfo()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms738520.aspx)を使うようにしてもいいと思います。特に、今後IPv6対応の可能性があるのならgetaddrinfo()は必須です。Windows XP以降で対応しています。
```
   recv(s, szStrRcv, sizeof(szStrRcv)-1, 0);   //   サーバーからのメッセージを取得
```[recv()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740121.aspx)の一度の呼び出しでサーバからの応答をすべて読み込めるとは限りません。これにはふたつの理由があります。
- 応答はバッファリングされます。recv()の返値が正の値である間は、読みとられて空になったバッファに残りのデータが入ってくるかもしれないので、再びrecv()を呼び出して読み取ります。最終的にrecv()が0を返せば、送られてくるデータは本当におしまいです。(**回答後追記**) これはソケットがノンブロッキングモードのときの動作です。ブロッキングモードだとrecv()はなにかデータが来るかエラーになるまで返ってきません。デフォルトはブロッキングモードで、ノンブロッキングモードにするにはWinSockでは[ioctlsocket()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms738573.aspx)をFIONBIOを渡して呼び出します。
- [RFC 959](https://tools.ietf.org/html/rfc959)によれば、FTPサーバからの応答は複数行になりえます (4.2. FTP REPLIESを参照)。recv()が0を返しても、サーバがまだ複数行応答の途中までしか送ってきていないのであれば、さらに待って読み取らなければなりません (具体的にいうと、「数字3桁と空白で始まる行の改行まで」を読み取るまでは、応答は完了していません)。
プロトコルの実装では、相互運用性の問題が生じないよう、プロトコルの定義をしている元の文書も確認されることをお勧めします。FTPでは、上述のRFC 959ですね。
```
//   ユーザー名を送信
```[send()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149.aspx)も、一度の呼び出しでバッファの内容すべてを送れるとは限りません。send()の返値は実際に送れたバッファの先頭からのバイト数ですので、途中までなら残りを送りなおさなければなりません (ちなみに、実際に「途中までしか送れない」といったことが起きるかどうか、私は確かめたことはないです。ただ、ドキュメントにそう書いてあるので)。
```
   //BIND設定
```[bind()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550.aspx)の説明によれば、第2の引数に渡されるsockaddr構造体について「For TCP/IP, if the port is specified as zero, the service provider assigns a unique port to the application from the dynamic client port range.」とのことです。ですからsin_portを0にしてbind()したあとでgetsockname()を使えばバインドしたポート番号がわかりますね。
ご質問のコードでもそうしようとしているようです。ただ、sockaddr構造体の内容を初期化していません。そのため、bind()を呼んだときにsin_portに0が入っている保証がありません。自動変数も含め、新たに確保したメモリは必ず初期化しましょう。
```
sockmine.sin_addr.S_un.S_addr = inet_addr(::szFtpServer) ;
```これは何をしているのかちょっとわかりませんでした。lesten()、connect()が成功すれば相手先アドレスは自動的に得られるはずです。
(**nagaettyさんの指摘により追記**)  
 
```  
//   データーコネクションサーバーへ接続  
```これなんですが、前のPORTコマンドの送信も含め、正しく処理できていないと思います。次のようにしないといけないです。  
0. socket()で新しいソケットを作成。※コントロールコネクションのソケットとは別です。  
0. sockaddr構造体を初期化。ソケットをbind()。  
0. getsockname()を使って、バインドしたポート番号を取得。  
0. [listen()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms739168.aspx)でソケットを接続受付可能に。  
0. PORTコマンドをコントロールコネクションで送信。サーバからの応答を受信してコマンドが成功したことを確認。  
0. [accept()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737526.aspx)で接続待ち (接続するまでブロック)。  
 
(追記おわり)  
 
---  
あと、全般的なこととして、Windows Socketsや標準ライブラリの関数を呼び出したときは、必ず返値をチェックし、エラーのときは対処するようにコードを書くべきだと思います。
とりあえず気づいた点は以上です。コードを修正してみてまた問題が起きるようなら、「質問2」とでも見出しをつけて追記していただければいいと思います。また、エラーメッセージについては表示されたまま (エラーコードなども含めて) を知らせてください。
1 ノンブロッキングモードについて追記

ikedas

ikedas score 2942

2016/10/22 14:12  投稿

元ねたは[こちら](http://www.yamatyuu.net/computer/program/vc2013/wsftplist/)でしょうかね。それはともかく、回答します。
---
```
   //   コントロールコネクション用のソケット作成
```最初のgethostbyname()からconnect()までは、これまではサーバに接続する際の定番だった処理ですが、今後は[getaddrinfo()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms738520.aspx)を使うようにしてもいいと思います。特に、今後IPv6対応の可能性があるのならgetaddrinfo()は必須です。Windows XP以降で対応しています。
```
   recv(s, szStrRcv, sizeof(szStrRcv)-1, 0);   //   サーバーからのメッセージを取得
```[recv()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740121.aspx)の一度の呼び出しでサーバからの応答をすべて読み込めるとは限りません。これにはふたつの理由があります。
- 応答はバッファリングされます。recv()の返値が正の値である間は、読みとられて空になったバッファに残りのデータが入ってくるかもしれないので、再びrecv()を呼び出して読み取ります。最終的にrecv()が0を返せば、送られてくるデータは本当におしまいです。
- 応答はバッファリングされます。recv()の返値が正の値である間は、読みとられて空になったバッファに残りのデータが入ってくるかもしれないので、再びrecv()を呼び出して読み取ります。最終的にrecv()が0を返せば、送られてくるデータは本当におしまいです。(**回答後追記**) これはソケットがノンブロッキングモードのときの動作です。ブロッキングモードだとrecv()はなにかデータが来るかエラーになるまで返ってきません。デフォルトはブロッキングモードで、ノンブロッキングモードにするにはWinSockでは[ioctlsocket()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms738573.aspx)をFIONBIOを渡して呼び出します。
- [RFC 959](https://tools.ietf.org/html/rfc959)によれば、FTPサーバからの応答は複数行になりえます (4.2. FTP REPLIESを参照)。recv()が0を返しても、サーバがまだ複数行応答の途中までしか送ってきていないのであれば、さらに待って読み取らなければなりません (具体的にいうと、「数字3桁と空白で始まる行の改行まで」を読み取るまでは、応答は完了していません)。
プロトコルの実装では、相互運用性の問題が生じないよう、プロトコルの定義をしている元の文書も確認されることをお勧めします。FTPでは、上述のRFC 959ですね。
```
//   ユーザー名を送信
```[send()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149.aspx)も、一度の呼び出しでバッファの内容すべてを送れるとは限りません。send()の返値は実際に送れたバッファの先頭からのバイト数ですので、途中までなら残りを送りなおさなければなりません (ちなみに、実際に「途中までしか送れない」といったことが起きるかどうか、私は確かめたことはないです。ただ、ドキュメントにそう書いてあるので)。
```
   //BIND設定
```[bind()](https://msdn.microsoft.com/en-us/library/windows/desktop/ms737550.aspx)の説明によれば、第2の引数に渡されるsockaddr構造体について「For TCP/IP, if the port is specified as zero, the service provider assigns a unique port to the application from the dynamic client port range.」とのことです。ですからsin_portを0にしてbind()したあとでgetsockname()を使えばバインドしたポート番号がわかりますね。
ご質問のコードでもそうしようとしているようです。ただ、sockaddr構造体の内容を初期化していません。そのため、bind()を呼んだときにsin_portに0が入っている保証がありません。自動変数も含め、新たに確保したメモリは必ず初期化しましょう。
```
sockmine.sin_addr.S_un.S_addr = inet_addr(::szFtpServer) ;
```これは何をしているのかちょっとわかりませんでした。lesten()、connect()が成功すれば相手先アドレスは自動的に得られるはずです。
あと、全般的なこととして、Windows Socketsや標準ライブラリの関数を呼び出したときは、必ず返値をチェックし、エラーのときは対処するようにコードを書くべきだと思います。
とりあえず気づいた点は以上です。コードを修正してみてまた問題が起きるようなら、「質問2」とでも見出しをつけて追記していただければいいと思います。また、エラーメッセージについては表示されたまま (エラーコードなども含めて) を知らせてください。

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る