Разрыв соединения с медленным SMTP-сервером по таймауту.

~xb1x

Новичок
Здравствуйте!

Есть задача по автоматической актуализации листа почтовой рассылки (не спам разумеется), понимаю как работают smtp-серверы и что мой способ дает погрешность, но все же.

Смысл способа в том, чтобы соединиться с MX-сервером и инициировать передачу команды RCPT TO:, получить ответ и обработать его. Если ответ 250 - записываем в очищенный лист адрес, если нет - ничего не делаем, переходим к следующему адресу. Далее инициируем отправку почты по очищенному листу и автоматически проверяем NDR-письма, которые свалились с тех ящиков, которые попали в этот список. Этап с обработкой RCPT TO: связан с минимизацией количества NDR-ов.

Проблема в том, что некоторые серверы очень долго висят перед тем как ответить на RCPT TO:, мне бы хотелось не ждать ответа от них в случае, если соединение длится более n-секунд таймаута и перейти к следующему адресу. Знаю, что новый cURL умеет SMTP, но там не возможно управлять соединением, т.е. после RCPT TO: сразу посылается DATA, да и cURL новый не везде. Помогите разрешить проблему или подскажите куда копать.

PHP:
function sWrite( $socket, $data, $echo = false ){

   if( $echo ) echo $data;
   fputs( $socket, $data );
   $answer = fread( $socket, 1 );
   $remains = socket_get_status( $socket ); 
   if( $remains --> 0 ) $answer .= fread( $socket, $remains['unread_bytes'] );
 
return $answer;
}

function easychecker($email) {

$mx = dns_get_record(end(explode("@", $email)), DNS_MX);
$mx = $mx[0]['target'];

$socket = fsockopen($mx, 25, $errno, $errstr, 10);
if( !$socket) {
  echo "$errstr ($errno)\n";
} else {

  sWrite( $socket, "" );

  sWrite( $socket, "EHLO vasyapupkin\r\n" );	
  sWrite( $socket, "MAIL FROM: <[email protected]>\r\n" );

  $response = sWrite( $socket, "RCPT TO: <$email>\r\n" );

  sWrite( $socket, "QUIT\r\n" );

  fclose( $socket );

if ((substr_count($response, "250") > 0) && (substr_count($response, "OK") > 0)) { 
$result = '1'; 
} else { 
$result = '0'; 
}

}

return $result;
}
 

~xb1x

Новичок
У stream-функций (stream_socket_client();) таймаут работает корректно на любой операционной системе, а не как у fsockopen(); ?
 

Gremboloid

инженера Гр...
В дополнение Не по существу вопроса, но по задаче
Список граблей на которые наткнулся при решении аналогичной задачи:

1. MX записи
Для каждого хоста может существовать несколько MX записей,
Пробовать установить соединение лучше в порядке увеличения веса записей. Если MX записей не найдено, то надо проверять А-запись

2. Согласно http://www.ietf.org/rfc/rfc0821.txt
$response не обязательно может быть 250
Например такие:
450 почтовый ящик недоступен. (например переполнен)
451 запрос сброшен потому что в данный момент на SMTP сервер пришло слишком много запросов.
452 адрес электронной почты попал в серый список (или временная ошибка MTA)
 

~xb1x

Новичок
В дополнение Не по существу вопроса, но по задаче
Список граблей на которые наткнулся при решении аналогичной задачи
Спасибо за информацию, подскажи каким образом общался с удаленными серверами? Тоже через fsockopen или как предлагает К.О. через потоки?
 

Gremboloid

инженера Гр...
Через fsockopen.
Еще некоторых хостинках столкнулся с тем что порты закрыты.
fsockopen возвращал ошибку соединения, а из-за чего не сразу не поймешь.
 

Gremboloid

инженера Гр...
Решил в своем коде перейти с fsockopen на использование функций stream_*
И столкнулся с интересной ситуацией. Вот пример проверки существование адреса [email protected]:

PHP:
// email: [email protected]

getmxrr('gmail.com', $mxhosts, $mxweights);
array_multisort($mxweights, $mxhosts);
$mxhosts[] = 'gmail.com';
var_dump($mxhosts);
// array (size=6)
//   0 => string 'gmail-smtp-in.l.google.com' (length=26)
//   1 => string 'alt1.gmail-smtp-in.l.google.com' (length=31)
//   2 => string 'alt2.gmail-smtp-in.l.google.com' (length=31)
//   3 => string 'alt3.gmail-smtp-in.l.google.com' (length=31)
//   4 => string 'alt4.gmail-smtp-in.l.google.com' (length=31)
//   5 => string 'gmail.com' (length=9)
  
$fp = @stream_socket_client("tcp://gmail-smtp-in.l.google.com:25", $errno, $errstr, $timeout);
stream_set_blocking($fp, 1);
stream_socket_response($fp);
// Response '220 mx.google.com ESMTP n2si4689632lbk.67'

stream_socket_sendto($fp, "HELO localhost\r\n");
stream_socket_response($fp);
// Response '250 mx.google.com at your service'

stream_socket_sendto($fp, "MAIL FROM: <noreply@localhost>\r\n");
stream_socket_response($fp);
// Response '250 2.1.0 OK n2si4689632lbk.67'

stream_socket_sendto($fp, "RCPT TO: <[email protected]>\r\n");
stream_socket_response($fp);
// Response '550-5.1.1 The email account that you tried to reach does not exist. Please try'
// Response '550-5.1.1 double-checking the recipient's email address for typos or'
// Response '550-5.1.1 unnecessary spaces. Learn more at'
// Response '550 5.1.1 http://support.google.com/mail/bin/answer.py?answer=6596 n2si4689632lbk.67'

stream_socket_sendto($fp, "RSET\r\n");
stream_socket_response($fp);
// Response '250 2.1.5 Flushed n2si4689632lbk.67'

stream_socket_sendto($fp, "QUIT\r\n");
stream_socket_response($fp);
// Response '221 2.0.0 closing connection n2si4689632lbk.67'
И тут начались танцы с бубнами вокруг stream_socket_response

Пример №1 в данной случае получаю бесконечный цикл
PHP:
function stream_socket_response($fp) {
    while (!feof($fp)) {
        $reply = stream_get_line($fp, 1024, "\r\n");
    }
	return $reply;
}
Пример №2 в данной случае выполнение всего кода у меня занимает ~32 сек.
PHP:
function stream_socket_response($fp) {
    while (($response = stream_get_line($fp, 1024, "\r\n")) !== false) {
        $reply = $response;
    }
	return $reply;
}

Пример №3 был использован вариант предложенный Фанатом в Чтение из сокета без таймаута
в данной случае выполнение всего кода у меня занимает ~3-4 сек.
PHP:
function stream_socket_response($fp) {
    do {
        $reply = stream_get_line($fp, 1024, "\r\n");
    } while ($reply[3] != ' ');
	return $reply;
}
PHP Version 5.3.16
Windows 7 Business Edition Service Pack 1
 

~xb1x

Новичок
fixxxer
Опытным путем было установлено, что например stream_socket_client со своим таймаутом абсолютно никакого разрыва соединения по истечению этого самого таймаута не инициируют. Есть еще какие-нибудь мысли?
 

Gremboloid

инженера Гр...
Есть. внимательнее читать документацию. Там написано что тайм-аут у stream_socket_client применяется только при создании соединения через сокет и нигде не написано чтобы оно применялось для разрыва соединения.
 
Сверху