【Winsock】持続的接続ができない?

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

【Winsock】持続的接続ができない?

#1

投稿記事 by MoNoQLoREATOR » 11年前

Winsockプログラミングのお話です。
httpに従って、

HEADリクエストでヘッダー情報取得

そこから必要な容量を読み取り、バッファを確保

GETリクエストでデータを取得

ということをやりたいのですが、どうやら持続的接続ができていないようなのです。
GETリクエストを送信した後に受信するデータ量が0なのです。
一旦接続し直してからGETリクエストを送ると正常にデータを受信することができます。

以下ソースコードです。(関数などをできるだけ展開してみました)

main.cpp
► スポイラーを表示
includes.h
► スポイラーを表示
HTTPheader.h
► スポイラーを表示
一応ソースコードをUPしておきます。
safe_fireworksのみ
safe_fireworks + ソースコードファイル(3つ全て)
※クリックした瞬間ダウンロードされます

アバター
h2so5
副管理人
記事: 2212
登録日時: 13年前
住所: 東京
連絡を取る:

Re: 【Winsock】持続的接続ができない?

#2

投稿記事 by h2so5 » 11年前

サーバーが持続的接続を許可していない可能性はないでしょうか?

アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

Re: 【Winsock】持続的接続ができない?

#3

投稿記事 by MoNoQLoREATOR » 11年前

Headerを見ると
Connection: Keep-Alive
とあるので、サーバーは持続的接続を許可しているものと思われます。
ただ、
Keep-Alive: timeout=5, max=100
とあり、データの受信にかなり時間がかかっていることから、タイムアウトしている可能性
はあるかもしれません。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

Re: 【Winsock】持続的接続ができない?

#4

投稿記事 by MoNoQLoREATOR » 11年前

実行ファイルをUPしましたので、よろしければ動作確認をお願いできませんか?
http://www25.atpages.jp/monoqloreator/o ... roject.lzh
※例によってクリックした瞬間にダウンロードが開始されます。
  また、入力するURLはhttp://よりも下の部分のみとしてください。
  さらに、おそらくアンチウイルスソフトがシャシャリ出て来るものと思われますが、
  それについては私を信用して頂くしかございません…。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 13年前
住所: 東海地方
連絡を取る:

Re: 【Winsock】持続的接続ができない?

#5

投稿記事 by softya(ソフト屋) » 11年前

localhostでテストしてみました。
これは、合格のなのでしょうか?

Do input URL
localhost/index.html
Now Communicating...

Begin = Init Winsock
Finish = Init Winsock

Begin = Create Socket
Finish = Create Socket

Begin = Get Domain and URI
Finish = Get Domain and URI

Begin = Get IP
Finish = Get IP

Begin = Connect to Server
Finish = Connect to Server

Begin = HEAD Request
Finish = HEAD Request

Begin = Receive Header
Finish = Receive Header

Begin = Analyze Header
header:
HTTP/1.1 200 OK
Date: Mon, 29 Apr 2013 01:49:45 GMT
Server: Apache/2.2.21 (Win32) mod_ssl/2.2.21 OpenSSL/1.0.0e PHP/5.3.8 mod_perl/2
.0.4 Perl/v5.10.1
Last-Modified: Sat, 19 Mar 2011 08:49:44 GMT
ETag: "1000000019c96-ca-49ed1f9d9da00"
Accept-Ranges: bytes
Content-Length: 202
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html


Estimated Size of Header and Body:567
Finish = Analyze Header

Begin = GET Request
Finish = GET Request

Begin = Receive Header and Body
Header and Body:

Finish = Receive Header and Body
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

Re: 【Winsock】持続的接続ができない?

#6

投稿記事 by MoNoQLoREATOR » 11年前

動作確認ありがとうございます。
Header情報は取得できていますが、肝心のlocalhost/index.htmlの内容が取得できていませんので、失格となります。
Begin = Receive Header
の後の部分が表示されるまでかなり時間がかかりませんでしたか?(私の場合5秒くらいかかります)
おそらくその間にタイムリミットが来て、サーバー側が接続を切ってしまっているのではないかと私は睨んでいるのですが…。

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 13年前
住所: 東海地方
連絡を取る:

Re: 【Winsock】持続的接続ができない?

#7

投稿記事 by softya(ソフト屋) » 11年前

MoNoQLoREATOR さんが書きました:動作確認ありがとうございます。
Header情報は取得できていますが、肝心のlocalhost/index.htmlの内容が取得できていませんので、失格となります。
Begin = Receive Header
の後の部分が表示されるまでかなり時間がかかりませんでしたか?(私の場合5秒くらいかかります)
おそらくその間にタイムリミットが来て、サーバー側が接続を切ってしまっているのではないかと私は睨んでいるのですが…。
そうですね5秒ぐらいは止まりますので、タイムリミットだと思います。
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

