PHP SOCKET 프로그래밍_php 기술에 대한 자세한 설명

WBOY
풀어 주다: 2016-05-16 20:15:11
원래의
988명이 탐색했습니다.

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 소켓 확장 사용

서버측 코드:

<&#63;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); 
로그인 후 복사

클라이언트 코드:

<&#63;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 <= 4.0.6의 기본값)

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 &#63; 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) 进入死循环

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