У меня сегодня был зарегистрирован тикет, в котором клиент сообщил, что при попытке отправить вложение функция PHP mail()
остановила его на одном из наших серверов Windows 2003 Server.
После расследования я смогвоспроизвести его проблему.Сообщения, содержащие небольшие вложения размером около 30–60 КБ, занимали 15–20 секунд, чтобы обрабатываться функцией mail()
.Большие вложения размером около 360-500 КБ занимали больше времени, чем максимально допустимое время выполнения сценария (90 секунд).
Мне удалось воспроизвести проблему на двух разных серверах Windows 2003 и сервере Windows 2008R2.Я также попробовал три разные версии PHP (5.2.14, 5.2.17 и 5.3.6 - все 32-битные и все не поточнобезопасные сборки в соответствии с рекомендациями Microsoft по запуску PHP в Windows).
Во всех случаяхпочта отправлялась через SMTP (т.е. не использовала реализацию sendmail).Я пробовал три различных сценария SMTP:
- Доставка напрямую в наш SMTP-кластер smarthost (работает exim)
- Доставка через локальную службу IIS SMTP, которая пересылается на наши smarthosts
- Доставка через локальную службу 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
.
Мне интересно, влияет ли размер буфера (8К по умолчанию в соответствии со статьей КБ ниже) или отсутствие настройки влияет на большие объемы данных.Нет вызовов на setsockopt()
для указания размера буфера или любых других опций для оптимизации вызовов на send()
.
Возможно, функция mail()
в Windows, использующая доставку SMTP, не предназначена для отправки больших писем?
Мне было бы интересно узнать, смотрел ли кто-нибудь еще этот код илииспытал то же самое.
Проблемы проектирования - Отправка небольших сегментов данных по TCP с помощью Winsock
Просто чтобы прояснить, мы ужеу вас есть альтернативное решение для клиента (SwiftMailer), поэтому речь не идет о получении рекомендаций по альтернативам.