PHP的mail()函数在Windows上对附件支持有些问题吗?

6
今天客户提交了一个工单,报告我们的 Windows 2003 服务器之一在尝试发送附件时 PHP 的 mail() 函数超时了。
经过调查,我成功重现了他的问题。包含大小约为 30-60KB 的小附件的邮件需要 15-20 秒才能被 mail() 函数处理;大小约为 360-500KB 的大附件则需要更长时间,甚至超出了脚本执行时间的最大限制(90 秒)。
我在两台不同的 Windows 2003 服务器和一台 Windows 2008R2 服务器上都能够重现这个问题。我还尝试了三个不同版本的 PHP(5.2.14、5.2.17 和 5.3.6 - 均为 Microsoft 建议在 Windows 上运行 PHP 的非线程安全构建版本),但问题依然存在。
在所有情况下,邮件都是通过 SMTP 发送的(即未使用 sendmail 实现)。我尝试了三种不同的 SMTP 场景:
  • 直接传递到我们的 SMTP 智能主机集群(运行 exim)
  • 通过本地 IIS SMTP 服务中继到我们的智能主机
  • 通过本地 IIS SMTP 服务进行 MX 查找和直接投递
无论采用以上哪种方式,发送附件的效果仍然不佳,这意味着问题不能归咎于缓慢的中继。
然后我在我们的 CentOS 服务器上运行了相同的代码,但没有出现任何这些问题,mail() 函数几乎立即返回。但是这些服务器上的 PHP 都是配置为使用 sendmail
接着我决定深入研究 PHP 源代码以查找 mail() 函数的实现,并在 ext/standard/mail.c 中发现了以下代码:
if (!sendmail_path) {
#if (defined PHP_WIN32 || defined NETWARE)
    /* handle old style win smtp sending */
    if (TSendMail(INI_STR("SMTP"), &tsm_err, &tsm_errmsg, headers, subject, to, message, NULL, NULL, NULL TSRMLS_CC) == FAILURE) {
        if (tsm_errmsg) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", tsm_errmsg);
            efree(tsm_errmsg);
        } else {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", GetSMErrorText(tsm_err));
    }
        return 0;
    }
    return 1;
#else
    return 0;
#endif

TSendMail() 在另一个源文件 win32/sendmail.c 中实现。所有发送到 SMTP 服务器的数据似乎都是通过一个名为 Post() 的函数在 sendmail.c 中同步传递的,其代码如下:

static int Post(LPCSTR msg)
{
    int len = strlen(msg);
    int slen;
    int index = 0;

    while (len > 0) {
        if ((slen = send(sc, msg + index, len, 0)) < 1)
            return (FAILED_TO_SEND);
        len -= slen;
        index += slen;
    }
    return (SUCCESS);
}
send()函数是winsock2函数。
我在想,对于更大量的数据,缓冲区大小(根据下面的KB文章,默认值为8K)或者缺乏调整是否会产生影响。没有调用setsockopt()来指定缓冲区大小或任何其他优化调用send()的选项。
也许在Windows上,使用SMTP传递的mail()函数不适合用于发送大型电子邮件?
我很想知道是否有人已经查看过这段代码或者遇到了同样的问题。

设计问题 - 在Winsock中使用TCP发送小数据段

只是为了明确,我们已经为客户提供了备选解决方案(SwiftMailer),因此这并不是为了得到替代建议。

1
很少见到管理员或者声望很高的人提出这样的问题,肯定是NP难题...... :P - Jared Farrish
@XzKto - 我猜那也是原因,尽管我没想到通过套接字发送会那么慢。无论如何,为什么不把它作为答案,我会将你标记为已接受的答案。 - Kev
@Kev♦ - 在我回答之前,能否请您确认这是否真实?我认为可以轻松使用tcpdump或Windows的其他替代品来完成此操作-只需查看您是否缓慢发送数据包(连续发包之间存在大的延迟)或远程服务器是否慢(接收到的数据包存在大的延迟)。还有可能是Windows实现弄乱了您的TCP窗口设置,并且由于强制远程服务器在每个接收到的数据包之后发送ACK而导致延迟由高RTT(ping)的远程服务器引起。 - XzKto
@XzKto 这已经在三个不同的Windows服务器和三个不同的传输路径上尝试过了(请参见第四段)。另一方面,SwiftMail也通过TCP套接字发送,但根本没有这个问题。 - Kev
Kev♦ - 我所知道的所有邮件协议(pop3,imap等)都在TCP上运行,因此任何邮件客户端/服务器都将通过TCP套接字发送/接收。问题可能出在TCP实现上,例如如果某些东西搞乱了TCP设置并且TCP窗口大小变为1000,那么在具有RTT = 10ms的连接上发送500KB数据将是(500KB / 1KB)* 10ms = 5秒钟的纯等待。 TCP分组窗口大小可以显示任何体面的分组嗅探器(tcpdump等)。 - XzKto
显示剩余4条评论
1个回答

1

没问题,我已经推荐了 SwiftMailer,它非常出色。 - Kev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接