C言語関係掲示板

過去ログ

No686 SMTPサーバへ添付ファイルを送るには

[戻る] [ホームページ]
No.7960

SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/03 03:15:10)


現在Win2k VC++5.0にてメールの送受信機能を付けたプログラムを組んでいます。
それで、添付ファイルを送信しようとしているのですが、うまく行きません。

base64でエンコードされたファイルのデータが下記のような形でバッファに納められています。

"CAAAAAkAAAAKAAAACAAEQAAABIAAAATAAAAFAAAABUA\r\n
AAD+////FwAAAHQAAAB4AAAAfAAAA/v///0kAAAiAAAA\r\n
IwAAACQAAAAlAAAD+////LAAAAC0AAAAuAAAALwAADAA\r\n
AAAxAAAAMgAAADMAAAAAAlAAAD+////LAAA0AAAA0AAA\r\n
          ・
          ・
         (中略)
          ・
          ・
///LAAA0AAAA0AAA\r\n"

このバッファは便宜上短くしてありますが、実際には改行コードまでを1行とし、
1行あたり、74文字あります。
また、1行あたりの最大の文字数も74文字以下であることが保証されています。

これを以下のように、ループで回しながらSMTPサーバへ送信しています。

    char *bufpos;
    char *buf64;    //エンコードされたファイルデータのバッファへのポインタ
    
    bufpos = strtok(buf64, "\r\n");
    do{
        char buf[100];
        sprintf(buf, "%s\r\n", bufpos);
        send(hsocket, buf, strlen(buf), 0);
    }while((bufpos = strtok(NULL, "\r\n")));


そうしたところ、小さなファイルの場合問題無いのですが、数十kbのファイルになると、
ブルーバックが出て、OSを巻き込んで落ちます。

また、色々調べてみると、send()関数はメッセージをすぐには送信せず、
送信バッファへ蓄えるだけの物だということが分かりました。
この時、送信バッファがいっぱいの状態で、これ以上蓄えることが出来ないとなると、この関数は
失敗するということ、そして、ソケットには同期・非同期というのがあるということも分かりました。

もし、ソケットが非同期で、送信バッファがいっぱいの状態でも十分な空きが出来るまで待たずに
すぐに制御を返し、その状態でさらにsend()関数で、送信バッファへ蓄えようとしているのなら、
もしかしたら、こういうことにもなるのかもしれないとも思います。

しかし、同期を取るというのが今ひとつ分かりません。
色々なサイトを見ると、非同期は”あえて”するものであり、通常は同期的である。
というような事が書かれています。

私はサーバへ接続する際、下記のようにしています。
  ※コードが助長的になりすぎるため、エラーチェックは省略しています。

    WSADATA wsadata;        //WinSock情報の構造体
    LPHOSTENT hostent;        //ホストサーバ情報の構造体
    SOCKET hsocket;            //WinSockソケット
    SOCKADDR_IN sockaddr;    //IPアドレスとポート番号

    WSAStartup(MAKEWORD(1, 1), &wsadata)
    hostent = gethostbyname(サーバ名);
    hsocket = socket(PF_INET, SOCK_STREAM, 0);

    //サーバへ接続する。
    sockaddr.sin_family = AF_INET;
    sockaddr.sin_port = htons((short)port);
    sockaddr.sin_addr = *((LPIN_ADDR)*hostent->h_addr_list);
    connect(hsocket, (PSOCKADDR)&sockaddr, sizeof(sockaddr)));

    memset(rcv, '\0', RESPONSEBUFSIZE);
    recv(hsocket, rcv, RESPONSEBUFSIZE, 0);


私の知識では、上記のコードで同期的なソケットを取得出来るものだと思っています。

OSを巻き込んで落ちるというのは極めて致命的であり、添付ファイルの送信機能というのも、
絶対に外せない機能なので、是が非でも回避したいところではありますが、まったく分かりません。

