1. 사전 지식
PHP의 소켓 모듈을 사용하여 어떤 작업을 수행하는 사람은 거의 본 적이 없습니다. 아마도 모든 사람이 이를 스크립팅 언어의 범위 내에 두지만 실제로 PHP의 소켓 모듈은 ftplist, http post 제출, smtp 제출을 포함하여 많은 작업을 수행할 수 있습니다. , 그룹 패키지 및 특수 메시지의 상호 작용(예: smpp 프로토콜), whois 쿼리. 다음은 보다 일반적인 쿼리 중 일부입니다.
특히 PHP의 소켓 확장 라이브러리가 할 수 있는 일은 C보다 나쁘지 않습니다.
PHP 소켓 연결 기능
1. 커널에 소켓이 통합되어 있습니다
이 일련의 기능은 활성 연결만 수행할 수 있으며 포트 모니터링 관련 기능은 구현할 수 없습니다. 그리고 4.3.0 이전에는 모든 소켓 연결이 차단 모드에서만 작동할 수 있었습니다.
이 일련의 기능에는
이 포함됩니다.
fsockopen, pfsockopen
이 두 가지 기능에 대한 자세한 내용은 php.net 사용자 매뉴얼을 확인하세요
이 리소스의 경우 fgets(), fwrite(), fclose() 등과 같이 파일에서 작동하는 거의 모든 함수를 사용할 수 있습니다. 모든 함수는 이러한 함수를 따라 네트워크 정보를 확인합니다. 흐름. 예:
fread()는 파일 포인터 핸들에서 최대 길이 바이트까지 읽습니다. 이 함수는 length 바이트를 읽거나, EOF에 도달하거나, (네트워크 스트림의 경우) 패킷을 사용할 수 있게 되면(둘 중 먼저 발생하는 경우) 파일 읽기를 중지합니다.
네트워크 흐름의 경우 완전한 패킷을 얻었을 때 중지에 주의해야 함을 알 수 있습니다.
2. php 확장 모듈에서 제공하는 소켓 기능입니다.
php4.x에는 모듈 확장=php_sockets.dll이 있고 Linux에는 확장=php_sockets.so가 있습니다.
이 모듈이 켜져 있으면 PHP가 청취 포트, 차단 모드와 비차단 모드 간 전환, 다중 클라이언트 대화형 처리 등을 포함한 강력한 소켓 기능을 가지고 있음을 의미합니다.
이 시리즈의 기능 목록은 http://www.php.net/manual/en/ref.sockets.php
를 참조하세요.
이 목록을 읽은 후, 그것이 매우 풍부하다고 생각하십니까? 하지만 이 모듈은 아직 여러 곳에서 매우 어리고 미성숙한 상태이며 관련 참고 문서가 거의 없다는 점은 아쉽습니다.(
저도 연구 중이라 자세한 내용은 당분간 다루지 않고 참고글만 드리겠습니다
http://www.zend.com/pecl/tutorials/sockets.php
2. PHP 소켓 확장 사용
서버측 코드:
<?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或者 字符.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를 사용하여 서버 시작:
php 서버.php
여기에서 소켓_읽기 기능에 주목하세요:
선택적 유형 매개변수는 명명된 상수입니다:
PHP_BINARY_READ - 시스템 recv() 함수를 사용합니다. 바이너리 데이터 읽기를 위한 보안. (PHP에서 > "기본값 = 4.1.0)
PHP_NORMAL_READ - 읽기가 n에서 중지되거나
(PHP
PHP_NORMAL_READ 매개변수의 경우, 서버의 응답 결과에 n이 없는 경우. 원인 소켓_read(): 소켓에서 읽을 수 없습니다
3. PHP 소켓 내부 소스코드
PHP 내부 소스코드부터 PHP가 제공하는 소켓 프로그래밍은 소켓에 레이어를 추가해 바인딩, 리슨 등의 기능을 더 간단하고 쉽게 호출할 수 있도록 해준다. 그러나 일부 비즈니스 로직 프로그램은 여전히 프로그래머가 직접 구현해야 합니다.
아래에서는 PHP의 내부 구현을 설명하기 위해 소켓_create의 소스 코드 구현을 사용합니다.
앞서 우리는 PHP의 소켓이 확장된 방식으로 구현된다고 언급했습니다. 소스 코드의 ext 디렉토리에서 소켓 디렉토리를 찾습니다. 이 디렉토리는 PHP의 소켓 구현을 저장합니다. PHP_FUNCTION(socket_create)을 직접 검색하고 소켓.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); }
Zend API는 실제로 PHP에서 사용할 수 있도록 c 함수 소켓을 래핑합니다. C 소켓 프로그래밍에서는 다음 방법을 사용하여 소켓을 초기화합니다.
//初始化Socket if( (socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){ printf("create socket error: %s(errno: %d)\n",strerror(errno),errno); exit(0); }
4. socket函数
函数名 描述
socket_accept() 接受一个Socket连接
socket_bind() 把socket绑定在一个IP地址和端口上
socket_clear_error() 清除socket的错误或最后的错误代码
socket_close() 关闭一个socket资源
socket_connect() 开始一个socket连接
socket_create_listen() 在指定端口打开一个socket监听
socket_create_pair() 产生一对没有差别的socket到一个数组里
socket_create() 产生一个socket,相当于产生一个socket的数据结构
socket_get_option() 获取socket选项
socket_getpeername() 获取远程类似主机的ip地址
socket_getsockname() 获取本地socket的ip地址
socket_iovec_add() 添加一个新的向量到一个分散/聚合的数组
socket_iovec_alloc() 这个函数创建一个能够发送接收读写的iovec数据结构
socket_iovec_delete() 删除一个已分配的iovec
socket_iovec_fetch() 返回指定的iovec资源的数据
socket_iovec_free() 释放一个iovec资源
socket_iovec_set() 设置iovec的数据新值
socket_last_error() 获取当前socket的最后错误代码
socket_listen() 监听由指定socket的所有连接
socket_read() 读取指定长度的数据
socket_readv() 读取从分散/聚合数组过来的数据
socket_recv() 从socket里结束数据到缓存
socket_recvfrom() 接受数据从指定的socket,如果没有指定则默认当前socket
socket_recvmsg() 从iovec里接受消息
socket_select() 多路选择
socket_send() 这个函数发送数据到已连接的socket
socket_sendmsg() 发送消息到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() 写数据到分散/聚合数组
5. PHP Socket模拟请求
我们使用stream_socket来模拟:
/** * * @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; }
注意,以上程序可能会进入死循环;
这个PHP的feof($fp) 需要注意的地方了,我们来分析为什么进入死循环。
while ( !feof($fp) ) { $r = fgets($fp, 1024); $response .= $r; }
实际上,feof是可靠的,但是结合fgets函数一块使用的时候,必须要小心了。一个常见的做法是:
$fp = fopen("myfile.txt", "r"); while (!feof($fp)) { $current_line = fgets($fp); //对结果做进一步处理,防止进入死循环 }
当处理纯文本的时候,fgets获取最后一行字符后,foef函数返回的结果并不是TRUE。实际的运算过程如下:
1) while()继续循环。
2) fgets 获取倒数第二行的字符串
3) feof返回false,进入下一次循环
4)fgets获取最后一行数据
5) 一旦fegets函数被调用,feof函数仍然返回的是false。所以继续执行循环
6) fget试图获取另外一行,但实际结果是空的。实际代码没有意识到这一点,试图处理另外根本不存在的一行,但fgets被调用了,feof放回的结果仍然是false
7) .....
8) 进入死循环