質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.51%
Socket.IO

Socket.IOはNode.js上で動くライブラリであり、すべてのブラウザとモバイルデバイスでリアルタイムのアプリを作動させる事を目的としています。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

Q&A

解決済

3回答

5641閲覧

WinsockでFTPのプロトコルだけでFTPクライアント処理の実現

kanbye

総合スコア17

Socket.IO

Socket.IOはNode.js上で動くライブラリであり、すべてのブラウザとモバイルデバイスでリアルタイムのアプリを作動させる事を目的としています。

Win32 API

Win32 APIはMicrosoft Windowsの32bitプロセッサのOSで動作するAPIです。

0グッド

1クリップ

投稿2016/10/18 23:47

編集2016/10/19 12:51

###前提・実現したいこと
FTPの処理手順を知るために、Winsockを利用してFTPを通してファイルをやり取りするソフトを作成しています。最終的にはWinsockを利用しないため(別のAPIを利用するため)、Winsockにある直接ファイルをやり取りするFTPGetFile/FTPPutFilといった関数は利用することはできません。send/recvを利用してデータを宗純したいと思います。
ネットからいろいろサンプルを使って作成していますが、PORTコマンドを実行した後、再度コネクト処理を実現するところで必ず失敗します。
手順が不足または間違っていると思いますが、よろしくご教授ください。

###発生している問題・エラーメッセージ

「対象のコンピューターによって拒否されたため、接続できませんでした。」
となり、2度目のCONNECT処理にて接続エラーになります。
なお、手動では問題なく接続できています。
手動での確認方法はコマンドプロンプトから"ftp -d"とftpのプロトコル表示をしながら確認しています。
###該当のソースコード
開発言語はVS2008上のC言語で行っています。
本ソースはPassiv接続をActive接続に書き直してテストしています。