色々と調べて見てはいるのですが、原因が分かりません。
長くなりましたが、どなたか分かる方いらっしゃいましたら、よろしくお願いします。

No.7963

Re:SMTPサーバへ添付ファイルを送るには
投稿者---shelly(2003/07/03 05:06:50)


WinSocはよく知りませんが・・・。
    char *bufpos;
    char *buf64;    //エンコードされたファイルデータのバッファへのポインタ
    
    bufpos = strtok(buf64, "\r\n");
    do{
        char buf[100];
        sprintf(buf, "%s\r\n", bufpos);
        send(hsocket, buf, strlen(buf), 0);
    }while((bufpos = strtok(NULL, "\r\n")));


ぱっと見では、最初のstrtokがNULL返したりしなければ落ちなそうですけど。。
buf64を作る時のメモリ確保が怪しんじゃないでしょうか。。
よかったらそちらのソースもみせていただけませんか?


>もし、ソケットが非同期で、送信バッファがいっぱいの状態でも十分な空きが出来るまで待たずに
>すぐに制御を返し、その状態でさらにsend()関数で、送信バッファへ蓄えようとしているのなら、
>もしかしたら、こういうことにもなるのかもしれないとも思います。

これは大丈夫だと思いますよ。
ソケットが送信準備が出来ていなければ、send関数から制御が戻って来ないです。

>しかし、同期を取るというのが今ひとつ分かりません。
>色々なサイトを見ると、非同期は”あえて”するものであり、通常は同期的である。
僕自身あえて非同期にしてるプログラムがあるんですが、
sendでブロックしたまま制御が戻ってこなくなることがあったので、
実験的にそうしています。
通常はselect()や、WSAAsyncSelect()を使って、書き込みが可能であることを
確認してからsendします。




No.7964

Re:SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/03 05:55:05)


早速のレスありがとうございます。

>ぱっと見では、最初のstrtokがNULL返したりしなければ落ちなそうですけど。。
>buf64を作る時のメモリ確保が怪しんじゃないでしょうか。。
>よかったらそちらのソースもみせていただけませんか?
>
>
base64のメモリは以下のようにして確保しています。

    base = (filesize*8) / 6;
    bufsize += ((bufsize/72) * 2) + 5;
    base64 = malloc(bufsize);


ファイルサイズは添付ファイルのサイズです。
それを、8ビット分掛けて、base64では6ビット毎に切られますから、6で割っています。
2行目で、72で割ってるのは72文字毎に改行コードを入れるため必要な、
改行コードの個数を出すためで、また、改行コードは2バイト必要とするため*2と、
しております。
そして+5と言うのは、なんとなく心配でして、余裕を持って余分に確保しています。

>>もし、ソケットが非同期で、送信バッファがいっぱいの状態でも十分な空きが出来るまで待たずに
>>すぐに制御を返し、その状態でさらにsend()関数で、送信バッファへ蓄えようとしているのなら、
>>もしかしたら、こういうことにもなるのかもしれないとも思います。
>
>これは大丈夫だと思いますよ。
>ソケットが送信準備が出来ていなければ、send関数から制御が戻って来ないです。
>
これは、非同期の場合もsend()関数は制御を返さないのでしょうか?

>>しかし、同期を取るというのが今ひとつ分かりません。
>>色々なサイトを見ると、非同期は”あえて”するものであり、通常は同期的である。
>僕自身あえて非同期にしてるプログラムがあるんですが、
>sendでブロックしたまま制御が戻ってこなくなることがあったので、
>実験的にそうしています。
>通常はselect()や、WSAAsyncSelect()を使って、書き込みが可能であることを
>確認してからsendします。
>
>

なるほど、やはり書き込みが可能であるかどうかは事前に知っておいてほうが
無難ですね。

バッファ回りが怪しいというご指摘でしたが、先ほどbase64のエンコード用のバッファを元のファイルサイズの2倍の領域を確保しましたが、やはり同じ現象が起きました。

