winsockのrecvから制御が返らない

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

winsockのrecvから制御が返らない

#1

投稿記事 by MoNoQLoREATOR » 7年前

winsockプログラミングのお話です。
初回のrecv関数呼び出しから制御が全く返ってきません。

以下は、WEBページを読み込むことを目的としたwinsockプログラムです。

コード:

#define _CRT_SECURE_NO_DEPRECATE
#define WIN32_LEAN_AND_MEAN

/*
#include "DxLib.h"
#define FW_DX_DEF_
//*/
#define FW_MAIN_
#define FW_STD_DEF_
//*
#define FW_STD_PROGRAMABLE_DEF_
#define FW_DX_PROGRAMABLE_DEF_
//*/
#include "../safe_fireworks/fireworks.h"

#include <WinSock2.h>
#pragma comment(lib, "wsock32.lib")


// this stores the got datas in "result"
// this returns true/false as Normal End or not
bool http(const std::string & URL, std::string & result)
{
	result = "";

	std::string url;
	if(URL.find("http://") == std::string::npos) url = URL;
	else url = URL.substr(7);
	printf("url:%s\n", url.c_str() );	// debug

	std::string domain = url.substr(0, url.find('/') );
	printf("domain:%s\n", domain.c_str() );	// debug

	const char lobyte = 2;
	const char hibyte = 0;




	// init winsock

	WSADATA wsd;
	bool wsdres = WSAStartup(MAKEWORD(2,0), &wsd) == 0;
    if
	(
		atexit
		(
			(void (*)(void) )(WSACleanup)	// cast of function pointer
		)
	)
	{
		WSACleanup();
        printf("failed at atexit(WSACleanup)\n");	// debug
        return false;
    }

	if(wsdres)
	{
		bool match = LOBYTE(wsd.wVersion)==lobyte && HIBYTE(wsd.wVersion)==hibyte;
		if(match==false)
		{
			printf("failed to match version\n");	// debug
			return false;
		}
	}
	else
	{
		printf("failed to init winsock\n");	// debug
		return false;
	}
	printf("succeeded to init winsock\n");	// debug





	// create socket

	SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock == INVALID_SOCKET)
	{
		printf("failed to create socket");	// debug
		return false;
	}







	// get IP address

	SOCKADDR_IN hostinfo;
	hostinfo.sin_family = AF_INET;
	hostinfo.sin_port = htons(short(80) );
	HOSTENT * host = gethostbyname(domain.c_str() );
	if(host==NULL)
	{
		printf("failed to find IP address\n");	// debug
		return false;
	}
	hostinfo.sin_addr.S_un.S_addr = *fw::pointer_cast<uint *>(host->h_addr_list[0]);






	// connect to the server

	if(connect(sock, fw::pointer_cast<sockaddr *>(&hostinfo), sizeof(hostinfo) ) == SOCKET_ERROR)
	{
		closesocket(sock);
		printf("failed to connect to socket\n");	// debug
		return false;
	}





	// send GET request to the server

	std::string request = fw::cnct()<<"GET http://"<<url<<=" HTTP/1.0\r\n\r\n";
    if(send(sock, request.c_str(), sizeof(request), 0) == SOCKET_ERROR)
	{
        printf("failed to send\n");	// debug
        printf("error:%d\n", WSAGetLastError() );	// debug
        shutdown(sock, SD_BOTH);
        closesocket(sock);
        return false;
    }






	// receive the datas

 	const uint bufsize = 1024;
	char recv_buf[bufsize];
    int nBytesRecv;
	printf("began recv\n");	// debug
    while(true)
	{
        nBytesRecv = recv(sock, recv_buf, sizeof(recv_buf), 0);
		printf("got a packet\n");	// debug
        if(nBytesRecv == SOCKET_ERROR)
		{
            printf("failed to receive\n");	// debug
            printf("error:%d\n", WSAGetLastError() );	// debug
            break;
        }
		else
		{
			if(nBytesRecv == 0) break;
        }
    }

	recv_buf[bufsize-1] = '\0';
    result = recv_buf;
    shutdown(sock,SD_BOTH);
    closesocket(sock);

	return true;
}



int main()
{
	std::string result;
	if(http("dixq.net/forum/index.php", result) ) printf("%s\n", result.c_str() );
}
※fw::pointer_castはポインタ間のキャストであることを明示するためのもので、fw::cnctは文字を連結するためのものです。

試したURLは
http://dixq.net/forum/index.php

http://www.geocities.jp/playtown1056/program/ws_6.htm
で、どちらも初回のrecv関数呼び出しから制御がいつまでたっても返ってきませんでした。
原因も解決法も全くわかりません。
どうすればよいのでしょうか?


また、recv関数の仕様について質問があります。
recv関数に指定するバッファは途中で変更するとどのような挙動になるのでしょうか?
バッファが足りなくなってきたらそれを破棄して動的に新たなバッファを確保してそれ以後そのバッファを指定するようにすれば程よいバッファサイズを実現することができそうです。
ただ、recvがどのような仕様になっているのかわからない内は手の出しようがありません。
recvが内部でどこまで書き込んだのかをポインタ値として持っているのならば指定するバッファを途中で変更しても意味がありませんし、バッファが変更された場合はエラー終了するようになっているかもしれません。

よろしくお願いいたします。

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

Re: winsockのrecvから制御が返らない

#2

投稿記事 by h2so5 » 7年前

バグで過負荷がかかったら困りますから、他人のサーバーでデバッグするのは止めたほうがいいです。
まずは自分のサーバーに対してリクエストしましょう。サーバー側のログも取れますから。

あとパケットキャプチャでログも取ってください。ネットワークプログラミングのデバッグの基本です。

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

Re: winsockのrecvから制御が返らない

#3

