PHP Sockets - 接受多个连接 [英] PHP Sockets - Accept multiple connections

查看:64
本文介绍了PHP Sockets - 接受多个连接的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试创建一个简单的客户端/服务器应用程序,因此我正在尝试使用 PHP 中的套接字.

I'm trying to create a simple client/server application and thus I am experimenting with sockets in PHP.

现在我有一个简单的 C# 客户端,它可以很好地连接到服务器,但我一次只能将一个客户端连接到该服务器(我在网上找到了这个代码示例,并对其进行了一些调整以进行测试).

Now I have a simple client in C# which connects to the server well, but i can only connect one client at once to this server (I found this code sample online and tweaked it a bit for testing purposes).

有趣的是,我发现了同样的问题,基于这里的相同示例:https://stackoverflow.com/questions/10318023/php-socket-connections-cant-handle-multiple-connection

Funny enough I found the same question, based on the same example here: https://stackoverflow.com/questions/10318023/php-socket-connections-cant-handle-multiple-connection

我试图了解它的每一部分,我已经接近看到它是如何详细工作的,但是由于某种原因,当我连接第二个客户端时,第一个客户端断开连接/崩溃.

I tried to understand every part of it and I'm close to seeing how it works in detail, but for some reason, when I connect a 2nd client, the first one gets disconnected / crashes.

谁能给我一些疯狂的想法或指示我应该看哪里?

Can anyone give me some wild ideas or a pointer to where I should look at?

<?php
// Set time limit to indefinite execution
set_time_limit (0);
// Set the ip and port we will listen on
$address = '127.0.0.1';
$port = 9000;
$max_clients = 10;
// Array that will hold client information
$client = array();
// Create a TCP Stream socket
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
// Bind the socket to an address/port
socket_bind($sock, $address, $port) or die('Could not bind to address');
// Start listening for connections
socket_listen($sock);
// Loop continuously
while (true) {
    // Setup clients listen socket for reading
    $read[0] = $sock;
    for ($i = 0; $i < $max_clients; $i++)
    {
        if (isset($client[$i]))
        if ($client[$i]['sock']  != null)
            $read[$i + 1] = $client[$i]['sock'] ;
    }
    // Set up a blocking call to socket_select()
    $ready = socket_select($read, $write = NULL, $except = NULL, $tv_sec = NULL);
    /* if a new connection is being made add it to the client array */
    if (in_array($sock, $read)) {
        for ($i = 0; $i < $max_clients; $i++)
        {
            if (!isset($client[$i])) {
                $client[$i] = array();
                $client[$i]['sock'] = socket_accept($sock);
                echo("Accepting incoming connection...\n");
                break;
            }
            elseif ($i == $max_clients - 1)
                print ("too many clients");
        }
        if (--$ready <= 0)
            continue;
    } // end if in_array

    // If a client is trying to write - handle it now
    for ($i = 0; $i < $max_clients; $i++) // for each client
    {
        if (isset($client[$i]))
        if (in_array($client[$i]['sock'] , $read))
        {
            $input = socket_read($client[$i]['sock'] , 1024);
            if ($input == null) {
                // Zero length string meaning disconnected
                echo("Client disconnected\n");
                unset($client[$i]);
            }
            $n = trim($input);
            if ($n == 'exit') {
                echo("Client requested disconnect\n");
                // requested disconnect
                socket_close($client[$i]['sock']);
            }
            if(substr($n,0,3) == 'say') {
                //broadcast
                echo("Broadcast received\n");
                for ($j = 0; $j < $max_clients; $j++) // for each client
                {
                    if (isset($client[$j]))
                    if ($client[$j]['sock']) {
                        socket_write($client[$j]['sock'], substr($n, 4, strlen($n)-4).chr(0));
                    }
                }
            } elseif ($input) {
                echo("Returning stripped input\n");
                // strip white spaces and write back to user
                $output = ereg_replace("[ \t\n\r]","",$input).chr(0);
                socket_write($client[$i]['sock'],$output);
            }
        } else {
            // Close the socket
            if (isset($client[$i]))
            echo("Client disconnected\n");
            if ($client[$i]['sock'] != null){ 
                socket_close($client[$i]['sock']); 
                unset($client[$i]); 
            }
        }
    }
} // end while
// Close the master sockets
echo("Shutting down\n");
socket_close($sock);
?>