また、このバッファは納められているデータの末尾から領域の末尾まで
0クリアされている事も確認しています。

もし、バッファ回りが問題では無いとするなら、他にはどんな原因が考えられるでしょうか。

度々申し訳ありませんがよろしくお願いします。

No.7965

Re:SMTPサーバへ添付ファイルを送るには
投稿者---shelly(2003/07/03 06:45:19)


    base = (filesize*8) / 6;
    bufsize += ((bufsize/72) * 2) + 5;
    base64 = malloc(bufsize);

ん?
2行目の(bufsize/72)のbufsizeには何が入ってますか?
実際のfilesizeに対して、何バイト確保しなければならないか、
そして現状のプログラムでは何バイト確保しているか確かめてみては
どうでしょうか。
多分ココが問題とみた。


>これは、非同期の場合もsend()関数は制御を返さないのでしょうか?

非同期の場合は-1を返してきます。

>なるほど、やはり書き込みが可能であるかどうかは事前に知っておいてほうが
>無難ですね。

そうですね。sendの戻り値も確認すべきです。なかなか失敗しないので
ついつい無視してしまうものですが。

>バッファ回りが怪しいというご指摘でしたが、先ほどbase64のエンコード用のバッファを元のファイルサイズの2倍の領域を確保しましたが、やはり同じ現象が起きました。
>
>また、このバッファは納められているデータの末尾から領域の末尾まで
>0クリアされている事も確認しています。
>
>もし、バッファ回りが問題では無いとするなら、他にはどんな原因が考えられるでしょうか。

ところで、実際どのステップでプログラムが異常終了してるかは
確認されましたか?
sendでおちてるのではないのでは・・・。


No.7975

Re:SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/03 13:31:50)


>
    base = (filesize*8) / 6;
    bufsize += ((bufsize/72) * 2) + 5;
    base64 = malloc(bufsize);

>ん?
>2行目の(bufsize/72)のbufsizeには何が入ってますか?
>実際のfilesizeに対して、何バイト確保しなければならないか、
>そして現状のプログラムでは何バイト確保しているか確かめてみては
>どうでしょうか。
>多分ココが問題とみた。
>
>
すみません、上記のコードは下記のように訂正します(汗)

    bufsize= (filesize*8) / 6;
    bufsize += ((bufsize/72) * 2) + 5;
    base64 = malloc(bufsize);


>ところで、実際どのステップでプログラムが異常終了してるかは
>確認されましたか?
>sendでおちてるのではないのでは・・・。

はい。実際にsend()関数で落ちている。 ということは確認していません。
しかし、send()関数を呼ぶdo-while文の中で落ちているということは確認しています。

と言うのも、send()関数のところにブレークポイントを置き、ループが回るたびに
処理を止めて、サーバへ送るバッファの中身を1回1回見ながら回したのですが、
問題のバグは再現しませんでした。

しかし、do-whileループに入る直前と直後にブレークポイントを置いた場合、
ループの直前までは何も問題無く行くのですが、ループ後のブレークポイントで
止まる前に落ちているようです。

また、send()関数の部分をコメントアウトし、変わりにfprintf()関数を使い、
バッファの中身をファイルへダンプしたのですが、これも問題なく動きました。
ファイルの中身も見た目、問題ないようです。

以上のことから、やはりsend()回りが怪しいと思うのですが、やっぱり原因不明です。


No.7976

Re:SMTPサーバへ添付ファイルを送るには
投稿者---shelly(2003/07/03 14:31:09)


>すみません、上記のコードは下記のように訂正します(汗)
    bufsize= (filesize*8) / 6;
    bufsize += ((bufsize/72) * 2) + 5;
    base64 = malloc(bufsize);


今見えている中では他に問題点が思いつかないせいもありますが、
どうしてもここにこだわってしまいます。

