本文主要和大家分享PHP之SOCKET编程详解,主要结合文字和代码的形式和大家分享,希望能帮助到大家。
1. 预备知识
一直以来很少看到有多少人使用php的socket模块来做一些事情,大概大家都把它定位在脚本语言的范畴内吧,但是其实php的socket模块可以做很多事情,包括做ftplist,http post提交,smtp提交,组包并进行特殊报文的交互(如smpp协议),whois查询。这些都是比较常见的查询。
特别是php的socket扩展库可以做的事情简直不会比c差多少。
php的socket连接函数
1、集成于内核的socket
这个系列的函数仅仅只能做主动连接无法实现端口监听相关的功能。而且在4.3.0之前所有socket连接只能工作在阻塞模式下。
此系列函数包括
fsockopen,pfsockopen
这两个函数的具体信息可以查询php.net的用户手册
他们均会返回一个资源编号对于这个资源可以使用几乎所有对文件操作的函数对其进行操作如fgets(),fwrite(), fclose()等单注意的是所有函数遵循这些函数面对网络信息流时的规律,例如:
fread() 从文件指针 handle 读取最多 length 个字节。 该函数在读取完 length 个字节数,或到达 EOF 的时候,或(对于网络流)当一个包可用时就会停止读取文件,视乎先碰到哪种情况。
可以看出对于网络流就必须注意取到的是一个完整的包就停止。
2、php扩展模块带有的socket功能。
php4.x 以后有这么一个模块extension=php_sockets.dll,Linux上是一个extension=php_sockets.so。
当打开这个此模块以后就意味着php拥有了强大的socket功能,包括listen端口,阻塞及非阻塞模式的切换,multi-client 交互式处理等
这个系列的函数列表参看http://www.php.net/manual/en/ref.sockets.php
看过这个列表觉得是不是非常丰富呢?不过非常遗憾这个模块还非常年轻还有很多地方不成熟,相关的参考文档也非常少:(
我也正在研究中,因此暂时不具体讨论它,仅给大家一个参考文章。
2. 使用PHP socket扩展
服务器端代码:
<?php /** * File name server.php * 服务器端代码 * * @author guisu.huang * @since 2012-04-11 * */ //确保在连接客户端时不会超时 set_time_limit(0); //设置IP和端口号 $address = "127.0.0.1"; $port = 2046; //调试的时候,可以多换端口来测试程序! /** * 创建一个SOCKET * AF_INET=是ipv4 如果用ipv6,则参数为 AF_INET6 * SOCK_STREAM为socket的tcp类型,如果是UDP则使用SOCK_DGRAM */ $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n"); //阻塞模式 socket_set_block($sock) or die("socket_set_block() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n"); //绑定到socket端口 $result = socket_bind($sock, $address, $port) or die("socket_bind() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n"); //开始监听 $result = socket_listen($sock, 4) or die("socket_listen() 失败的原因是:" . socket_strerror(socket_last_error()) . "/n"); echo "OK\nBinding the socket on $address:$port ... "; echo "OK\nNow ready to accept connections.\nListening on the socket ... \n"; do { // never stop the daemon //它接收连接请求并调用一个子连接Socket来处理客户端和服务器间的信息 $msgsock = socket_accept($sock) or die("socket_accept() failed: reason: " . socket_strerror(socket_last_error()) . "/n"); //读取客户端数据 echo "Read client data \n"; //socket_read函数会一直读取客户端数据,直到遇见\n,\t或者\0字符.PHP脚本把这写字符看做是输入的结束符. $buf = socket_read($msgsock, 8192); echo "Received msg: $buf \n"; //数据传送 向客户端写入返回结果 $msg = "welcome \n"; socket_write($msgsock, $msg, strlen($msg)) or die("socket_write() failed: reason: " . socket_strerror(socket_last_error()) ."/n"); //一旦输出被返回到客户端,父/子socket都应通过socket_close($msgsock)函数来终止 socket_close($msgsock); } while (true); socket_close($sock);
客户端代码:
<?php /** * File name:client.php * 客户端代码 * * @author guisu.huang * @since 2012-04-11 */ set_time_limit(0); $host = "127.0.0.1"; $port = 2046; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)or die("Could not create socket\n"); // 创建一个Socket $connection = socket_connect($socket, $host, $port) or die("Could not connet server\n"); // 连接 socket_write($socket, "hello socket") or die("Write failed\n"); // 数据传送 向服务器发送消息 while ($buff = socket_read($socket, 1024, PHP_NORMAL_READ)) { echo("Response was:" . $buff . "\n"); } socket_close($socket);
使用cli方式启动server:
php server.php
这里注意socket_read函数:
可选的类型参数是一个命名的常数:
PHP_BINARY_READ - 使用系统recv()函数。用于读取二进制数据的安全。 (在PHP>“默认= 4.1.0)
PHP_NORMAL_READ - 读停在\ n或\r(在PHP <= 4.0.6默认)
Für den Parameter PHP_NORMAL_READ, wenn das Antwortergebnis des Servers kein n enthält. Verursacht socket_read(): Kann nicht vom Socket lesen
3. Gleichzeitige IO-Programmierung von PHP
Originaltext: http:// rango .swoole.com/archives/508
1)
Blockierung der Multiprozess-/Multithread-Synchronisation
Die frühesten serverseitigen Programme basieren auf Multiprozessen und Multithread Um das Problem der Parallelität zu lösenIO. Das Prozessmodell erschien am frühesten, und das Konzept des Prozesses existiert seit der Geburt des Unix-Systems. Die frühesten serverseitigen Programme sind im Allgemeinen AkzeptierenEin Prozess wird erstellt, wenn ein Client eine Verbindung herstellt, und dann tritt der untergeordnete Prozess in die Schleife ein Synchronisationsblockierbereich Interagieren Sie mit Clientverbindungen und senden und empfangen Sie Verarbeitungsdaten.
Der Multithreading-Modus erschien später, Threads sind leichter als Prozesse und der Speicherstapel wird zwischen Threads gemeinsam genutzt, sodass die Interaktion zwischen ihnen möglich ist verschiedenen Threads ist sehr einfach zu implementieren. In einem Programm wie einem Chatroom können beispielsweise Client-Verbindungen miteinander interagieren und Spieler im Chatroom können Nachrichten an jede andere Person senden. Die Implementierung im Multithread-Modus ist sehr einfach und eine Client-Verbindung kann direkt im Thread gelesen und geschrieben werden. Der Multiprozessmodus erfordert die Verwendung von Pipelines, Nachrichtenwarteschlangen und gemeinsam genutztem Speicher, um eine Dateninteraktion zu erreichen, die zusammenfassend als Interprozesskommunikation (
IPC) bezeichnet wird ), was nur mit komplexen Technologien erreicht werden kann. Codebeispiel:
Mehrprozess/Der Prozess des Thread-Modells ist
Erstellen Sie einen Socket, binden Sie den Server-Port (bind) , und hören Sie auf dem Port (listen), verwenden Sie in PHP stream_socket_serverEine Funktion kann die oben genannten 3 Schritte ausführen, natürlich können Sie das auch Verwenden Sie php socketsErweiterungen werden separat implementiert.
tritt in die while-Schleife ein und blockiert in AkzeptierenVorgang, warten auf den Eingang der Client-Verbindung. Zu diesem Zeitpunkt wechselt das Programm in einen Ruhezustand, bis ein neuer Client eine Verbindung zum Server initiiert und das Betriebssystem diesen Prozess aktiviert. Die Funktion accept gibt den Socket
Der Hauptprozess durchläuft Fork(php: pcntl_fork) Um einen untergeordneten Prozess zu erstellen, verwenden Sie pthread_create(php unter multi -Threading-Modell: neuer Thread) erstellt einen untergeordneten Thread. Sofern unten nicht anders angegeben, wird Prozess zur Darstellung des Prozess-Threads / verwendet.
Nachdem der untergeordnete Prozess erfolgreich erstellt wurde, tritt er in die while-Schleife ein und blockiert in rev(php: fread) wird aufgerufen und wartet darauf, dass der Client Daten an den Server sendet. Nach dem Empfang der Daten verarbeitet das Serverprogramm diese und verwendet dann send (php: fwrite) sendet eine Antwort an den Client. Ein Dienst mit langer Verbindung interagiert weiterhin mit dem Client, während ein Dienst mit kurzer Verbindung im Allgemeinen schließt, sobald er eine Antwort erhält.
Wenn die Clientverbindung geschlossen wird, wird der untergeordnete Prozess beendet und zerstört alle Ressourcen. Der Hauptprozess wird diesen untergeordneten Prozess recyceln.
Das größte Problem bei diesem Modell ist, dass der Prozess / Thread-Erstellung und -Zerstörung sind teuer. Daher kann das obige Modell nicht auf sehr ausgelastete Serverprogramme angewendet werden. Die entsprechende verbesserte Version löst dieses Problem, bei dem es sich um das klassische Leader-Follower-Modell handelt.
Codebeispiel:
Seine Besonderheit besteht darin, dass es nach dem Programm erstellt wird startet N Prozesse. Jeder untergeordnete Prozess tritt in Akzeptieren ein und wartet auf den Eingang neuer Verbindungen. Wenn der Client eine Verbindung zum Server herstellt, wird einer der untergeordneten Prozesse aktiviert, beginnt mit der Verarbeitung der Client-Anfrage und akzeptiert keine neuen TCP-Verbindungen mehr. Wenn diese Verbindung geschlossen wird, wird der untergeordnete Prozess freigegeben, Akzeptieren erneut eingegeben und an der Verarbeitung neuer Verbindungen beteiligt.
Der Vorteil dieses Modells besteht darin, dass es den Prozess vollständig wiederverwenden kann, ohne zusätzlichen Verbrauch und mit sehr guter Leistung. Viele gängige Serverprogramme basieren auf diesem Modell, wie zum Beispiel Apache, PHP-FPM .
Das Multiprozessmodell hat auch einige Nachteile.
Dieses Modell hängt stark von der Anzahl der Prozesse ab, um Parallelitätsprobleme zu lösen. Eine Clientverbindung muss einen Prozess belegen, wie viele Arbeitsprozesse vorhanden sind und wie viele gleichzeitig ausgeführt werden Verarbeitungsmöglichkeiten So viele wie es gibt. Das Betriebssystem ist in der Anzahl der Prozesse, die es erstellen kann, begrenzt.
Das Starten einer großen Anzahl von Prozessen führt zu einem zusätzlichen Prozessplanungsverbrauch. Wenn es Hunderte von Prozessen gibt, kann der Verbrauch der Prozesskontextwechselplanung CPUweniger als 1 %<🎜 ausmachen > Sie können es ignorieren. Wenn Tausende oder sogar Zehntausende Prozesse gestartet werden, wird der Verbrauch sprunghaft ansteigen. Der geplante Verbrauch kann mehrere zehn Prozent der CPU oder sogar 100 % ausmachen .
Es gibt auch einige Szenarien, die das Multiprozessmodell nicht lösen kann, wie zum Beispiel Instant-Chat-Programme (IM), ein Server muss Zehntausende oder sogar Hunderttausende oder Millionen von Verbindungen gleichzeitig aufrechterhalten (der klassische C10K<🎜). > Problem), das Multiprozessmodell liegt außerhalb meiner Möglichkeiten. Es gibt ein weiteres Szenario, das ebenfalls die Schwäche des Multiprozessmodells darstellt. Normalerweise startet der Web-Server 100 Prozesse, wenn eine Anfrage < verbraucht 🎜>100 ms, 100 Prozesse können 1000qps<🎜 bereitstellen >, diese Verarbeitungsfähigkeit ist ziemlich gut. Wenn die Anfrage jedoch den Aufruf der externen Netzwerkschnittstelle Http erfordert, z. B. QQ , die Weibo-Anmeldung dauert lange, eine Anfrage dauert 10 Sekunden. Dieser Prozess kann 1 Sekunden nur 0,1 Anfragen verarbeiten, 100 Prozesse können nur 10qps erreichen. IO Wiederverwendungstechnologie. EigentlichIODie Geschichte der Wiederverwendung ist so lang wie die von LinuxselectSystemaufrufe können 1024 Verbindungen innerhalb eines Prozesses aufrechterhalten. Später wurde der Systemaufruf Umfrage hinzugefügt und einige Verbesserungen an Umfrage< vorgenommen 🎜>. Das Problem von 1024 wurde gelöst und es können beliebig viele Verbindungen aufrechterhalten werden. Aber auswählen/abfragen Ein weiteres Problem besteht darin, dass eine Schleife erforderlich ist, um zu erkennen, ob Ereignisse in der Verbindung vorliegen. Hier entsteht das Problem, wenn der Server 100 Millionen Verbindungen hat und nur eine Verbindung zu einem bestimmten Zeitpunkt Daten an den Server sendet, Auswählen/Abfragenmuss 100 zehntausend Mal wiederholt werden, davon nur 1 Mal sind Treffer, und die restlichen 9910.000 9999 Zeiten sind ungültig und verschwenden CPU Ressourcen . BisLinux 2.6 stellt der Kernel ein neues epollSystem bereit Der Anruf kann eine unbegrenzte Anzahl von Verbindungen ohne Abfrage aufrechterhalten, was das C10K-Problem wirklich löst. Heutzutage basieren verschiedene asynchrone IO Serverprogramme mit hoher Parallelität auf epoll Implementiert, wie zum Beispiel Nginx, Node.js, Erlang, Golang. Ein Single-Process- und Single-Threaded-Programm wie Node.js kann mehr als 1<🎜 verwalten > MillionenTCP-Verbindungen, alles dank der epoll-Technologie. IOAsynchrone nicht blockierende Programme mit dem klassischen Reactor wiederverwenden Modell, ReaktorWie der Name schon sagt, bedeutet es Reaktor. Es verarbeitet keine Daten, die gesendet und empfangen werden. Es kann nur Ereignisänderungen eines Socket-Handles überwachen. ReaktorJa4 Kerngeschäft: hinzufügenSteckdose hinzufügenanhören Reaktor, kann zuhören
socket kann auch den Clientsocket oder eine Pipe, eventfd < erstellen 🎜>, Signale usw. einstellenEreignisüberwachung ändern, Sie können Legen Sie den Abhörtyp fest, z. B. lesbar und beschreibbar. Lesbar und leicht verständlich, zum Hören
socket bedeutet, dass eine neue Clientverbindung akzeptiert muss. Damit Clientverbindungen Daten empfangen können, ist recv erforderlich. Beschreibbare Ereignisse sind etwas schwieriger zu verstehen. Ein SOCKET verfügt über einen Cache-Bereich. Wenn Sie eine Verbindung zum Client herstellen möchten, senden Sie 2M< Die Daten von 🎜> können nicht auf einmal gesendet werden. Standardmäßig verfügt der Cache-Bereich TCP des Betriebssystems nur über 256K. Sie können jeweils nur 256K senden. Nachdem der Cache voll ist, senden Gibt den Fehler EAGAIN zurück. Zu diesem Zeitpunkt müssen Sie beschreibbare Ereignisse überwachen. Bei der reinen asynchronen Programmierung müssen Sie beschreibbare Ereignisse überwachen, um sicherzustellen, dass der Sendevorgang vollständig nicht blockierend ist . delvom Reaktor Aus der Veranstaltung entfernt und nicht mehr auf Ereignisse warten Rückruf ist die entsprechende Verarbeitungslogik nach Eintreten des Ereignisses, normalerweise in Hinzufügen/Setzen wird formuliert. C Sprache wird mit Funktionszeigern implementiert, JS kann anonyme Funktionen verwenden, PHPSie können anonyme Funktionen, Objektmethoden-Arrays und String-Funktionsnamen verwenden. Reactor ist eigentlich nur ein Ereignisgenerator Socket verarbeitet Vorgänge wie Verbinden/Akzeptieren, Senden/Empfangen, Schließen ist in Rückruf Abgeschlossen in . Informationen zur spezifischen Codierung finden Sie im folgenden Pseudocode: ReaktorDas Modell kann auch sein Bei Verwendung mit mehreren Prozessen kann durch die Kombination mehrerer Threads nicht nur asynchrones, nicht blockierendes IO erreicht, sondern auch die Vorteile mehrerer Kerne genutzt werden. Die derzeit beliebten asynchronen Serverprogramme sind alle auf diese Weise: wie zum Beispiel Nginx: Multi-Process Reaktor Nginx+Lua: MultiprozessReaktor+Coroutine Golang : EinzelthreadReaktor+Multithread-Coroutine Swoole:多线程Reactor+多进程Worker 4. PHP socket内部源码
从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。 Zend API实际对c函数socket做了包装,供PHP使用。
而在c的socket编程中,我们使用如下方式初始化socket。 5. socket函数 Funktionsname
Beschreibung socket_sendmsg() 发送消息到socket 6. PHP Socket模拟请求 我们使用stream_socket来模拟: 注意,以上程序可能会进入死循环; 这个PHP的feof($fp) 需要注意的地方了,我们来分析为什么进入死循环。 实际上,feof是可靠的,但是结合fgets函数一块使用的时候,必须要小心了。一个常见的做法是: 当处理纯文本的时候,fgets获取最后一行字符后,foef函数返回的结果并不是TRUE。实际的运算过程如下: 1) while()继续循环。 2) fgets 获取倒数第二行的字符串 3) feof返回false,进入下一次循环 4)fgets获取最后一行数据 5) 一旦fegets函数被调用,feof函数仍然返回的是false。所以继续执行循环 6) fget试图获取另外一行,但实际结果是空的。实际代码没有意识到这一点,试图处理另外根本不存在的一行,但fgets被调用了,feof放回的结果仍然是false 7) ..... 8) 进入死循环 相关推荐: Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung der SOCKET-Programmierung in PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!IOWiederverwendung/Ereignisschleife /Asynchrone Nichtblockierung
下面我们以socket_create的源码实现来说明PHP的内部实现。
前面我们有说到php的socket是以扩展的方式实现的。在源码的ext目录,我们找到sockets目录。这个目录存放了PHP对于socket的实现。直接搜索PHP_FUNCTION(socket_create),在sockets.c文件中找到了此函数的实现。如下所示代码:/* {{{ proto resource socket_create(int domain, int type, int protocol) U
Creates an endpoint for communication in the domain specified by domain, of type specified by type */
PHP_FUNCTION(socket_create)
{
long arg1, arg2, arg3;
php_socket *php_sock = (php_socket*)emalloc(sizeof(php_socket));
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lll", &arg1, &arg2, &arg3) == FAILURE) {
efree(php_sock);
return;
}
if (arg1 != AF_UNIX
#if HAVE_IPV6
&& arg1 != AF_INET6
#endif
&& arg1 != AF_INET) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket domain [%ld] specified for argument 1, assuming AF_INET", arg1);
arg1 = AF_INET;
}
if (arg2 > 10) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "invalid socket type [%ld] specified for argument 2, assuming SOCK_STREAM", arg2);
arg2 = SOCK_STREAM;
}
php_sock->bsd_socket = socket(arg1, arg2, arg3);
php_sock->type = arg1;
if (IS_INVALID_SOCKET(php_sock)) {
SOCKETS_G(last_error) = errno;
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Unable to create socket [%d]: %s", errno, php_strerror(errno TSRMLS_CC));
efree(php_sock);
RETURN_FALSE;
}
php_sock->error = 0;
php_sock->blocking = 1;
1257,1-8 61%
ZEND_REGISTER_RESOURCE(return_value, php_sock, le_socket);
}
/* }}} */
//初始化Socket
if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)\n",strerror(errno),errno);
exit(0);
}
socket_accept() akzeptiert eine Socket-Verbindung
socket_bind() bindet den Socket an eine IP-Adresse und einen Port
socket_clear_error () Löschen Sie den Socket-Fehler oder den letzten Fehlercode
socket_close() Schließen Sie eine Socket-Ressource
socket_connect() Starten Sie eine Socket-Verbindung
socket_create_listen () öffnet einen Socket, der den angegebenen Port überwacht
socket_create_pair() generiert ein Paar undifferenzierter Sockets in einem Array
socket_create() generiert einen Socket, äquivalent zum Generieren eine Socket-Datenstruktur
socket_get_option() zum Abrufen der Socket-Option
socket_getpeername() zum Abrufen der IP-Adresse eines entfernten ähnlichen Hosts
socket_getsockname() Ruft die IP-Adresse des lokalen Sockets ab
socket_iovec_add() Fügt einem Scatter-/Aggregate-Array einen neuen Vektor hinzu
socket_iovec_alloc() Diese Funktion erstellt Eine iovec-Datenstruktur, die senden, empfangen, lesen und schreiben kann
socket_iovec_delete() löscht einen zugewiesenen iovec
socket_iovec_fetch() gibt die Daten der angegebenen iovec-Ressource zurück
socket_iovec_free() gibt eine Iovec-Ressource frei
socket_iovec_set() legt den neuen Wert der Iovec-Daten fest
socket_last_error() ruft den letzten Fehler ab Code des aktuellen Sockets
socket_listen() hört auf alle Verbindungen vom angegebenen Socket
socket_read() 读取指定长度的数据
socket_readv() 读取从分散/聚合数组过来的数据
socket_recv() 从socket里结束数据到缓存
socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg() 从iovec里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的socket
socket_sendto() 发送消息到指定地址的socket
socket_set_block() 在socket里设置为块模式
socket_set_nonblock() socket里设置为非块模式
socket_set_option() 设置socket选项
socket_shutdown() 这个函数允许你关闭读、写、或指定的socket
socket_strerror() 返回指定错误号的周详错误
socket_write() 写数据到socket缓存
socket_writev() 写数据到分散/聚合数组/**
*
* @param $data= array=array('key'=>value)
*/
function post_contents($data = array()) {
$post = $data ? http_build_query($data) : '';
$header = "POST /test/ HTTP/1.1" . "\n";
$header .= "User-Agent: Mozilla/4.0+(compatible;+MSIE+6.0;+Windows+NT+5.1;+SV1)" . "\n";
$header .= "Host: localhost" . "\n";
$header .= "Accept: */*" . "\n";
$header .= "Referer: http://localhost/test/" . "\n";
$header .= "Content-Length: ". strlen($post) . "\n";
$header .= "Content-Type: application/x-www-form-urlencoded" . "\n";
$header .= "\r\n";
$ddd = $header . $post;
$fp = stream_socket_client("tcp://localhost:80", $errno, $errstr, 30);
$response = '';
if (!$fp) {
echo "$errstr ($errno)<br />\n";
} else {
fwrite($fp, $ddd);
$i = 1;
while ( !feof($fp) ) {
$r = fgets($fp, 1024);
$response .= $r;
//处理这一行
}
}
fclose($fp);
return $response;
}
while ( !feof($fp) ) {
$r = fgets($fp, 1024);
$response .= $r;
}
$fp = fopen("myfile.txt", "r");
while (!feof($fp)) {
$current_line = fgets($fp);
//对结果做进一步处理,防止进入死循环
}