Heim > Artikel > Backend-Entwicklung > Lassen Sie uns über die PHP-SOCKET-Programmierung sprechen (mit ausführlicher Erklärung)
在PHP中有太多我们需要学习和了解的东西,今天这篇文章就让我们一起聊聊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 交互式处理等
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:
<span style="font-family:Arial,Helvetica,Georgia,sans-serif; color:#525454"><span style="line-height:24px">php server.php</span></span><span style="font-family:Arial,Helvetica,Georgia,sans-serif; color:#525454"><span style="line-height:24px"></span></span><span style="font-family:Arial,Helvetica,Georgia,sans-serif; color:#525454"><span style="line-height:24px"></span></span>
这里注意socket_read函数:
Der optionale Typparameter ist eine benannte Konstante:
PHP_BINARY_READ – verwendet die Systemfunktion recv(). Sicherheit zum Lesen von Binärdaten. (Standard in PHP = 4.1.0)
PHP_NORMAL_READ – Lesen stoppt bei n oder r (Standard in PHP 4.0.6) n. Ursache socket_read(): Lesen vom Socket nicht möglich
3. Gleichzeitige E/A-Programmierung in PHP
Die frühesten serverseitigen Programme lösten das Problem der Parallelität
IOdurch Multiprozess und Multithread. Das Prozessmodell erschien am frühesten, und das Konzept des Prozesses gibt es seit der Geburt des
UnixDie frühesten serverseitigen Programme sind im Allgemeinen Akzeptieren
Ein Prozess wird erstellt, wenn ein Client eine Verbindung herstellt, und dann tritt der untergeordnete Prozess in eine Schleife ein, um synchron und blockierend mit der Clientverbindung zu interagieren und zu senden und zu empfangen Daten.
Der Multithreading-Modus ist später leichter als Prozesse und der Speicherstapel wird von Threads gemeinsam genutzt, sodass die Interaktion zwischen verschiedenen Threads sehr einfach zu implementieren ist. 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 und nur mit komplexen Technologien erreicht werden kann.
Codebeispiel:
Multiprozess/Der Prozess des Thread-Modells ist
Thread-Erstellung und -Zerstörung sehr teuer ist. Daher kann das obige Modell nicht auf sehr ausgelastete Serverprogramme angewendet werden. Die entsprechende verbesserte Version löst dieses Problem. Dies ist das klassische Leader-Follower-Modell. Codebeispiel:
Seine Besonderheit besteht darin, dass nach dem Start des Programms
N
Prozesse erstellt werden. Jeder untergeordnete Prozess tritt in
Accept ein und wartet auf neue 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 und tritt erneut in Accept ein, um an der Verarbeitung neuer Verbindungen teilzunehmen. Der Vorteil dieses Modells besteht darin, dass der Prozess ohne zusätzlichen Verbrauch vollständig wiederverwendet werden kann und die Leistung sehr gut ist. Viele gängige Serverprogramme basieren auf diesem Modell, wie zum Beispiel Apache,
PHP-FPM. Das Multiprozessmodell hat auch einige Nachteile. ), bei denen ein Server Zehntausende oder sogar Hunderttausende oder Millionen verwalten muss Verbindungen gleichzeitig herzustellen (klassisches C10KProblem), ist das Multiprozessmodell unzureichend. Es gibt ein weiteres Szenario, das ebenfalls die Schwäche des Multiprozessmodells darstellt. Normalerweise startet Web Server 100 Prozesse, wenn eine Anfrage 100ms verbraucht, 100 Prozesse können 1000qps bereitstellen Art der Verarbeitungsfähigkeit ist immer noch gut. Wenn die Anfrage jedoch die Http-Schnittstelle des externen Netzwerks aufrufen muss, z. B. QQ, Weibo-Anmeldung, dauert es lange, eine Anfrage dauert 10 Sekunden . Dieser eine Prozess kann nur 0,1 Anfragen in 1 Sekunden verarbeiten, und 100 Prozesse können nur 10qps verarbeiten , so Die Rechenleistung ist Schade. Gibt es eine Technologie, die die gesamte Parallelität IO in einem Prozess bewältigen kann? Die Antwort lautet: Ja, das ist IO Multiplexing-Technologie. aktuell ly IOGeschichte und mehr der Wiederverwendung Der Prozess ist gleich lang, Linux stellt seit langem den select-Systemaufruf bereit, der 1024 Verbindungen in einem Prozess aufrechterhalten kann. Später wurde der poll-Systemaufruf hinzugefügt, poll hat einige Verbesserungen vorgenommen, das Problem des 1024-Limits gelöst und kann eine beliebige Anzahl von Verbindungen aufrechterhalten. Ein weiteres Problem bei select/poll besteht jedoch darin, dass eine Schleife ausgeführt werden muss, um zu erkennen, ob Ereignisse in der Verbindung vorliegen. Hier kommt das Problem: Wenn der Server 100 Millionen Verbindungen hat und nur eine Verbindung zu einem bestimmten Zeitpunkt Daten an den Server sendet, muss select/poll eine Schleife ausführen 100 Zehntausend Mal, davon nur 1 Mal, und die restlichen 9910.0009999 Mal alles ungültig und verschwendet CPU Ressourcen. Erst als Linux 2.6Kernel den neuen epollSystemaufruf bereitstellte, der eine unbegrenzte Anzahl von Verbindungen ohne Abfragen aufrechterhalten konnte, wurde dieses Problem wirklich gelöst. C10K Frage. Heutzutage werden verschiedene asynchrone IO-Serverprogramme basierend auf epoll implementiert, wie Nginx, Node. js 、 Erlang, Golang. Ein Single-Process- und Single-Threaded-Programm wie Node.js kann dank epoll mehr als 1MillionenTCP Verbindungen verwalten Technologie. iomultiple asynchrone nicht blockierende Programme verwenden das klassische reactor-Modell, reactoras der Name, es bedeutet einen Reaktor, und es verarbeitet keine Daten selbst senden und empfangen. Sie können einfach die Ereignisänderungen eines Socket-Handles überwachen. Reaktor hat 4 Kernoperationen: Reactor ist lediglich ein Ereignisgenerator, der tatsächlich das Socket-Handle betreibt, z. B. connect/accept , senden/empfangen , Schließen erfolgt in Rückruf. Informationen zur spezifischen Codierung finden Sie im folgenden Pseudocode: ReactorDas Modell kann auch mit Multiprozess und Multithread kombiniert werden, um asynchrones, nicht blockierendes IO zu erreichen und Multi-Core nutzen. Die derzeit beliebten asynchronen Serverprogramme sind alle auf diese Weise: wie 4. PHP socket内部源码 从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。 Zend API实际对c函数socket做了包装,供PHP使用。 而在c的socket编程中,我们使用如下方式初始化socket。
5. socket函数 Beschreibung des Funktionsnamens
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) 进入死循环 推荐学习:《PHP视频教程》
IOWiederverwendung/Ereignisschleife/nicht blockierend
下面我们以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;
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öscht den Socket-Fehler oder den letzten Fehlercode _Socket_Close () Schalten Sie eine Socket-Ressource aus.
Socket_connect () Starten Sie eine Socket-Verbindung.
Socket_create_listen () Öffnen Sie einen Socket. Monitor_create_pair (), um ein Paar ohne Unterschied zu generieren. Ket zu einem Array.
socket_create() generiert einen Socket, der äquivalent ist Generieren einer Socket-Datenstruktur
socket_get_option() Ruft die Socket-Option ab
socket_getpeername() Ruft die IP-Adresse eines entfernten ähnlichen Hosts ab
socket_getsockname() Ruft die IP der lokalen Socket-Adresse 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öschen Sie einen zugewiesenen iovec
socket_iovec_fetch() Gibt die Daten der angegebenen Iovec-Ressource zurück.
socket_iovec_free() Gibt eine Iovec-Ressource frei
socket_listen () Hört alle Verbindungen vom angegebenen Socket ab.
socket_read() Liest die Daten der angegebenen Länge.
socket_readv() Liest die Daten aus dem Scatter/Aggregate-Array.
socket_recv() Beendet die Daten vom Socket zum Cache
socket_recvfrom() akzeptiert Daten vom angegebenen Socket. Wenn nicht angegeben, wird standardmäßig der aktuelle Socket verwendet
socket_recvmsg() Nachrichten von iovec empfangen
socket_select() Mehrfachauswahl
socket_send() Diese Funktion sendet Daten an den angeschlossenen Socket
socket_sendmsg() Sendet Nachrichten an Socket
s endto() Sende a Nachricht an den Socket an der angegebenen Adresse
socket_set_block() Setzt den Socket auf Blockmodus
socket_set_nonblock() Setzt den Socket auf Nichtblockmodus
socket_set_option() Setzt die Socket-Option
socket_shutdown() Mit dieser Funktion können Sie das Lesen, Schreiben oder Schließen des angegebenen Sockets schließen. Socket_strerror() gibt einen detaillierten Fehler mit der angegebenen Fehlernummer zurück. Socket_write() schreibt Daten in den Socket-Cache. Socket_writev() schreibt Daten in den Socket das Scatter/Aggregate-Array
/**
*
* @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);
//对结果做进一步处理,防止进入死循环
}
Das obige ist der detaillierte Inhalt vonLassen Sie uns über die PHP-SOCKET-Programmierung sprechen (mit ausführlicher Erklärung). Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!