Lors de la création d'une application de serveur basée sur socket à l'aide de PHP, socket_accept () est une fonction très centrale qui accepte une connexion à partir d'une prise d'écoute. Cependant, dans des environnements de concurrence élevés, les développeurs rencontrent souvent le blocage Socket_Accept () , les retards de réponse et même les accidents de serveur. Cet article commencera à partir du mécanisme sous-jacent et analysera en détail pourquoi socket_accept () devient un goulot d'étranglement dans des scénarios de concurrence élevés, et fournira des suggestions d'optimisation basées sur la mise en œuvre de PHP.
Le processus général de création d'un serveur TCP dans PHP est le suivant:
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($socket, '0.0.0.0', 8080);
socket_listen($socket);
while (true) {
$client = socket_accept($socket);
// Suivi $client Traitement de lecture et d'écriture
}
Le but de socket_accept () est d'extraire une connexion client qui a terminé la poignée de main de la prise d'écoute et renvoyer une nouvelle prise pour la communication de données.
Par défaut, socket_accept () bloque. S'il n'y a pas de connexion client, il attendra qu'il y ait une connexion à venir. Bien que cette méthode puisse fonctionner bien dans des scénarios de faible concurrence, les problèmes suivants seront exposés sous une concurrence élevée:
Étant donné que socket_accept () bloque, il ne peut pas exécuter une autre logique en attendant une connexion. En supposant que dans les scénarios de concurrence élevés, des milliers de demandes de connexion arrivent en même temps, et une seule connexion peut être traitée à la fois dans la boucle principale, les connexions restantes ne peuvent être que la file d'attente. Cette méthode de traitement "à thread + lourdement" est susceptible de devenir un goulot d'étranglement sous une concurrence élevée.
Le protocole TCP maintient un arriéré du côté serveur pour stocker les connexions qui ont terminé trois poignées de main mais n'ont pas été acceptées par la couche d'application. Si la file d'attente est pleine, la nouvelle connexion sera rejetée directement par le système d'exploitation. Le troisième paramètre de php socket_listten () est la taille de la file d'attente du backlog, qui peut être seulement 128 par défaut.
socket_listen($socket, 1024); // Augmenter backlog taille
Même si l'arriéré est réglé sur grand, si socket_accept () ne peut pas gérer la connexion dans le temps, la file d'attente sera remplie rapidement.
Le mode CLI de PHP est essentiellement synchrone, bloquant, unique et manque de capacités d'E / S asynchrones et de concurrence. Cela signifie que nous ne pouvons pas gérer plusieurs connexions simultanément via un modèle axé sur les événements comme Nginx ou Node.js. Bien que PCNTL_FORK () puisse être utilisé pour créer des processus enfants, cela consomme beaucoup de ressources et est complexe dans la gestion.
De plus, PHP n'a pas de mécanisme de boucle d'événements à haute performance intégré (comme Epoll, Kqueue), ce qui limite encore son évolutivité dans des scénarios de concurrence élevés.
Une méthode d'amélioration consiste à implémenter le multiplexage des E / S non bloquant en combinaison avec socket_select () :
$clients = [];
while (true) {
$read = array_merge([$socket], $clients);
$write = $except = null;
if (socket_select($read, $write, $except, null) > 0) {
if (in_array($socket, $read)) {
$newClient = socket_accept($socket);
if ($newClient) {
$clients[] = $newClient;
socket_getpeername($newClient, $ip, $port);
echo "Nouvelle connexion à partir de $ip:$port\n";
}
}
foreach ($clients as $key => $client) {
$data = socket_read($client, 1024, PHP_NORMAL_READ);
if ($data === false || trim($data) == '') {
unset($clients[$key]);
socket_close($client);
continue;
}
socket_write($client, "You said: $data");
}
}
}
Cette méthode peut atténuer le goulot d'étranglement des performances de socket_accept () dans une certaine mesure, mais elle est toujours basée sur le modèle SELECT () unique. Lorsqu'il y a trop de connexions, SELECT () lui-même deviendra inefficace. Des modèles plus efficaces tels que Epoll ne sont pas soutenus nativement par PHP.
En plus de l'optimisation au niveau du code, certaines améliorations peuvent être apportées à la couche de déploiement:
Utilisation du proxy inversé : utilisez Nginx ou Caddy comme serveur proxy inversé pour distribuer l'équilibrage de chargement des connexions client à plusieurs instances PHP.
Utilisez une extension PHP ou Swoole : si l'entreprise a des exigences extrêmement élevées pour une concurrence élevée, vous pouvez envisager d'utiliser des extensions de PHP comme Swoole , qui met en œuvre un modèle de service asynchrone à base de coroutine, prend naturellement le traitement Epoll et multi-processus.
Modèle multi-processus ou démon : utilisez pcntl_fork () pour créer un modèle de pool de processus simple, et chaque processus d'enfant appelle indépendamment socket_accept () pour accepter les connexions.
La raison fondamentale pour laquelle Socket_Accept () devient un goulot d'étranglement sous une principale concurrence est son mécanisme d'appel de blocage et les limites du modèle de course à file unique PHP. Bien que un certain degré de traitement simultané puisse être obtenu via socket_select () , à long terme, en utilisant des extensions qui prennent en charge les E / S asynchrones (comme Swoole), les modèles de processus ou l'utilisation de services externes pour soulager le stress est une solution plus réalisable.
Dans les scénarios à forte concurrence, l'utilisation de socket n'est pas seulement un problème d'optimisation du code, mais aussi un défi complet dans la conception d'architecture et les modèles de course. Lors du choix de PHP pour construire des services de connexion longs simultanés élevés, il est important de peser ses limites naturelles et son évolutivité.