【问题标题】:Is it possible to re-use deliberately left open sockets?是否可以重复使用故意留空的套接字?
【发布时间】:2012-06-21 21:05:09
【问题描述】:

我正在使用PHP sockets extension(基本上是与socket(2) 相关的linux 系统调用的包装器),并且希望在为后续请求提供服务时重新使用我打开的套接字。性能是一个关键因素。

我打开的套接字都指向同一个 IP,这使得无法使用像 pfsockopen() 这样的其他功能(因为它每次都重用同一个套接字),我一次需要几个。

问题

如果我让我打开的套接字故意打开服务一个请求(我不调用socket_close()socket_shutdown())并将具有完全相同参数的套接字连接到为下一个请求提供服务的相同IP; linux会重用之前打开的socket/文件描述符吗?

我最终要做的是避免每次请求都进行 TCP 握手。

其他信息

  • 我使用 apache worker MPM - 这意味着不同的请求可以是,但不一定由不同的进程提供服务。为简单起见,我们假设所有请求都来自同一个进程。

  • 我可以在 PHP 中获取打开和连接的套接字的文件描述符 ID。我可以打开并读写/dev/fd/{$id},但没有任何目的-它没有与远程服务器通信(也许这是一种天真的方法)。如果有人知道如何完成这项工作,我也会认为这也是一个可以接受的答案。

【问题讨论】:

  • 只是我对它的低级系统方面了解的很少,我相信 Linux 系统每次都会尝试使用不同的文件描述符,而不是回收旧的文件描述符。例如在您的情况下,它很可能会为您使用的每个套接字分配一个新的文件描述符。此外,如果您不关闭它们,您可能会用完可用的文件描述符。但我想你已经知道了。
  • 内核将为进程重用以前关闭的文件描述符,但不会打开套接字。你有办法保持从一个 HTTP 请求到下一个 HTTP 请求的状态吗?
  • 感谢您的 cmets。 @NikolaiNFetissov:是的,我这样做了,但是 PHP 没有公开原始文件描述符,而且我无法真正将 PHP 套接字资源保存在任何地方(我假设它们不可序列化)。

标签: php linux sockets tcp file-descriptor


【解决方案1】:

如果您想在同一进程中重用套接字,只需保持连接打开即可。这实际上是避免 TCP 握手的唯一选择。确保keepalives开启:

s.setsockopt( socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

如果您想生成新进程并将连接传递给它们,是的,它们将能够写入/dev/fd/{$id},这将通过网络发送数据。只需确保在 exec 期间未关闭套接字(了解 SOCK_CLOEXEC)。

不可能将套接字传递给不相关的进程。您必须使用某种形式的进程间通信来实现这一点,而且我不确定内部网或 Internet 条件下 TCP 握手的开销是否足以证明与此相关的复杂性和其他开销是合理的。

【讨论】:

  • 感谢您的回答。我相信SO_KEEPALIVE 仅适用于侦听套接字,而不适用于对等套接字。然而,我也没有在我的问题中明确说明这一点。所以竖起大拇指!
  • @Tom - 也谢谢。您可以在客户端使用SO_KEEPALIVE;在连接之前或什至在建立连接之后(只是不在握手中间)。
【解决方案2】:

如果我故意打开我打开的套接字服务一个请求, (我不调用 socket_close() 或 socket_shutdown())并连接一个 具有完全相同参数的套接字到为下一个服务的相同 IP 要求; linux会重新使用之前打开的socket / 文件描述符?

不,但如果您处于同一进程中,您可以始终继续使用原始版本。您所说的实际上是连接池。

【讨论】:

  • 您好,谢谢您的回答。请记住,PHP 是无状态的,并且没有直接的方法可以将资源从一个请求周期传递到下一个请求周期。
【解决方案3】:

虽然Jirka Hanika 给出的答案对于大多数系统都是正确的,但我得出的结论是遗憾地不适用于PHP;使用 PHP sockets extension 重用套接字是不可能从用户空间实现的。

导致这个结论的代码是:

function statDescriptors() {
    foreach (glob('/proc/self/fd/*') as $sFile) {
        $r = realpath($sFile);
        // ignore local file descriptors
        if($r === false) {
            echo `stat {$sFile}`, "\n";
        }
    }       
}

header('Content-Type: text/plain');

statDescriptors();

$oSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

socket_set_option($oSocket, SOL_SOCKET, SO_KEEPALIVE, 1);
socket_set_option($oSocket, SOL_SOCKET, SO_REUSEADDR, 1);

socket_connect($oSocket, '173.194.35.33', 80); // Google IP

socket_write($oSocket, 'GET / HTTP/1.0' . "\r\n");
socket_write($oSocket, 'Connection: Keep-Alive' . "\r\n\r\n");

socket_read($oSocket, 1024 * 8);

// socket_close($oSocket); // toggle this line comment during test

echo 'PID is: ', getmypid(), "\n";

statDescriptors();

此代码将stat() 当前进程在其执行开始和结束时打开套接字文件描述符。在两者之间,它将打开一个带有SO_KEEPALIVE 的套接字,向它写入请求并读取响应。然后它会选择性地关闭套接字(切换行注释)并回显当前进程的 PID(以确保您在同一个进程中)。

您会看到,无论您是否关闭套接字,为上一个请求创建的文件描述符在此循环执行开始时将不再存在,并且将创建并连接一个全新的套接字。

我无法测试 SOCK_CLOEXEC,因为它(还没有?)在扩展程序中实现。

(这是使用 PHP 5.4.0 测试的)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 1970-01-01
    • 2015-05-24
    • 2013-01-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多