例えば以下のようなテキスト55バイトのファイルがあったとします。
「abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123」
これをBase64でエンコードして、72文字で改行すると
「YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAx\r\nMg==\r\n」
になります。
これに終端文字'\0'を加えると、81バイト必要になるんですが、
上記プログラムでfilesizeが55の場合、bufsizeは80になります。

こういった異常終了が起きるときはどこかでメモリアクセス違反を
起こしていることが多いように思います。
RFC2822のメッセージを作成するときにはメモリを動的に取る処理が
多くなると思いますので、ツール等を用いてチェックしたほうが
よいと思います。


No.7983

Re:SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/03 21:27:50)


>>すみません、上記のコードは下記のように訂正します(汗)
    bufsize= (filesize*8) / 6;
    bufsize += ((bufsize/72) * 2) + 5;
    base64 = malloc(bufsize);

>
>今見えている中では他に問題点が思いつかないせいもありますが、
>どうしてもここにこだわってしまいます。
>
しかし、base64でエンコードした場合、バッファのサイズは元のサイズの
1.333倍程度になるはずです。
そこで、2倍のバッファを確保しても再現したということは、ただのバッファ回りの
問題である。 とは納得がいきません。
また、私の拙い経験則に基づけばmallocなどで確保した領域の範囲を超えてメモリを
書き換えた場合、その領域をfree()するときに、エラーを出して落ちる。 という事が分かってます。
今回は、エンコードしたバッファをファイルへダンプしたときにfree()しても、
このバグは再現しなかった事から、やはりバッファ回りが問題であるという可能性は
薄いのでわと考えます。

>例えば以下のようなテキスト55バイトのファイルがあったとします。
>「abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123」
>これをBase64でエンコードして、72文字で改行すると
>「YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAx\r\nMg==\r\n」
>になります。
>これに終端文字'\0'を加えると、81バイト必要になるんですが、
>上記プログラムでfilesizeが55の場合、bufsizeは80になります。

あ……すみません。 確かに80になります……(汗)

>こういった異常終了が起きるときはどこかでメモリアクセス違反を
>起こしていることが多いように思います。
>RFC2822のメッセージを作成するときにはメモリを動的に取る処理が
>多くなると思いますので、ツール等を用いてチェックしたほうが
>よいと思います。

私自身からスレッドを立てておいて言うのもなんですが、情報が少ないような気がします。
そこで、今現在実際に使用しているコードを出来る限り形を残しながら、コンソールベースへ
書き直しましたものをアップします。

私の環境では以下のリンクのプログラムで問題のバグが再現します。
ご多忙中のこととは思いますが、目を通されたならご指摘いただけると幸いです。

http://www.geocities.co.jp/SiliconValley-PaloAlto/2848/main.html HTMLバージョン

http://www.geocities.co.jp/SiliconValley-PaloAlto/2848/main.txt テキストバージョン

No.7988

Re:SMTPサーバへ添付ファイルを送るには
投稿者---shelly(2003/07/03 23:27:39)


>そこで、2倍のバッファを確保しても再現したということは、ただのバッファ
>回りの問題である。 とは納得がいきません。

そうでした。それを忘れていました。すいません。

>また、私の拙い経験則に基づけばmallocなどで確保した領域の範囲を超えてメモリを
>書き換えた場合、その領域をfree()するときに、エラーを出して落ちる。 という事が分かってます。

メモリアクセス違反がある場合は、何でもアリだと思っていた方がいいと思いますよ。
範囲を超えて書き換えたデータがポインタ変数の値だったりしたら、
全く関係ないところで落ちたりもします。


>今回は、エンコードしたバッファをファイルへダンプしたときにfree()しても、
>このバグは再現しなかった事から、やはりバッファ回りが問題であるという可能性は
>薄いのでわと考えます。

これもなんとも言えません。
ビルドし直せばメモリ上の配置も変わるため、挙動が変わることもあります。
昔、コメント行を1行入れると落ちなくなるプログラムを見たことがあります。