推荐答案

这里的当前最佳答案是错误的,您不需要多个线程来处理多个客户端.您可以使用非阻塞 I/O 和 stream_select/socket_select 来处理来自客户端的可操作消息.我建议在 socket_* 上使用 stream_socket_* 函数.

The current top answer here is wrong, you don't need multiple threads to handle multiple clients. You can use non-blocking I/O and stream_select / socket_select to process messages from clients that are actionable. I'd recommend using the stream_socket_* functions over socket_*.

虽然非阻塞 I/O 工作得很好,但你不能进行任何涉及阻塞 I/O 的函数调用,否则阻塞 I/O 会阻塞整个进程并且所有客户端挂起,而不仅仅是一个.

While non-blocking I/O works quite fine, you can't make any function calls with involve blocking I/O, otherwise that blocking I/O blocks the complete process and all clients hang, not just one.

这意味着所有 I/O 必须是非阻塞的或保证非常快(这并不完美,但可能是可以接受的).因为不仅您的套接字需要使用 stream_select,而且您还需要选择 所有 打开的流,所以我建议使用一个库来注册读写观察者一旦流变得可读/可写就执行.

That means all I/O has to be non-blocking or guaranteed to be very fast (which isn't perfect, but might be acceptable). Because not only your sockets need to use stream_select, but you need to select on all open streams, I'd recommend a library that offers to register read and write watchers that are executed once a stream becomes readable / writable.

有多个这样的框架可用,最常见的是 ReactPHP放大器.底层事件循环非常相似,但 Amp 在这方面提供了更多功能.

There are multiple such frameworks available, the most common ones are ReactPHP and Amp. The underlying event loops are pretty similar, but Amp offers a few more features on that side.

两者之间的主要区别在于 API 的方法.虽然 ReactPHP 到处都使用回调,但 Amp 试图通过使用协程并针对此类用途优化其 API 来避免它们.

The main difference between the two is the approach for APIs. While ReactPHP uses callbacks everywhere, Amp tries to avoid them by using coroutines and optimizing its APIs for such a usage.

Amp 的入门" 指南基本上就是关于这个主题的.您可以在此处阅读完整指南.我将在下面提供一个工作示例.

Amp's "Getting Started" guide is basically exactly about this topic. You can read the full guide here. I'll include a working example below.

<?php

require __DIR__ . "/vendor/autoload.php";

// Non-blocking server implementation based on amphp/socket.

use Amp\Loop;
use Amp\Socket\ServerSocket;
use function Amp\asyncCall;

Loop::run(function () {
    $uri = "tcp://127.0.0.1:1337";

    $clientHandler = function (ServerSocket $socket) {
        while (null !== $chunk = yield $socket->read()) {
            yield $socket->write($chunk);
        }
    };

    $server = Amp\Socket\listen($uri);

    while ($socket = yield $server->accept()) {
        asyncCall($clientHandler, $socket);
    }
});

Loop::run() 运行事件循环并监视定时器事件、信号和可操作的流,这些可以用 Loop::on*() 注册方法.使用 Amp\Socket\listen() 创建服务器套接字.Server::accept() 返回一个 Promise 可用于等待新的客户端连接.一旦客户端被接受,它就会执行一个协程,该协程从客户端读取并将相同的数据回显给它.有关详细信息,请参阅 Amp 的文档.

Loop::run() runs the event loop and watches for timer events, signals and actionable streams, which can be registered with Loop::on*() methods. A server socket is created using Amp\Socket\listen(). Server::accept() returns a Promise which can be used to await new client connections. It executes a coroutine once a client is accepted that reads from the client and echo's the same data back to it. For more details, refer to Amp's documentation.

这篇关于PHP Sockets - 接受多个连接的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