投稿記事 by MoNoQLoREATOR » 7年前

制御が返ってこない問題は自己解決しました。
参考にしたページが悪く、鵜呑みにしてしまっていました。
sendの第2引数で何故かsizeofが使われており、とりあえずその通りに書いておいたのですが…少し考えれば確実におかしいですよね。
今回の場合std::stringを使っているので第2引数にはrequest.length()を指定するのが正解ですね。
そうすると、無事WEBページの情報を取得することができました。
最後に編集したユーザー MoNoQLoREATOR on 2013年2月13日(水) 15:48 [ 編集 1 回目 ]

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

Re: winsockのrecvから制御が返らない

#4

投稿記事 by MoNoQLoREATOR » 7年前

>>h2so5さん
確かにそうですね。
今後はApachを入れて自分のPCサーバーでデバッグします。

パケットキャプチャというものがあったのですか。
ありがとうございます。調べてみます。

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

Re: winsockのrecvから制御が返らない

#5

投稿記事 by YuO » 7年前

そもそもsendが全てを送信するとは限らないです。なので,\r\n\r\nより短い文字列だけが送信された場合,相手側がしばらく続きを待っている可能性はあります。
sendの戻り値を確認しましょう。
ただ,GETメソッドの指定がメチャクチャです。

ついでに,WSACleanupを強引にキャストしてatexitに渡しているので未定義の振る舞いをするでしょう。
# そもそもWSAStartup失敗したらWSACleanup不要。


ネットワークプログラムは,まずはローカルサーバーを使って,ローカルでエラーがないことを確認してから外部のサーバーに繋げるのがマナーです。
このプログラムのアクセスは,相手のサーバーへの負荷を無駄に増やすだけなので,やめた方がよいでしょう (せめてHTTPのプロトコルに沿ってから繋げる)。
Apache Http ServerなりIISなりを用意するところから始めて下さい。

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

Re: winsockのrecvから制御が返らない

#6

投稿記事 by MoNoQLoREATOR » 7年前

よく考えたらsizeofも間違っていませんね。
ポインタのサイズが返されるのかと思っていましたが、確保されたバッファのサイズが返されるようです。
あの発言は斜線で消しておきます。

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

Re: winsockのrecvから制御が返らない

#7

投稿記事 by MoNoQLoREATOR » 7年前

>>YuOさん
返信ありがとうございます。
GETメソッドの指定がメチャクチャなのは、おそらく編集前の表示です。
この掲示板のオプションの「URLを自動的にパースしない」にチェックを入れて送信するとこちらの期待通りの表示になりました。
現在のGETメソッドの指定の仕方でもメチャクチャでしょうか?

WSAStartupが失敗したらWSACleanupは不要なのですか。ありがとうございます。
あと、atexitに渡したときの動作は保障されないのですか。参考サイトに書いてあったので、こんなこともできるのかと驚きましたがやはり危険ですよね。

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

Re: winsockのrecvから制御が返らない

#8

投稿記事 by YuO » 7年前

MoNoQLoREATOR さんが書きました:現在のGETメソッドの指定の仕方でもメチャクチャでしょうか?
メチャクチャではなくなりましたが,正しくないことには変わりないです。
プロキシに接続しているのであれば正しいのですが,直接接続しているのですよね。
Studying HTTPなどで,HTTPをちゃんと勉強すべきです。
MoNoQLoREATOR さんが書きました:あと、atexitに渡したときの動作は保障されないのですか。参考サイトに書いてあったので、こんなこともできるのかと驚きましたがやはり危険ですよね。
関数ポインタをキャストした物は,元の型に戻した場合のみ呼び出すことができ,動作が保証されます。
WSACleanupのプロトタイプは,

コード:

WINSOCK_API_LINKAGE
int
WSAAPI
WSACleanup(
    void
    );
で (WinSock2.h 2214 - 2219, v7.1A),WSAAPIはFAR PASCAL,つまりは__stdcallです。

コード:

int __stdcall WSACleanup (void);
それに対して,atexitは

コード:

#include <stdlib.h>
int atexit(void (*func)(void));
と定義されています (ISO/IEC 9899:1999 7.20.4.2 The atexit function)。
よって,WSACleanupをatexitのfunc引数に直接渡す場合はcoversionが入りますが,
If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.
ですから (ISO/IEC 9899:1999 6.3 Conversions - 6.3.2.3 Pointers ¶8後段),振る舞いは未定義となります。
# cdeclとstdcallという呼び出し規約の相違があるため,特にやるべきではない (引数が無いので恐らく問題ないが)。

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

Re: winsockのrecvから制御が返らない

#9

投稿記事 by MoNoQLoREATOR » 7年前

>>YuOさん

関数ポインタのキャストについての詳しい解説までして頂きありがとうございます。
HTTPについてですが、教えていただいた解説サイトは大変勉強になりました。まだ全て読んでいませんが目から鱗です。
HTTPリクエストの記述は間違っていました。
URIにはスキーマやドメイン名を含まずそれに続くURLの部分を記述するべきだったのですね。
今回の例ではこのような記述になっていれば良いということですね。

コード:

GET /forum/index.php HTTP/1.0\r\n\r\n
これからlocalhostでテストしてみます。

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

Re: winsockのrecvから制御が返らない

#10

投稿記事 by MoNoQLoREATOR » 7年前

recvの第2引数に指定するバッファを途中で変えてみましたが、これは意味が無いようです。
最初に指定したバッファに延々と書き込まれ続けました。

HTTPヘッダの表記について質問があります。
私は以下のように解釈したのですが、これで合っているのでしょうか?
・field-nameと : の間にはLWSは入らない
・field-valueの後は必ずCRLFである。つまり、field-name:field-value(SP|HT)(CRLF)は許されない

閉鎖

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