CSocketでPOP3受信エラー

[戻る]
Aki 2010/02/27(土) 21:25:20
VC++6.0でCSocketを使ったメール受信を行いたいのですが、
本文の受信でエラーで落ちてしまうのですが、
原因が分からず困っております。
お知恵を拝借致したくお願いいたします。

char    m_sbuf[1024];    //送信バッファ
char    m_rbuf[1024];    //受信バッファ
CString sMail;
char    comm[256];
sprintf ( comm, "%d", n);    //nはメッセージ番号
sMail = comm;
strcpy( m_sbuf, "RETR " + sMail + "\r\n");
Send( m_sbuf, strlen( m_sbuf));    //送信
if ( !Check())            //受信
{
    AfxMessageBox( "RETRエラー");
    return FALSE;
}

//送信Send関数
int CPop::Send(const void* lpBuf, int nBufLen, int nFlags) 
{
    TRACE( "> %s\n", lpBuf);    
    return CSocket::Send(lpBuf, nBufLen, nFlags);
}

//受信兼エラーチェック関数
int CPop::Check()
{
    int    len;
    len = CSocket::Receive( m_rbuf, sizeof( m_rbuf));
    m_rbuf[len] = '\0';
    TRACE( "< %s\n", m_rbuf);
    if ( strnicmp( m_rbuf, "-ERR", 4) == 0)
        return FALSE;
    return TRUE;
}

RETRコマンドの送信はできていますので
(デバッグウィンドウで確認すると+OK 1864 octetsが返ってきています)
受信が上手くいっていないようです。
しかし他のコマンド(STATやUIDL)はサーバから
結果が受信できています。
メールの本文を受信する部分でつまづいています。

ちなみに
m_rbuf[len] = '\0';
の意味も分かりません。
なんの為に必要で、何をしているのでしょうか??
何かアドバイスを頂けたらと思います。

επιστημη [E-Mail] [HomePage] 2010/02/27(土) 23:43:35
> メールの本文を受信する部分でつまづいています。

そこ詳しく。
 TRACE( "< %s\n", m_rbuf);
のとこまで到達していますか? 期待する文字列が出力されていますか?

> m_rbuf[len] = '\0';
> の意味も分かりません。

受信文字列の末尾が'\0'終端されていないのでここで追加しています。

tetrapod 2010/02/28(日) 07:38:57
っていうか自分でそう書いておいて「意味がわかりません」だって?
なんのこっちゃ。

TCP なんだから Receive 1回で送られてきたストリームを受け取れるわけが無い。

Aki 2010/02/28(日) 12:09:17
επιστημηさん、tetrapodさん、
ご意見ありがとうございます。

>そこ詳しく。
> TRACE( "< %s\n", m_rbuf);
>のとこまで到達していますか? 期待する文字列が出力されていますか?

到達しています。
しかし期待する文字列は出力されていません。
最初の一行だけ

+OK 1864 octets

は出力されているのですが、
期待する文字列は2行目以降に
メールヘッダと本文が出力されて欲しいです。

>TCP なんだから Receive 1回で送られてきたストリームを受け取れるわけが無い。

分割して送信されているのでしょうか?

瀬戸っぷ [E-Mail] 2010/02/28(日) 13:36:29
>>TCP なんだから Receive 1回で送られてきたストリームを受け取れるわけが無い。
>分割して送信されているのでしょうか?

ヌ回のrecv()で受信しきれるとは限らない。
ということです。
送信側の1回のsend()と受信側の1回のrecv()は必ずしも対応しているとは限らない。
というところに注意して受信処理を作る必要があります。
# POP3ならコマンド送信のsend()は1回で送信バッファに入るでしょうから、send()の分
割については考えなくてもよいでしょう。

昔、メールチェッカー作ったときに受信処理関係でいろいろミスしたりしましたが…。

Aki 2010/02/28(日) 20:24:34
瀬戸っぷさん、アドバイスありがとうございます。

>ヌ回のrecv()で受信しきれるとは限らない。
>ということです。

受信バッファを受け取ったデータ長づつずらして
Receiveの戻り値が0になるまで繰り返すように
したのですが、まったくなにも受け取らなくなりました。。

while文が無ければ、最初の一行だけ受け取れるのは
変わらないですが。

なにかお気付きの点等ありますでしょうか??

int    len;
int    poslen;
poslen = 0;
while(poslen < sizeof( m_rbuf))
{
    len = CSocket::Receive( m_rbuf + poslen, sizeof( m_rbuf));
    if(len < 0)            //受信エラー
    {
        AfxMessageBox( "受信失敗");         
        return FALSE;
    }
    if(len == 0) break;
    poslen += len;
}
m_rbuf[poslen] = '\0';
TRACE( "< %s\n", m_rbuf);
if ( strnicmp( m_rbuf, "-ERR", 4) == 0)
    return FALSE;
return TRUE;

瀬戸っぷ [E-Mail] 2010/02/28(日) 23:30:09
>受信バッファを受け取ったデータ長づつずらして
>Receiveの戻り値が0になるまで繰り返すように
>したのですが、まったくなにも受け取らなくなりました。。

