Maison > développement back-end > tutoriel php > PHP réalise le multiplexage des sockets réseau et des E/S de la programmation système

PHP réalise le multiplexage des sockets réseau et des E/S de la programmation système

不言
Libérer: 2023-03-23 16:50:02
original
1850 Les gens l'ont consulté

Le contenu partagé avec vous dans cet article concerne le multiplexage des sockets réseau et des E/S dans la programmation système PHP. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer

Toujours depuis lors, PHP. a rarement été utilisé pour la programmation socket.Après tout, c'est un langage de script, et l'efficacité deviendra un gros goulot d'étranglement. Cependant, on ne peut pas dire que PHP ne peut pas être utilisé pour la programmation socket, ni que les performances de programmation socket de PHP. est si faible. Par exemple, Workerman, un framework de socket PHP bien connu, est développé en PHP pur et prétend avoir d'excellentes performances, donc dans certains environnements, la programmation de socket PHP peut également montrer ses compétences.

PHP fournit une série de méthodes similaires à celles de la bibliothèque de sockets du langage C que nous pouvons appeler :

socket_accept — Accepts a connection on a socket
socket_bind — 给套接字绑定名字
socket_clear_error — 清除套接字或者最后的错误代码上的错误
socket_close — 关闭套接字资源
socket_cmsg_space — Calculate message buffer size
socket_connect — 开启一个套接字连接
socket_create_listen — Opens a socket on port to accept connections
socket_create_pair — Creates a pair of indistinguishable sockets and stores them in an array
socket_create — 创建一个套接字(通讯节点)
socket_get_option — Gets socket options for the socket
socket_getopt — 别名 socket_get_option
socket_getpeername — Queries the remote side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type
socket_getsockname — Queries the local side of the given socket which may either result in host/port or in a Unix filesystem path, dependent on its type
socket_import_stream — Import a stream
socket_last_error — Returns the last error on the socket
socket_listen — Listens for a connection on a socket
socket_read — Reads a maximum of length bytes from a socket
socket_recv — 从已连接的socket接收数据
socket_recvfrom — Receives data from a socket whether or not it is connection-oriented
socket_recvmsg — Read a message
socket_select — Runs the select() system call on the given arrays of sockets with a specified timeout
socket_send — Sends data to a connected socket
socket_sendmsg — Send a message
socket_sendto — Sends a message to a socket, whether it is connected or not
socket_set_block — Sets blocking mode on a socket resource
socket_set_nonblock — Sets nonblocking mode for file descriptor fd
socket_set_option — Sets socket options for the socket
socket_setopt — 别名 socket_set_option
socket_shutdown — Shuts down a socket for receiving, sending, or both
socket_strerror — Return a string describing a socket error
socket_write — Write to a socket
Copier après la connexion


Pour plus de détails, veuillez consulter le manuel officiel de PHP sur les sockets : http://php.net/manual/zh/book.sockets.php


Un exemple simple de serveur TCP phptcpserver.php :

<?php

$servsock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);  // 创建一个socket

if (FALSE === $servsock)
{
    $errcode = socket_last_error();
    fwrite(STDERR, "socket create fail: " . socket_strerror($errcode));
    exit(-1);
}