>私の環境では以下のリンクのプログラムで問題のバグが再現します。
>ご多忙中のこととは思いますが、目を通されたならご指摘いただけると幸いです。

試してみました。が、再現しない・・・!
環境は
Win2000+VC6.0
Win2000+gcc version 3.2.2 (mingw special 20030208-1)
です。

添付ファイルも15KB、372KB、10Mの3パターンやりましたが、、
うまいこと送信されました。


気になる、というより教えていただきたいんですが、
WSAStartup(MAKEWORD(1, 1), &wsadata)
MAKEWORDの引数ってバージョン番号ですよね?
1.1って古くないですか??

No.7990

Re:SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/04 01:34:15)


>範囲を超えて書き換えたデータがポインタ変数の値だったりしたら、
>全く関係ないところで落ちたりもします。
>

確かに。しかし今回書き換えているのはあくまでもポインタ変数(char*)の
指し示す先の領域のデータな訳ですから、ダングリングポインタを作ってしまう
ということは考慮に入れていません。

>>今回は、エンコードしたバッファをファイルへダンプしたときにfree()しても、
>>このバグは再現しなかった事から、やはりバッファ回りが問題であるという可能性は
>>薄いのでわと考えます。
>
>これもなんとも言えません。
>ビルドし直せばメモリ上の配置も変わるため、挙動が変わることもあります。
>昔、コメント行を1行入れると落ちなくなるプログラムを見たことがあります。
>

それは、コメントの有無でバイナリが変わる。 と解釈してもよろしいですか?
私は認識では、コメントは翻訳上無視されるものであり、論理ソースには含まれない。
と、今の今まで思っておりました。

>>私の環境では以下のリンクのプログラムで問題のバグが再現します。
>>ご多忙中のこととは思いますが、目を通されたならご指摘いただけると幸いです。
>
>試してみました。が、再現しない・・・!
>環境は
>Win2000+VC6.0
>Win2000+gcc version 3.2.2 (mingw special 20030208-1)
>です。
>
>添付ファイルも15KB、372KB、10Mの3パターンやりましたが、、
>うまいこと送信されました。
>

再現されない…… そうですか……・・・私の環境ではほぼ例外なく再現されるのに……
やはり、処理系が古いからなのでしょうか。 謎は深まるばかりです。

>気になる、というより教えていただきたいんですが、
>WSAStartup(MAKEWORD(1, 1), &wsadata)
>MAKEWORDの引数ってバージョン番号ですよね?

はい。確かにMAKEWORDマクロの引数は第一引数にメジャーバージョン。第二引数に
マイナーバージョンを指定する。 とあります。

>1.1って古くないですか??

古いのですか? 確かに言われてみればVC++5.0にVer2のヘッダファイルが
含まれてるというところをみると、Ver2というのは何年も前からあることに
なりますよね……

ただ、私が見た資料ではVer2にはVer1.1を含んでいて互換性があり、
Ver2はサポートされていないこともあるから、1.1を使う方が無難である。
というようなことが書いてあったと記憶しているものですから。

また、試しにとMAKEWORDマクロの引数を(2,0)としてみましたが結果は同じでした。

もしかしたら、コンパイラオプションの設定が間違っているとか、ソースコードとは
全く関係のない部分でミスをしているかもしれませんので、もう少し調べてみますが、
それでも改善しないようならば、新しい処理系を入手するなど環境を替えようと思います。

長々とお付き合いありがとうございました。

No.8029

Re:SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/05 05:45:41)


>試してみました。が、再現しない・・・!
>環境は
>Win2000+VC6.0
>Win2000+gcc version 3.2.2 (mingw special 20030208-1)
>です。
>
>添付ファイルも15KB、372KB、10Mの3パターンやりましたが、、
>うまいこと送信されました。

終わってしまったスレッドとは思いますが、失礼いたします。