recv()が0に…となると、Socketが切断されたとき…かと思いますが……
POP3では、サーバ側から切断されることはまず無いかと思われます。
# まぁ、そのヘンはサーバ側の実装次第ですが。
# HTTPなら、サーバから切断してくれますけどね。

ということで、RETRコマンドでの受信完了は受信したデータを見て自分で判定する必要が
あるかと。
# LISTコマンドなどで取得できる各メッセージのサイズから…とか。
# この場合コマンドのレスポンスの「+OK」などの行はサイズに含まれないので注意が必
要ですが。
\r\n.\r\nの受信で終了です。
# ちなみに、送信側のメールで.だけの行があった場合には…"..\r\n"に変換されているハズ。
# その辺りについてはRFCで確認を。(SMTPとPOPのRFCを)
# ついでに、LISTコマンドの最後も"\r\n.\r\n"です。

あと……
>len = CSocket::Receive( m_rbuf + poslen, sizeof( m_rbuf));
では、バッファオーバーランしますのでご注意を。
# m_rbufの半分まで使っていても、recv()には受信可能バッファサイズとしてm_rbufの領
域分ある。と渡しています。


本文受信が終わった後のコトになりますし、RFC違反だからかまわない…とも言えますが…
以前、受信した迷惑メールでは改行が"\r\r\n"になっていたことがあった。
というのもオフトピとして……。
# おかげで受信処理で吹っ飛んだりした…。

PATIO 2010/03/01(月) 10:15:05
送受信処理と言うのは何でもそうですけれど、
基本的にプロトコルに従って送受信しますよね。
ですから、プロトコル自体をきちんと理解する事がまず第一歩だと思います。

で、通信では相手から受信するべきデータサイズを通知してもらうか、
データの終端に置くデータを決めておいてそれでデータの区切りを感知するか
どちらかの方法を取る場合が多いと思います。
データサイズが予め分かっている場合、データサイズ分のデータが受信できるまで
データの受信を繰り返し、サイズが満たされたタイミングでひとつのデータとして
取り扱います。
終端のデータが決まっている場合はそのデータが来るまで受信を続け、
そのデータが受信できたら一つのデータの受信が完了したと判断します。

データのサイズの通知方法に関しても様々で、
データのヘッダ部にデータサイズを格納して返却するタイプや
別途やり取りしてサイズを予め取得するタイプもありますね。

この辺はプロトコル上の決まりを理解していないと処理できません。
メールの受信に関してはPOP3というプロトコルを使用しますから
プロトコルに関してきちんと調べた方が良いと思います。
プロトコルを理解しないと通信は出来ませんから。

あと、プロトコルに従って実装していてもたまにプロトコルから外れたデータが
きたり、プロトコル上にはっきり記載されていない部分で通常は来ないパターンの
データが来たりするケースもあります。この辺になると経験則で対応するしかない
ので、どこまでのデータまで対応するのかは開発者の判断になります。

一般のメールソフトにしてもこう言ったイレギュラーなデータに対する対応は
随時追加される事が多いです。たぶん歴史が長いメールソフトにはたくさんの
例外的な処理が入っていると思います。

Aki 2010/03/01(月) 15:02:44
瀬戸っぷさん、PATIOさん、
アドバイスありがとうございます。

件名:Test、本文:Testとしたサンプルメールを送信し、
手動でTelnet接続してRETRしてみたところ
最終が\r\n.\r\nでした。
例外は無しにして、まずはこのメールのみ受信できるようになればと思います。

char   m_rbuf[4096];
int    len;
int    poslen;
poslen = 0;
while(poslen < sizeof( m_rbuf))
{
    len = CSocket::Receive( m_rbuf + poslen, sizeof( m_rbuf) - poslen);
    if(len < 0)            //受信エラー
    {
        AfxMessageBox( "受信失敗");         
        return FALSE;
    }
    poslen += len;
    if(strstr(m_rbuf + poslen, "\r\n.\r\n") != NULL) break;
}
m_rbuf[poslen] = '\0';
TRACE( "< %s\n", m_rbuf);
return TRUE;

のようにしてみました。
結果は2回ほどReceiveを実行していて、
戻り値は正常に帰って来るようになりました。
1回目の戻り値が1460、2回目が1745でした。
(当たり前なのかもしれないですが毎回戻り値は異なる。)

しかし、受け取ったm_rbufの中身が正しくないのか、
 TRACE( "< %s\n", m_rbuf);    ←ここで落ちてます。

LISTの様にReceive1回で済むコマンドであれば
複数行でも正常なのですが

にーに 2010/03/01(月) 15:31:02
TRACEの文字数の制限では?
一度に512バイト以上出力しようとすると例外が発生したはずです。

TRACEで出力可能なのは半角512文字まで!
http://www.dinop.com/vc/trace_512.html

Ismaell. 2010/03/01(月) 15:34:14
TRACEはm_rbufが大きいと落ちます。

Aki 2010/03/01(月) 17:16:07
[[解決]]
にーにさん、Ismaell.さん、
アドバイスありがとうございます。

で、できました!
とりあえず受信バッファをファイルに落としたら
期待する文字列が表れました。

さすがVC++でご飯を食べている人たちは違いますね。

なんとか首の皮一枚でつながりそうです。
ありがとうございました。

古いスレッドには追加発言できません。