Re: 【Winsock】持続的接続ができない?

#8

投稿記事 by MoNoQLoREATOR » 11年前

それにしても、どうしてこんなにも時間がかかるのでしょうか?
こんなものなのでしょうか?
それとも受信の仕方が間違っているのでしょうか?

アバター
softya(ソフト屋)
副管理人
記事: 11677
登録日時: 13年前
住所: 東海地方
連絡を取る:

Re: 【Winsock】持続的接続ができない?

#9

投稿記事 by softya(ソフト屋) » 11年前

Keep-Aliveはやった事がないのでよくわかりませんが、WireSharkなどでパケットキャプチャして異常なパケットは出ていないのでしょうか?
by softya(ソフト屋) 方針:私は仕組み・考え方を理解して欲しいので直接的なコードを回答することはまれですので、すぐコードがほしい方はその旨をご明記下さい。私以外の方と交代したいと思います(代わりの方がいる保証は出来かねます)。

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: 【Winsock】持続的接続ができない?

#10

投稿記事 by YuO » 11年前

コードを追ってデバッグしましたか。
124行目のrecvで0が返ることを期待しているようですが,ブロッキングソケットなので,recvは読む物がなくなれば単純にブロックします。
で,タイムアウトしてFINを送信された場合(shutdown(s, SD_SEND))には,recvは0を返すので,その先へ行くのだと思います。
# 面倒なのでブロックされたことを確認した時点で書いています。
当然,サーバー側は「もう送信しない」と表明しているので,GETを受信しても何も返し(返せ)ません。


ちなみに,VS 2010, IIS 8 on Windows 8の環境下で,以下のコードは問題なく動きます。
# ヘッダサイズが変わらないことを前提にした手抜きコードですが。

コード:

#include <WinSock2.h>
#include <Windows.h>
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <cstdlib>

#pragma comment(lib, "ws2_32.lib")

class Finally
{
private:
    std::function<void ()> func_;

    Finally (const Finally &);
    Finally& operator= (const Finally &);
public:
    Finally (const std::function<void ()> & func) : func_(func) {}
    ~Finally () { func_(); }
};

auto send_string (::SOCKET, const char *) -> bool;