先ほど gcc version 3.2.3 (mingw 20030504-1)を導入し、
コンパイル&実行してみましたが、例のバグが再現してしまいます。

また、私の環境で再現したバイナリを、知人に頼み込み知人の環境で
走らせてもらいましたところ、再現はしなかったそうです。

私はOSやその他のシステム回りに問題があるのではと思い、Win2kへ
service pack 4 を入れましたが、結果は同じでした。
どうしても、私の環境では落ちずにはいられないようです。

shellyさんはSocketを使ったプログラムに詳しいものとお見受けします。
今回の問題と似たような現象をどこかで聞いたことはありませんでしょうか。
また、この手の問題に強いサイトなどご存じではありませんでしょうか。

ご多忙中の中お手数とは思いますが、もしご存じであるならばお聞かせいただくと
うれしいです。



No.8056

Re:SMTPサーバへ添付ファイルを送るには
投稿者---shelly(2003/07/07 08:13:59)


あまり有要な情報がなくて申し訳ないですが・・・

>shellyさんはSocketを使ったプログラムに詳しいものとお見受けします。
>今回の問題と似たような現象をどこかで聞いたことはありませんでしょうか。
>また、この手の問題に強いサイトなどご存じではありませんでしょうか。

詳しい、といえるレベルではありません。経験がある、という程度です。
しかもLinux/UNIXでの開発だったため、Windowsは未経験といえます。
その時のプログラムは一応Windowsでも動きますが。。。

その時に参考にした頁は、
Programming UNIX Sockets in C - Frequently Asked Questionsですが、タイトルの通り、UNIXソケットについてのFAQです。

ところでsendやrecvの戻り値を拾ってみたりはしました?
もしかしたら何か訴えてるかも知れません。


No.8066

Re:SMTPサーバへ添付ファイルを送るには
投稿者---はろはろ(2003/07/07 23:07:28)


>あまり有要な情報がなくて申し訳ないですが・・・

いえ、こうしてレスをいただけるだけでもうれしいです^^;

>その時に参考にした頁は、
>Programming UNIX Sockets in C - Frequently Asked Questionsですが、タイトルの通り、UNIXソケットについてのFAQです。

わざわざありがとうございました。 早速拝見させていただきます。

>ところでsendやrecvの戻り値を拾ってみたりはしました?
>もしかしたら何か訴えてるかも知れません。

    if(send(hsocket, buf, strlen(buf), 0) == SOCKET_ERROR){
        return WSAGetLastError();
    }
    if(recv(hsocket, rcv, RESPONSEBUFSIZE, 0) == SOCKET_ERROR){
        return WSAGetLastError();
    }


のように、少し強引ですが、send()・recv()がエラーを返した時点で即呼び出し元へ
帰るようにしてみましたが、その前に落ちているようです。

Windowsを再インストールしてもみましたが、どうもうまくいきません。
私のところでコンパイルし、バグが再現したバイナリを知人の環境で走らせたところ、
うまく動作したところから、私の環境固有の問題だと思ったのですが・・・


以前shellyさんがNo.7963でselect()やWSAAsyncSelect()を使って・・・と、
でおっしゃっていました。
私自身、よくよく考えてみるとプログレスバーなどを付けようと思っているので、
ここはやはり、非同期で処理しようかと思っています。

ここまでお付き合いいただいて本当に身勝手だとは思いますが、処理を非同期へと
切り替えるにあたり、また新しい疑問が浮上すると思います。
そのときはまたよろしくお願いします。



No.7966

Re:SMTPサーバへ添付ファイルを送るには
投稿者---nop(2003/07/03 09:55:08)


>base = (filesize*8) / 6;
>bufsize += ((bufsize/72) * 2) + 5;
>base64 = malloc(bufsize);

2 行目に使われている「bufsize」は、
このコードを通る前にはどのような値が入っているのでしょうか?
また、1 行目の「base」はどこで使用されているのでしょうか?