if (!socket_bind($servsock, &#39;127.0.0.1&#39;, 8888))    // 绑定ip地址及端口
{
    $errcode = socket_last_error();
    fwrite(STDERR, "socket bind fail: " . socket_strerror($errcode));
    exit(-1);
}

if (!socket_listen($servsock, 128))      // 允许多少个客户端来排队连接
{
    $errcode = socket_last_error();
    fwrite(STDERR, "socket listen fail: " . socket_strerror($errcode));
    exit(-1);
}

while (1)
{
    $connsock = socket_accept($servsock);  //响应客户端连接

    if ($connsock)
    {
        socket_getpeername($connsock, $addr, $port);  //获取连接过来的客户端ip地址和端口
        echo "client connect server: ip = $addr, port = $port" . PHP_EOL;

        while (1)
        {
            $data = socket_read($connsock, 1024);  //从客户端读取数据

            if ($data === &#39;&#39;)
            {
                //客户端关闭
                socket_close($connsock);
                echo "client close" . PHP_EOL;
                break;
            }
            else
            {
                echo &#39;read from client:&#39; . $data;
                $data = strtoupper($data);  //小写转大写
                socket_write($connsock, $data);  //回写给客户端
            }
        }
    }
}

socket_close($servsock);
Copier après la connexion


Démarrer ce serveur :


[root@localhost php]# php phptcpserver.php
Copier après la connexion


Après cela, le serveur y a été bloqué, en attendant que le client se connecte. Nous pouvons utiliser la commande telnet pour nous connecter à ce serveur :


.

[root@localhost ~]# telnet 127.0.0.1 8888
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is &#39;^]&#39;.
ajdjajksdjkaasda
AJDJAJKSDJKAASDA
小明哈哈哈哈笑
小明哈哈哈哈笑
小明efsfsdfsdf了哈哈哈
小明EFSFSDFSDF了哈哈哈
Copier après la connexion


Sortie côté serveur :


[root@localhost php]# php phptcpserver.php 
client connect server: ip = 127.0.0.1, port = 50398
read from client:ajdjajksdjkaasda
read from client:小明哈哈哈哈笑
read from client:小明efsfsdfsdf了哈哈哈
Copier après la connexion


Mais en fait, il y a un problème avec ce serveur TCP. Il ne peut gérer que la connexion et la transmission de données d'un client à la fois. Le processus est responsable de la lecture et de l'écriture des données client. Lorsque le client Lorsqu'aucune donnée n'est transmise, le serveur TCP est dans un état de lecture bloqué et ne peut plus gérer les demandes de connexion des autres clients.


Une façon de résoudre ce problème consiste à utiliser un serveur multi-processus Chaque fois qu'un client se connecte, le serveur ouvre un sous-processus responsable de. Les données du client sont transmises et le processus parent surveille toujours la connexion du client, mais le coût de démarrage du processus est élevé. Ce mécanisme multi-processus ne peut évidemment pas prendre en charge une concurrence élevée.

Une autre solution consiste à utiliser le mécanisme de multiplexage IO et à utiliser la méthode socket_select fournie par PHP. Elle peut surveiller plusieurs sockets et si l'état de l'une des sockets change, par exemple à partir de. non inscriptible à inscriptible, d'illisible à lisible, cette méthode reviendra, afin que nous puissions traiter le socket, gérer les connexions client, les opérations de lecture et d'écriture, etc. Regardons l'introduction de socket_select dans la documentation php

socket_select — Runs the select() system call on the given arrays of sockets with a specified timeout

说明

int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )

socket_select() accepts arrays of sockets and waits for them to change status. 
Those coming with BSD sockets background will recognize that those socket resource arrays are in fact the so-called file descriptor sets.
 Three independent arrays of socket resources are watched.

You do not need to pass every array to socket_select(). You can leave it out and use an empty array or NULL instead.
 Also do not forget that those arrays are passed by reference and will be modified after socket_select() returns.

返回值

On success socket_select() returns the number of socket resources contained in the modified arrays, 
which may be zero if the timeout expires before anything interesting happens.
 On error FALSE is returned. The error code can be retrieved with socket_last_error().
Copier après la connexion


traduit grossièrement :

socket_select --- Exécute l'appel système select() sur les tableaux de sockets donnés, avec un délai d'attente spécifique.

socket_select() accepte plusieurs tableaux de sockets comme paramètres et les écoute pour changer d'état

Ceux-ci sont basés sur des scokets BSD et peuvent les identifier tableaux de ressources socket En fait, il s'agit d'une collection de descripteurs de fichiers.

Trois tableaux de ressources de socket différents seront surveillés en même temps.

Ces trois tableaux de ressources ne sont pas obligatoires. Vous pouvez utiliser un tableau vide ou NULL comme paramètre. N'oubliez pas que ces trois tableaux sont passés par référence lorsque la fonction revient plus tard. , les valeurs de ces tableaux seront modifiées.

Socket_select() renvoie avec succès le nombre total de sockets dont l'état a changé dans ces trois tableaux. Si le délai d'attente est défini et qu'il n'y a aucun changement d'état dans le délai d'attente, cette fonction renverra 0. . Renvoie FALSE lorsqu'une erreur se produit. Vous pouvez utiliser socket_last_error() pour obtenir le code d'erreur.



Utilisez socket_select() pour optimiser le code phptcpserver.php :

<?php

$servsock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);  // 创建一个socket

if (FALSE === $servsock)
{
    $errcode = socket_last_error();
    fwrite(STDERR, "socket create fail: " . socket_strerror($errcode));
    exit(-1);
}

if (!socket_bind($servsock, &#39;127.0.0.1&#39;, 8888))    // 绑定ip地址及端口
{
    $errcode = socket_last_error();
    fwrite(STDERR, "socket bind fail: " . socket_strerror($errcode));
    exit(-1);
}

if (!socket_listen($servsock, 128))      // 允许多少个客户端来排队连接
{
    $errcode = socket_last_error();
    fwrite(STDERR, "socket listen fail: " . socket_strerror($errcode));
    exit(-1);
}

/* 要监听的三个sockets数组 */
$read_socks = array();
$write_socks = array();
$except_socks = NULL;  // 注意 php 不支持直接将NULL作为引用传参,所以这里定义一个变量

$read_socks[] = $servsock;

while (1)
{
    /* 这两个数组会被改变,所以用两个临时变量 */
    $tmp_reads = $read_socks;
    $tmp_writes = $write_socks;

    // int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
    $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);  // timeout 传 NULL 会一直阻塞直到有结果返回

    foreach ($tmp_reads as $read)
    {

        if ($read == $servsock)
        {
            /* 有新的客户端连接请求 */
            $connsock = socket_accept($servsock);  //响应客户端连接, 此时不会造成阻塞
            if ($connsock)
            {
                socket_getpeername($connsock, $addr, $port);  //获取远程客户端ip地址和端口
                echo "client connect server: ip = $addr, port = $port" . PHP_EOL;

                // 把新的连接sokcet加入监听
                $read_socks[] = $connsock;
                $write_socks[] = $connsock;
            }
        }
        else
        {
            /* 客户端传输数据 */
            $data = socket_read($read, 1024);  //从客户端读取数据, 此时一定会读到数组而不会产生阻塞

            if ($data === &#39;&#39;)
            {
                //移除对该 socket 监听
                foreach ($read_socks as $key => $val)
                {
                    if ($val == $read) unset($read_socks[$key]);
                }

                foreach ($write_socks as $key => $val)
                {
                    if ($val == $read) unset($write_socks[$key]);
                }


                socket_close($read);
                echo "client close" . PHP_EOL;

            }
            else
            {
                socket_getpeername($read, $addr, $port);  //获取远程客户端ip地址和端口

                echo "read from client # $addr:$port # " . $data;

                $data = strtoupper($data);  //小写转大写

                if (in_array($read, $tmp_writes))
                {
                    //如果该客户端可写 把数据回写给客户端
                    socket_write($read, $data);
                }
            }
        }
    }
}

socket_close($servsock);
Copier après la connexion



Désormais, ce serveur TCP peut prendre en charge plusieurs clients se connectant en même temps. Test :


Fin du serveur :


[root@localhost php]# php phptcpserver.php 
client connect server: ip = 127.0.0.1, port = 50404
read from client # 127.0.0.1:50404 # hello world
client connect server: ip = 127.0.0.1, port = 50406
read from client # 127.0.0.1:50406 # hello PHP
read from client # 127.0.0.1:50404 # 少小离家老大回
read from client # 127.0.0.1:50404 # 乡音无改鬓毛衰
read from client # 127.0.0.1:50406 # 老当益壮,
read from client # 127.0.0.1:50406 # 宁移白首之心
client close
client connect server: ip = 127.0.0.1, port = 50408
Copier après la connexion


Modifiez légèrement le retour du serveur ci-dessus, renvoyez un en-tête de réponse HTTP et un corps de réponse HTTP simple, afin qu'il devienne le serveur HTTP le plus simple :




....

                socket_getpeername($read, $addr, $port);  //获取远程客户端ip地址和端口

                echo "read from client # $addr:$port # " . $data;

                $response = "HTTP/1.1 200 OK\r\n";
                $response .= "Server: phphttpserver\r\n";
                $response .= "Content-Type: text/html\r\n";
                $response .= "Content-Length: 3\r\n\r\n";
                $response .= "ok\n";

                if (in_array($read, $tmp_writes))
                {
                    //如果该客户端可写 把数据回写给客户端
                    socket_write($read, $response);
                    socket_close($read);  // 主动关闭客户端连接
                    //移除对该 socket 监听
                    foreach ($read_socks as $key => $val)
                    {
                        if ($val == $read) unset($read_socks[$key]);
                    }

                    foreach ($write_socks as $key => $val)
                    {
                        if ($val == $read) unset($write_socks[$key]);
                    }
                }
.....
Copier après la connexion





Redémarrez le serveur et utilisez curl pour simuler la requête du serveur http :


[root@localhost ~]# curl &#39;127.0.0.1:8888&#39;
ok
[root@localhost ~]# curl &#39;127.0.0.1:8888&#39;
ok
[root@localhost ~]# curl &#39;127.0.0.1:8888&#39;
ok
[root@localhost ~]# curl &#39;127.0.0.1:8888&#39;
ok
[root@localhost ~]# curl &#39;127.0.0.1:8888&#39;
ok
[root@localhost ~]#
Copier après la connexion


Sortie côté serveur :


client connect server: ip = 127.0.0.1, port = 50450
read from client # 127.0.0.1:50450 # GET / HTTP/1.1
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: 127.0.0.1:8888
Accept: */*

client close
client connect server: ip = 127.0.0.1, port = 50452
read from client # 127.0.0.1:50452 # GET / HTTP/1.1
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: 127.0.0.1:8888
Accept: */*

client close
client connect server: ip = 127.0.0.1, port = 50454
read from client # 127.0.0.1:50454 # GET / HTTP/1.1
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: 127.0.0.1:8888
Accept: */*

client close
client connect server: ip = 127.0.0.1, port = 50456
read from client # 127.0.0.1:50456 # GET / HTTP/1.1
User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2
Host: 127.0.0.1:8888
Accept: */*

client close
Copier après la connexion



Un tel serveur HTTP à haute concurrence a été développé. Utilisez un logiciel de test de stress pour tester la capacité de concurrence :




Êtes-vous un peu impatient de voir plus de 5 000 QPS^^.

PHP est le meilleur langage du monde c'est tout !



Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal