Heim  >  Artikel  >  Backend-Entwicklung  >  PHP realisiert Netzwerk-Socket- und IO-Multiplexing der Systemprogrammierung

PHP realisiert Netzwerk-Socket- und IO-Multiplexing der Systemprogrammierung

不言
不言Original
2018-04-13 10:36:031747Durchsuche

Der Inhalt, der in diesem Artikel mit Ihnen geteilt wird, befasst sich mit Netzwerk-Socket und IO-Multiplexing in der PHP-Systemprogrammierung. Er hat einen gewissen Referenzwert.

Seitdem ist PHP immer ein Leitfaden wurde selten für die Socket-Programmierung verwendet. Schließlich handelt es sich um eine Skriptsprache, und die Effizienz wird zu einem großen Engpass. Es kann jedoch nicht gesagt werden, dass PHP nicht für die Socket-Programmierung verwendet werden kann ist so niedrig, dass beispielsweise Workerman, ein bekanntes PHP-Socket-Framework, in reinem PHP entwickelt wurde und angeblich eine hervorragende Leistung aufweist, sodass in einigen Umgebungen auch die PHP-Socket-Programmierung ihre Fähigkeiten unter Beweis stellen kann.

PHP stellt eine Reihe von Methoden bereit, die denen in der C-Sprach-Socket-Bibliothek ähneln und die wir aufrufen können:

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


Weitere Einzelheiten finden Sie im offiziellen PHP-Handbuch zu Sockets: http://php.net/manual/zh/book.sockets.php


Ein einfaches TCP-Server-Beispiel 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);


Diesen Server starten:


[root@localhost php]# php phptcpserver.php


Danach wurde der Server dort blockiert und wartet auf die Verbindung des Clients. Wir können den Telnet-Befehl verwenden, um eine Verbindung zum Server herzustellen:


[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了哈哈哈


Serverseitige Ausgabe:


[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了哈哈哈


Aber tatsächlich gibt es ein Problem mit diesem TCP-Server. Er kann jeweils nur die Verbindung und Datenübertragung eines Clients verarbeiten Der Prozess ist für das Lesen und Schreiben von Client-Daten verantwortlich. Wenn der Client keine Daten überträgt, befindet sich der TCP-Server in einem blockierten Lesezustand und kann keine Verbindungsanfragen von anderen Clients mehr verarbeiten.


Eine Möglichkeit, dieses Problem zu lösen, ist die Verwendung eines Multiprozessservers. Immer wenn ein Client eine Verbindung herstellt, öffnet der Server einen dafür verantwortlichen Unterprozess Die Daten des Clients werden übertragen und der übergeordnete Prozess überwacht weiterhin die Verbindung des Clients, aber die Kosten für den Start des Prozesses sind hoch. Dieser Multiprozessmechanismus kann offensichtlich keine hohe Parallelität unterstützen.

Eine andere Lösung besteht darin, den IO-Multiplexing-Mechanismus zu verwenden und die von PHP bereitgestellte socket_select-Methode zu verwenden. Sie kann mehrere Sockets überwachen und prüfen, ob sich der Status eines der Sockets ändert, z. B. von Von nicht beschreibbar zu beschreibbar, von nicht lesbar zu lesbar, wird diese Methode zurückgegeben, sodass wir den Socket verarbeiten, Clientverbindungen verarbeiten, Lese- und Schreibvorgänge ausführen usw. können. Schauen wir uns die Einführung von socket_select in der PHP-Dokumentation an

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().


grob übersetzt:

socket_select --- Führt den select()-Systemaufruf auf den angegebenen Socket-Arrays mit einem bestimmten Timeout aus.

socket_select() akzeptiert mehrere Socket-Arrays als Parameter und wartet darauf, dass sie ihren Zustand ändern

Diese basieren auf BSD-Scokets und können diese identifizieren Socket-Ressourcen-Arrays Tatsächlich handelt es sich um eine Sammlung von Dateideskriptoren.

Drei verschiedene Socket-Ressourcen-Arrays werden gleichzeitig überwacht.

Diese drei Ressourcen-Arrays sind nicht erforderlich. Sie können ein leeres Array oder NULL als Parameter verwenden. Vergessen Sie nicht, dass diese drei Arrays als Referenz übergeben werden , werden die Werte dieser Arrays geändert.

Socket_select() gibt erfolgreich die Gesamtzahl der Sockets zurück, deren Status sich in diesen drei Arrays geändert hat. Wenn ein Timeout festgelegt ist und innerhalb des Timeouts keine Statusänderung erfolgt, gibt diese Funktion 0 zurück Gibt FALSE zurück, wenn ein Fehler auftritt. Sie können socket_last_error() verwenden, um den Fehlercode abzurufen.



Verwenden Sie socket_select(), um den phptcpserver.php-Code zu optimieren:

<?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);



Jetzt kann dieser TCP-Server mehrere Clients gleichzeitig unterstützen, die eine Verbindung herstellen:


Serverende:


[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


Ändern Sie die obige Serverrückgabe leicht und geben Sie einen HTTP-Antwortheader und einen einfachen HTTP-Antworttext zurück, sodass er zum einfachsten HTTP-Server wird:




....

                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]);
                    }
                }
.....





Starten Sie den Server neu und simulieren Sie mit Curl die Anforderung des http-Servers:


[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 ~]#


Serverseitige Ausgabe:


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



Ein solcher HTTP-Server mit hoher Parallelität wurde entwickelt, um die Parallelitätsfähigkeit zu testen:




Sind Sie ein wenig gespannt, mehr als 5.000 QPS zu sehen^^?

PHP ist die beste Sprache der Welt!



Das obige ist der detaillierte Inhalt vonPHP realisiert Netzwerk-Socket- und IO-Multiplexing der Systemprogrammierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:PHP-Cache-Instanz mit RedisNächster Artikel:PHP-Cache-Instanz mit Redis