이 기사에서 공유한 내용은 PHP의 시스템 프로그래밍의 네트워크 소켓 및 IO 다중화에 관한 것입니다. 필요한 친구가 참조할 수 있습니다.
오랫동안 PHP는 거의 사용되지 않았습니다. 프로그래밍은 결국 스크립팅 언어이므로 효율성이 큰 병목 현상이 됩니다. 그러나 소켓 프로그래밍에 PHP를 사용할 수 없으며 PHP의 소켓 프로그래밍 성능이 너무 낮다고 말할 수도 없습니다. 예를 들어, 잘 알려진 PHP 소켓 프레임워크인 Workerman은 순수 PHP로 개발되었으며 뛰어난 성능을 자랑하므로 일부 환경에서는 PHP 소켓 프로그래밍도 그 기술을 뽐낼 수 있습니다.
PHP는 호출할 수 있는 C 언어 소켓 라이브러리의 메소드와 유사한 일련의 메소드를 제공합니다.
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
자세한 내용은 소켓에 대한 공식 PHP 매뉴얼을 확인하세요. http ://php .net/manual/zh/book.sockets.php
간단한 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);
이 서버를 시작하세요:
[root@localhost php]# php phptcpserver.php
그 후 서버가 차단되어 클라이언트가 연결될 때까지 기다립니다.
[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了哈哈哈
서버 출력:
[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了哈哈哈
하지만 실제로 이 TCP 서버에는 한 번에 한 클라이언트의 연결과 데이터 전송만 처리할 수 있다는 문제가 있습니다. 클라이언트가 데이터를 전송하지 않으면 TCP 서버는 읽기 차단 상태가 되어 더 이상 다른 클라이언트의 연결 요청을 처리할 수 없습니다.
이 문제를 해결하는 한 가지 방법은 다중 프로세스 서버를 사용하는 것입니다. 클라이언트가 연결될 때마다 서버는 클라이언트와의 데이터 전송을 담당하는 하위 프로세스를 열고 상위 프로세스는 여전히 클라이언트의 프로세스를 모니터링합니다. 하지만 프로세스 시작 비용이 많이 듭니다. 이 다중 프로세스 메커니즘은 분명히 높은 동시성을 지원할 수 없습니다.
또 다른 해결책은 IO 멀티플렉싱 메커니즘을 사용하고 PHP에서 제공하는 소켓_select 메서드를 사용하는 것입니다. 소켓 중 하나의 상태가 쓰기 불가능에서 쓰기 가능으로, 읽을 수 없음에서 읽기 가능으로 변경되는 경우를 모니터링할 수 있습니다. , 이 메소드는 소켓을 처리하고, 클라이언트 연결을 처리하고, 읽기 및 쓰기 작업 등을 수행할 수 있도록 반환됩니다. PHP 문서에서 소켓_select에 대한 소개를 살펴보겠습니다
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().
대략 번역:
socket_select --- 주어진 소켓 배열 세트에서 select() 시스템 호출을 실행합니다. 특정 시간 초과.
socket_select()는 여러 소켓 배열을 매개변수로 받아들이고 상태 변경을 수신합니다.
이 BSD 스코켓은 이러한 소켓 리소스 배열이 실제로 파일 설명자의 모음임을 인식하는 기능을 기반으로 합니다.
세 개의 서로 다른 소켓 리소스 배열이 동시에 모니터링됩니다.
이 세 가지 리소스 배열은 필수가 아닙니다. 빈 배열이나 NULL을 매개 변수로 사용할 수 있습니다. 이 세 가지 배열은 함수가 반환된 후 해당 배열의 값이 전달됩니다. 변화가 되십시오.
socket_select()는 이 세 배열에서 상태가 변경된 총 소켓 수를 성공적으로 반환합니다. 시간 초과가 설정되어 있고 시간 초과 내에 상태 변경이 없으면 이 함수는 0을 반환합니다. 오류가 발생하면 FALSE를 반환하면 소켓_last_error()를 사용할 수 있습니다. 오류 코드를 가져옵니다. T phptcpserver.php 코드 이전에 소켓_select()를 사용하여 최적화:
<?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);
이제 이 TCP 서버는 여러 클라이언트가 동시에 연결할 수 있도록 지원합니다. 테스트:
서버 측:
R
[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
.... 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]); } } .....
5,000QPS가 넘는 모습이 조금 기대되시나요?^^.
PHP는 세계 최고의 언어입니다!
위 내용은 PHP는 시스템 프로그래밍의 네트워크 소켓 및 IO 다중화를 실현합니다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!