void _tmain(void){ char szStr[256]; // 送信バッファ char szStrRcv[1024]; // 受信バッファ _tsetlocale(LC_ALL, _TEXT("")); // // FTPサーバーの定義 strcpy_s(::szFtpServer, sizeof(::szFtpServer), SERVER_NAME); strcpy_s(::szUserName, sizeof(::szUserName), USER_NAME); strcpy_s(::szPass, sizeof(::szPass), PASSWORD); WSADATA wsaData; LPHOSTENT lpHost; SOCKET s; SOCKADDR_IN sockadd; // Winsockの初期化 if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { WSA_ERROR_MSG(_TEXT("関数WSAStartup:error")); return; } lpHost = gethostbyname(::szFtpServer); if (lpHost == NULL) { WSA_ERROR_MSG(_TEXT("関数gethostbyname:error")); return; } // コントロールコネクション用のソケット作成 s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { WSA_ERROR_MSG(_TEXT("関数socket:error")); WSACleanup(); return; } sockadd.sin_family = AF_INET; sockadd.sin_port = htons(PORT); // ホストバイトオーダーをネットワークバイトオーダーに変換 sockadd.sin_addr.S_un.S_addr = inet_addr(::szFtpServer); // サーバーのIPアドレス取得 if (connect(s, (PSOCKADDR)&sockadd, sizeof(sockadd))) { // コントロールコネクションサーバーへ接続 WSA_ERROR_MSG(_TEXT("関数connect:error")); closesocket(s); WSACleanup(); return; } memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv)-1, 0); // サーバーからのメッセージを取得 printf(szStrRcv) ; if (strncmp(szStrRcv, "220", 3) != 0){ WSA_ERROR_MSG(_TEXT("error")); closesocket(s); WSACleanup(); return; } // ユーザー名を送信 sprintf_s(szStr, sizeof(szStr), "USER %s\r\n", ::szUserName); printf(szStr) ; send(s, szStr, (int)strlen(szStr), 0); memset(szStrRcv, 0, sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv)-1, 0); printf(szStrRcv) ; if (strncmp(szStrRcv, "331", 3) != 0){ WSA_ERROR_MSG(_TEXT("error")); closesocket(s); WSACleanup(); return; } // パスワードを送信 sprintf_s(szStr, sizeof(szStr), "PASS %s\r\n", ::szPass); printf(szStr) ; send(s, szStr, (int)strlen(szStr), 0); memset(szStrRcv, 0, sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv)-1, 0); printf(szStrRcv) ; if (strncmp(szStrRcv, "230", 3) != 0){ WSA_ERROR_MSG(_TEXT("error")); closesocket(s); WSACleanup(); return; } // サーバーのカレントディレクトリを指定 sprintf_s(szStr, sizeof(szStr), "CWD ./\r\n"); printf(szStr) ; send(s, szStr, (int)strlen(szStr), 0); memset(szStrRcv, 0, sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv)-1, 0); printf(szStrRcv) ; //BIND設定 SOCKADDR_IN sockmine; sockmine.sin_family = AF_INET; int len = sizeof(SOCKADDR_IN); //sockmine.sin_addr.s_addr = INADDR_ANY; //sockmine.sin_port = 0; if (getsockname(s, (struct sockaddr *)&sockmine, &len) < 0) { WSA_ERROR_MSG(_TEXT("getsockname failed.\n")); closesocket(s); WSACleanup(); return; } unsigned long low, hi; hi = (ntohs(sockmine.sin_port) >> 8) & 0xff; low = ntohs(sockmine.sin_port) & 0xff; sprintf_s(szStr, "PORT %d,%d,%d,%d,%d,%d\n", sockmine.sin_addr.S_un.S_un_b.s_b1, sockmine.sin_addr.S_un.S_un_b.s_b2, sockmine.sin_addr.S_un.S_un_b.s_b3, sockmine.sin_addr.S_un.S_un_b.s_b4, hi,low); send(s, szStr, (int)strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStr)); recv(s, szStrRcv, sizeof(szStrRcv) - 1, 0); printf(szStrRcv) ; sockmine.sin_addr.S_un.S_addr = inet_addr(::szFtpServer) ; SOCKET s2 = FtpDataConnect(sockmine.sin_addr, hi * 256 + low); // LISTコマンドを送信 sprintf_s(szStr, sizeof(szStr), "LIST\r\n"); printf(szStr) ; send(s, szStr, (int)strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStr)); recv(s, szStrRcv, sizeof(szStrRcv)-1, 0); printf(szStrRcv) ; // データーコネクションサーバーからLISTコマンドの結果を受け取る FtpList(s2); closesocket(s2); closesocket(s); WSACleanup(); return; } // データーコネクションサーバーへ接続 SOCKET FtpDataConnect(in_addr ipa, unsigned int port){ SOCKET s; s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { WSA_ERROR_MSG(_TEXT("関数socket:error")); return 0; } struct sockaddr_in sockadd = { AF_INET, htons(port) }; sockadd.sin_addr = ipa; if (connect(s, (PSOCKADDR)&sockadd, sizeof(sockadd)) == -1) { WSA_ERROR_MSG(_TEXT("関数connect:error")); closesocket(s); return 0; } return s; } // LISTコマンドの結果をファイルftplist.txtに保存 void FtpList(SOCKET s){ char buf[16]; FILE* fp; if (_tfopen_s(&fp, _TEXT("D:\\ftplist.txt"), _TEXT("w") )){ _tprintf(TEXT("ファイルが開けません\n")); return; } int len; do{ len = recv(s, buf, sizeof(buf), 0); fwrite(buf, sizeof(char), len, fp); } while (len == sizeof(buf)); fclose(fp); } // Winsockのエラーコードに対応するメッセージを標準出力に表示する void WSA_erro_msg(void){ __WSA_ERROR_CODE__ = WSAGetLastError(); LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, __WSA_ERROR_CODE__, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 既定の言語 (TCHAR*)&lpMsgBuf, 0, NULL ); _tprintf(_TEXT("%s\n"), lpMsgBuf); // バッファを解放する。 LocalFree(lpMsgBuf); }

###試したこと
ポート番号が手動でコマンド打ちした場合(コマンドラインから"ftp -d"で動作)と、ポート番号があっていないのが原因かと思い、ポート番号を固定してみましたが、変化ありませんでした。

###補足情報(言語/FW/ツール等のバージョンなど)
VS2013/2015上でもコンパイル動作はしていますので、VSのバージョンの問題はありません。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2016/10/19 03:05

▼PORTコマンドの内容をバッファに入れた後にsend()やrecv()を実行していないように見えますが、いいのでしょうか。▼「2度目の接続エラー」とはどういった状態を指すのでしょうか、また手作業ではどのようにして確認したのでしょうか。▼それと、コードの前後をバッククォート (`) 3つの行で囲むときれいに表示されます。現状はインデントが消えてしまって見づらいです。
kanbye

2016/10/19 03:42

訂正しました。PORTコマンドなどはSendCommand関数で送信しています。受領も同じくその中です。よろしくお願いします。
ikedas

2016/10/19 07:18

「PORTコマンドなどはSendCommand関数で送信しています」とのことですが、ご質問中の「該当のソースコード」に示されたコードには、そのようなものは現れないようです。実際に動作を試されたソースコードを提示しておられますか。
kanbye

2016/10/19 07:57

失礼しました。見ているめいいソースを間違えて説明しておりました。送信はsend関数で行っています。アップしたソースは全ソースです。リンクが通りませんか?
ikedas

2016/10/19 08:01

全ソースだとすると、「sprintf_s(szStr, "PORT %d,%d,%d,%d,%d,%d\n", ……」としてPORTコマンドの内容をバッファに入れた後にsend()やrecv()を実行して*いない*ように見えますが、いいのでしょうか。
kanbye

2016/10/19 08:13

失礼しました。該当の分を追加しました。ただ、エラー内容が変わりまして、タイムアウトでやはり2回目の接続がうまくいきません。
ikedas

2016/10/19 08:27

エラーはFtpDataConnect()の中で起きているのでしょうから、この関数を呼び出す直前で、引数に正しいものが渡っているか確認してください。ちなみに、すぐ上の「csin_port」という変数や「low = 79;」という文も謎です。
kanbye

2016/10/19 12:57

設定どおりには入っております。にもかかわらず、接続できません。現在は、対処のサーバーより拒否されましたとなります。コードの問題以前に必要なプロトコルが正しいのかどうかが不明です。
ikedas

2016/10/19 13:06

いやいや(^^;)、まずは正しい書き方のコードにしないと、コードが正しい実装になってるのかも確かめられないでしょう。まあそれはともかく、ぱっと見おかしいところはなくなったようなんで、これからコードをちゃんと読ませてもらいますね。FTPのアクティブモードの実装とはなかなか意欲的なテーマですね。
kanbye

2016/10/19 13:34

要は安定的にFTPクライアントしてファイルを送受信できればいいのです。アクティブモードを選択したのはこれが標準と思っているためです。
guest

回答3

0

元ねたはこちらでしょうかね。それはともかく、回答します。


// コントロールコネクション用のソケット作成

最初のgethostbyname()からconnect()までは、これまではサーバに接続する際の定番だった処理ですが、今後はgetaddrinfo()を使うようにしてもいいと思います。特に、今後IPv6対応の可能性があるのならgetaddrinfo()は必須です。Windows XP以降で対応しています。

投稿2016/10/22 04:47

編集2016/10/22 07:10
ikedas

総合スコア4198

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

自己解決

いろいろその後、調べました結果、Active接続の時点で勘違いしておりました。
Passive接続が一般であり、実際その方法で接続はうまくいきました。

サンプルソースに対して、PORTコマンドを実行後、PASVコマンドを実行することで、その後、思い通りの処理ができました。

sockmine.sin_addr.S_un.S_addr = inet_addr(::szFtpServer) ;

これはPORTコマンドに実行で、利用するポート番号を取得しますが、この後、データコネクトを行う場合、サーバーアドレスが必要となるため、サーバーアドレスに戻す必要があるため、設定しています。

投稿2016/10/23 08:36

kanbye

総合スコア17

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

※削除しました。

データ通信経路の接続要求を行う前に(PORT要求送信前に)受信用の処理を起動させて、受信待ちにしておく必要があるのでないかと思います。

理由は:
サーバ側の処理が速い場合は、サーバから接続要求が送信された時点で、受信PORTが開いていない
可能性があります。

投稿2016/10/22 05:49

編集2016/10/22 06:41
nagaetty

総合スコア1106

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2016/10/22 06:45 編集

※前のコメントは削除しました。 本当だ! bind()したあとで二度目のconnect()をする前にlisten()していませんね。後で回答に追記します。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.51%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問