Maison > Article > développement back-end > PHP réalise le multiplexage des sockets réseau et des E/S de la programmation système
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
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, '127.0.0.1', 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 === '') { //客户端关闭 socket_close($connsock); echo "client close" . PHP_EOL; break; } else { echo 'read from client:' . $data; $data = strtoupper($data); //小写转大写 socket_write($connsock, $data); //回写给客户端 } } } } socket_close($servsock);
Démarrer ce serveur :
[root@localhost php]# php phptcpserver.php
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 '^]'. ajdjajksdjkaasda AJDJAJKSDJKAASDA 小明哈哈哈哈笑 小明哈哈哈哈笑 小明efsfsdfsdf了哈哈哈 小明EFSFSDFSDF了哈哈哈
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了哈哈哈
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().
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, '127.0.0.1', 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 === '') { //移除对该 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);
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
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]); } } .....
Redémarrez le serveur et utilisez curl pour simuler la requête du serveur http :
[root@localhost ~]# curl '127.0.0.1:8888' ok [root@localhost ~]# curl '127.0.0.1:8888' ok [root@localhost ~]# curl '127.0.0.1:8888' ok [root@localhost ~]# curl '127.0.0.1:8888' ok [root@localhost ~]# curl '127.0.0.1:8888' ok [root@localhost ~]#
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
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!