auto main (void) -> int
{
    ::WSADATA wd;
    if (::WSAStartup(MAKEWORD(2,2), &wd))
    {
        std::cerr << "WSAStartup failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 1;
    }
    Finally dtorWsa_([]() { ::WSACleanup(); });

    ::SOCKET s;
    if ((s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
    {
        std::cerr << "socket failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 2;
    }
    Finally dtorS_ ([s]() { ::closesocket(s); });

    ::sockaddr_in sai = { 0 };
    sai.sin_family = AF_INET;
    sai.sin_addr.S_un.S_addr = ::inet_addr("127.0.0.1");
    sai.sin_port = ::htons(80);

    if (::connect(s, reinterpret_cast<::sockaddr *>(&sai), sizeof(sai)) == INVALID_SOCKET)
    {
        std::cerr << "connect failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 3;
    }

    if (!send_string(s, "HEAD / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"))
    {
        return 4;
    }

    int recved;
    char buffer[4096];
    std::string headReceived;
    while ((recved = ::recv(s, buffer, sizeof(buffer), 0)) != 0 && recved != SOCKET_ERROR)
    {
        std::string temp(buffer + 0, buffer + recved);
        std::cout << temp;
        headReceived += temp;
        if (headReceived.size() > 4 && headReceived.substr(headReceived.size() - 4, 4) == "\r\n\r\n")
        {
            break;
        }
    }
    if (recved == SOCKET_ERROR)
    {
        std::cerr << "recv(1) failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 5;
    }

    auto pos = headReceived.find("\r\nContent-Length: ");
    if (pos == std::string::npos)
    {
        std::cerr << "Not Found Content-Length" << std::endl;
        return 6;
    }
    auto contentLength = std::strtol(headReceived.c_str() + pos + std::strlen("\r\nContent-Length: "), nullptr, 10);
    auto resultLength = headReceived.size() + contentLength;

    if (!send_string(s, "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"))
    {
        return 7;
    }

    int readSize = 0;
    while ((recved = ::recv(s, buffer, sizeof(buffer), 0)) != 0 && recved != SOCKET_ERROR)
    {
        std::string temp(buffer + 0, buffer + recved);
        std::cout << temp;
        resultLength -= recved;
        if (resultLength <= 0)
        {
            break;
        }
    }
    if (recved == SOCKET_ERROR)
    {
        std::cerr << "recv(2) failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 8;
    }

    if (::shutdown(s, SD_SEND) == SOCKET_ERROR)
    {
        std::cerr << "shutdown with SD_SEND failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 9;
    }

    while ((recved = ::recv(s, buffer, sizeof(buffer), 0)) != 0 && recved != SOCKET_ERROR)
    {
        std::string temp(buffer + 0, buffer + recved);
        std::cout << temp;
    }
    if (recved == SOCKET_ERROR)
    {
        std::cerr << "recv(3) failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 10;
    }

    if (::shutdown(s, SD_BOTH) == SOCKET_ERROR)
    {
        std::cerr << "shutdown with SD_BOTH failed (" << ::WSAGetLastError() << ")" << std::endl;
        return 11;
    }

    return 0;
}

auto send_string (::SOCKET s, const char * str) -> bool
{
    auto length = std::strlen(str);

    while (length > 0)
    {
        auto ret = ::send(s, str, length, 0);
        if (ret == SOCKET_ERROR)
        {
            std::cerr << "send failed (" << ::WSAGetLastError() << ")" << std::endl;
            return false;
        }
        str += ret;
        length -= ret;
    }
    return true;
}
オフトピック
とりあえず,必要なファイルを残し,不要なファイルを削除して.zipにしてください。
.suoあるのに.slnがなかったり,VS 2010でプロジェクト作ってソースを追加したら,関係のない箇所でエラーになる等,コードを実行するまでに面倒なことが多すぎます。
エラー関連でいうなら,ライブラリとして作るなら,A/Wをきっちり指定すべきです。
さらに,W系を標準とすべきです (特にファイル名)。
非表示エリア
この非表示エリアを表示するには、登録し、ログインする必要があります。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

Re: 【Winsock】持続的接続ができない?

#11

投稿記事 by MoNoQLoREATOR » 11年前

なるほど理解しました。
1回分のデータを全て受信し終えるとrecvは0を返すのだと思い込んでいたのがいけなかったのですね。(持続的接続でなければ期待通りだったのですが…)
持続的接続については解決しました。
ありがとうございました。
以下解決コードです。(main.h以外は省略。又、デバッグのための出力は残ったままです)
► スポイラーを表示

問題はA/Wについてです。(以下オフトピック)
オフトピック
>>エラー関連でいうなら,ライブラリとして作るなら,A/Wをきっちり指定すべきです。
とのことですが、これは例えばMessageBoxならMessageBoxAやMessageBoxWを使えということでしょうか。
それとも #define や #pragma を用いて指定する方法があるからそれを使えということでしょうか。
また、W系を標準とすべき理由は何でしょう?

YuO
記事: 947
登録日時: 13年前
住所: 東京都世田谷区

Re: 【Winsock】持続的接続ができない?

#12

投稿記事 by YuO » 11年前

MoNoQLoREATOR さんが書きました:>>エラー関連でいうなら,ライブラリとして作るなら,A/Wをきっちり指定すべきです。
とのことですが、これは例えばMessageBoxならMessageBoxAやMessageBoxWを使えということでしょうか。
そうです。
  • MessageBoxを使うならば,TCHAR系で統一する
  • char系を使いたいならば,MessageBoxAを使う
  • wchar_t系を使いたいならば,MessageBoxWを使う
ライブラリのインターフェースとしては,
  • インラインで展開される場合を除いて,インターフェースにTCHAR系をそのまま残してはならない
    →UNICODEの有無によって,関数のシグネチャが変わってしまうから
  • インライン展開されない場合は,char系のみ,wchar_t系のみ,または両方のどれかのパターンから選択する
    →MessageBoxは両方を用意しているパターン。
という作りにします。
MoNoQLoREATOR さんが書きました:また、W系を標準とすべき理由は何でしょう?
A系を使っているプログラムから,W系のAPIを使った場合にはすべての文字を使うことができます。
しかし,W系を使っているプログラムから,A系のAPIを使った場合には一部の文字しか使えなくなります。
このため,W系を標準とする方が,制限がすくないのです。
特にファイル名と書いたのは,A系のAPIで使える文字の制限によって,ファイルやディレクトリにアクセスできなくなるという,致命的な問題があります。
# ユーザー名に使われていたら(本来の)AppDataにアクセスできないということになります。

アバター
MoNoQLoREATOR
記事: 284
登録日時: 13年前
住所: 東京

Re: 【Winsock】持続的接続ができない?

#13

投稿記事 by MoNoQLoREATOR » 11年前

丁寧なご説明をありがとうございました。
よくわかりました。
地道に書き換えてゆきます。

閉鎖

“C言語何でも質問掲示板” へ戻る