用PHP实现的IMAP或POP3服务器

7

有没有用PHP实现的POP3/IMAP服务器?

我正在使用SendGrid处理我的电子邮件服务。我打算使用文件/数据库/其他方式在自己的服务器上存储这些消息,并且现在我想为我的用户提供完整的POP3或IMAP(最好是IMAP)访问其邮箱的功能。是否有PHP实现的这样的功能?或者在Windows Azure分布式环境中运行POP3/IMAP的其他可能性(假设我将邮箱存储在共享的Blob/Table/数据库中)。


2
可能有PHP的实现,但是如果有,请不要使用它!除了shell脚本之外,我认为PHP是做这件事情最糟糕的语言。如果你想要用Web开发者理解的语言来实现这个功能,请看看Node.js。 - DaveRandom
3
POP3和IMAP并不是特别复杂的协议(取决于你想支持什么),在PHP中可以相对容易地实现,但是并发性是PHP中的一个大问题,因为它不支持线程,即使进程分叉也很...呃...好吧,让我们友善地说是具有挑战性的,在Windows上实现起来也很困难。此外,从效率的角度来看,这将是令人震惊的。您是否无法安装Node.js?在Google上搜索,我已经找到了2个使用简单易用API实现的POP3和1个IMAP服务器。 - DaveRandom
4
我无法看出“我的应用程序使用PHP”在这里特别相关 - 数据存储层(应该)已经与应用程序层分离了,你谈论的是一个守护进程/服务,它将独立于您的应用程序之外存在。 - DaveRandom
3
理论上,PHP脚本可以通过在接收客户端连接时生成自身的新副本来解决并发问题。同样的技巧也可以用于保持服务器的活性:监听套接字60秒;如果没有任何事件发生,则关闭套接字,在本地打开一个文件以生成(或切换到)一个新的服务器线程,然后退出。这种方法虽然看起来很古怪,但却很酷。 - cleong
1
完全没有必要传递任何资源。该脚本将侦听端口110。它从socket_accept() 获取一个新的套接字,停止侦听,生成自身的一个副本,并处理会话。同时,该副本开始侦听下一个传入请求的110端口。 - cleong
显示剩余3条评论
1个回答

12

为了证明在PHP中实际上可以编写POP3服务器,这里有一个例子。该服务器不进行身份验证,或者几乎不做任何其他操作。它只是一遍又一遍地发送相同的消息。但它确实能够工作。Thunderbird能够从中检索到邮件。完全没有用,但也有点酷。

我的设置是运行在Windows上的Apache 2,并且使用PHP 5.2。

<?php

// echo something so fopen() would return
header("Content-type: text/plain");
echo "OK\n";
flush();

// listen for incoming connection
$listen_socket = socket_create_listen(110, 1);
$r = $w = $e = array($listen_socket);
$n = socket_select($r, $w, $e, 120);
$client_socket = ($n == 1) ? socket_accept($listen_socket) : null;
socket_close($listen_socket);

// spawn copy of myself
$internal_url = "http://{$_SERVER['HTTP_HOST']}:{$_SERVER['SERVER_PORT']}{$_SERVER['SCRIPT_NAME']}";
$stream_context_options = array (
    'http' => array (
        'method' => 'GET',
        'timeout' => 1
    )
);
$context = stream_context_create($stream_context_options);
if($f = fopen($internal_url, "rb", 0, $context)) {
    fclose($f);
}

if(!$client_socket) {
    // timed out
    exit;
}

// start handling the session
$read_buffer = "";
$write_buffer = "+OK POP3 server ready\r\n";
$active = true;

$messages = array(
    "From: webmaster@example.com\r\nSubject: This is a test\r\n\r\nHello world!\r\n"
);


$idle_start = time();
while(true) {
    $r = $w = $e = array($client_socket);
    $n = socket_select($r, $w, $e, 60);
    if($n) {
        if($r) {
            // read from the socket
            $read_buffer .= socket_read($client_socket, 128);
            $idle_start = time();
        }
        if($w) {
            if($write_buffer) {
                // write to the socket
                $written = socket_write($client_socket, $write_buffer);
                $write_buffer = substr($write_buffer, $written);
                $idle_start = time();
            } else if($active) {
                $now = time();
                $idle_time = $now - $idle_start;
                if($idle_time > 10) {
                    // exit if nothing happened for 10 seconds
                    break;
                } else if($idle_time > 2) {
                    // start napping when the client is too slow
                    sleep(1);
                }
            } else {
                break;
            }
        }
        if($e) {
            break;
        }
        if($read_buffer) {
            if(preg_match('/(.*?)(?:\s+(.*?))?[\r\n]+/', $read_buffer, $matches)) {
                $read_buffer = substr($read_buffer, strlen($matches[0]));
                $command = $matches[1];
                $argument = $matches[2];
                switch($command) {
                    case 'USER':
                        $username = $argument;
                        $write_buffer .= "+OK $username is welcome here\r\n";
                        break;
                    case 'PASS':
                        $message_count = count($messages);
                        $write_buffer .= "+OK mailbox has $message_count message(s)\r\n";
                        break;
                    case 'QUIT': 
                        $write_buffer .= "+OK POP3 server signing off\r\n";
                        $active = false;
                        break;
                    case 'STAT':
                        $message_count = count($messages);
                        $mailbox_size = 0;
                        foreach($messages as $message) {
                            $mailbox_size += strlen($message);
                        }
                        $write_buffer .= "+OK $message_count $mailbox_size\r\n";
                        break;
                    case 'LIST':
                        $start_index = (int) $argument;
                        $message_count = count($messages) - $start_index;
                        $total_size = 0;
                        for($i = $start_index; $i < count($messages); $i++) {
                            $total_size += strlen($messages[$i]);
                        }
                        $write_buffer .= "+OK $message_count messages ($total_size octets)\r\n";
                        for($i = $start_index; $i < count($messages); $i++) {
                            $message_id = $i + 1;
                            $message_size = strlen($messages[$i]);
                            $write_buffer .= "$message_id $message_size\r\n";
                        }
                        $write_buffer .= ".\r\n";
                        break;
                    case 'RETR':
                        $message_id = (int) $argument;
                        $message = $messages[$message_id - 1];
                        $message_size = strlen($message);
                        $write_buffer .= "+OK $message_size octets\r\n";
                        $write_buffer .= "$message\r\n";
                        $write_buffer .= ".\r\n";
                        break;
                    case 'DELE':
                        $write_buffer .= "+OK\r\n";
                        break;
                    case 'NOOP':
                        $write_buffer .= "+OK\r\n";
                        break;
                    case 'LAST':
                        $message_count = count($messages) - $start_index;
                        $write_buffer .= "+OK $message_count\r\n";
                        break;
                    case 'RSET':
                        $write_buffer .= "+OK\r\n";
                        break;
                    default:
                        $write_buffer .= "-ERR Unknown command '$command'\r\n";
                }
            }
        }
    } else {
        break;
    }
}

?>

嗯,这个源代码是哪里来的?你是自己写的吗? - Pavel S.
2
如果您使用的是非Windows主机,您可以使用PCNTL来分叉一个新进程。 - Xeoncross
1
我感到非常惊叹,看着这段代码^^ 我无言以对,感到敬畏。这既令人惊叹又有些扭曲。 - Jaiden Snow
@JaidenSnow 讽刺!那样做对你没有好处 :P - mic

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