PHP での SOCKET プログラミングの詳細な説明

小云云
リリース: 2018-03-28 15:10:47
オリジナル
5261 人が閲覧しました

本文主要和大家分享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默认)

パラメータPHP_NORMAL_READの場合、サーバーの応答結果が n でない場合。原因socket_read(): ソケットから読み取ることができません


3。

原文: http://rango.swoole. com /archives/508

1) マルチプロセス/マルチスレッドの同期ブロック



初期のサーバーサイドプログラムは、マルチプロセスとマルチスレッドによって同時実行性の問題

IO

を解決しました。プロセス モデルは最も早くに登場し、プロセスの概念は Unix システムの誕生以来存在しています。 最も初期のサーバー側プログラムは一般に Accept クライアントが接続するとプロセスが作成され、その後、子プロセスがループに入り、同期的かつブロック的な方法でクライアント接続と対話し、送信と受信を行います。データ。 マルチスレッド モードは後で登場し、スレッドはプロセスよりも軽量であり、メモリ スタックはスレッド間で共有されるため、異なるスレッド間の対話は非常に簡単に実装できます。たとえば、チャット ルームのようなプログラムでは、クライアント接続は相互に対話でき、チャット ルーム内のプレーヤーは他の人にメッセージを送信できます。マルチスレッド モードでの実装は非常に簡単で、クライアント接続はスレッド内で直接読み書きできます。マルチプロセス モードでは、プロセス間通信 (

IPC

) と総称されるデータ対話を実現するために、パイプライン、メッセージ キュー、共有メモリの使用が必要ですが、これは複雑なテクノロジでのみ実現できます。 コード例:

マルチプロセス/スレッドモデルのプロセスは

  1. ソケットを作成し、サーバーポートをバインド(bind) )、リスニングポート( listen)、PHPstream_socket_serverを使用して、上記の3の手順を完了します1つの機能、もちろんそれも使えます php ソケット拡張機能は個別に実装されます。

  2. whileループに入り、accept操作をブロックし、クライアント接続が入るのを待ちます。このとき、プログラムは、新しいクライアントがサーバーへの接続を開始するまでスリープ状態に入り、オペレーティングシステムがこのプロセスを起動します。 accept関数は、クライアントが接続したsocket

  3. を返します。メインプロセスはforkを渡します() php: pcntl_fork) 子プロセスを作成するには、pthread_create(php: new Thread) は子スレッドを作成します。以下で特に明記されていない限り、process はプロセス/スレッドを表すためにも使用されます。

  4. 子プロセスが正常に作成されると、whileループに入り、recv(php: fread) を呼び出し、クライアントがサーバーにデータを送信するのを待ちます。データを受信した後、サーバー プログラムはそれを処理し、send (php: fwrite) はクライアントに応答を送信します。接続時間が長いサービスはクライアントとの対話を継続しますが、接続時間が短いサービスは通常、応答を受信した後に終了します。 クライアント接続が閉じられると、子プロセスが終了し、すべてのリソースが破棄されます。メインプロセスはこの子プロセスをリサイクルします。

  5. このモデルの最大の問題は、

/

スレッドの作成と破棄のプロセスに非常にコストがかかることです。したがって、上記のモデルは非常に負荷の高いサーバー プログラムには適用できません。対応する改良版はこの問題を解決します。これは古典的な リーダー-フォロワー モデルです。 コード例:

その特徴は、プログラムの開始後に


N

のプロセスを作成することです。各子プロセスは Accept に入り、新しい接続が入ってくるのを待ちます。クライアントがサーバーに接続すると、子プロセスの 1 つが起動され、クライアント要求の処理が開始され、新しい TCP 接続は受け入れられなくなります。この接続が閉じられると、子プロセスは解放され、Acceptを再入力して、新しい接続の処理に参加します。

このモデルの利点は、プロセスを完全に再利用でき、追加の消費がなく、非常に優れたパフォーマンスが得られることです。 ApachePHP-FPMなど、多くの一般的なサーバープログラムはこのモデルに基づいています。

マルチプロセス モデルにはいくつかの欠点もあります。

  1. このモデルは、同時実行性の問題を解決するためにプロセスの数に大きく依存しており、1 つのクライアント接続に必要なワーカー プロセスの数は同時処理能力に依存します。オペレーティング システムが作成できるプロセスの数には制限があります。

  2. 多数のプロセスを開始すると、追加のプロセス スケジューリングの消費が発生します。数百のプロセスがある場合、プロセス コンテキスト切り替えスケジューリングの消費量は CPU 1% 未満である可能性があり、数千、さらには数万のプロセスが開始される場合は無視できます。消費が急増するでしょう。スケジュールの消費量は、CPUの数十パーセント、あるいは100%を占める可能性があります。

  3. インスタント チャット プログラム (

IM) など、サーバーが数万、場合によっては数十万、数百万を維持する必要がある、マルチプロセス モデルでは解決できないシナリオもいくつかあります。同時に接続できる数が多い場合 (古典的な C10K 問題)、マルチプロセス モデルでは不十分です。

マルチプロセス モデルの弱点でもある別のシナリオがあります。通常、Webサーバーは、1つのリクエストが100ms100プロセスを消費する場合、100プロセスを開始します1000qpsを提供できます、これ処理能力はまだ良いです。ただし、リクエストで外部ネットワーク Http インターフェイス (QQ、Weibo ログインなど) を呼び出す必要がある場合は、長い時間がかかり、1 つのリクエストには 10 秒 かかります。 。その 1 つのプロセスは 0.1 リクエストを 1 秒でしか処理できず、100 プロセスは 10qps しか処理できません。 、こんな感じで処理能力はあまりにも悪い。

すべての同時実行IOを1つのプロセスで処理できるテクノロジーはありますか?答えは「はい」です。これは IO 多重化テクノロジーです。

IO再利用/イベントループ/非同期ノンブロッキング

実際に IO 再利用の歴史は同じですマルチプロセス 長らく、Linuxは、1つのプロセスで1024接続を維持できるselectシステムコールを長い間提供してきました。その後、pollシステムコールが追加され、pollはいくつかの改良を加え、1024制限の問題を解決し、任意の数の接続を維持できるようになりました。しかし、select/pollのもう1つの問題は、接続上にイベントがあるかどうかを検出するためにループする必要があることです。ここで問題が発生します。サーバーに1億の接続があり、特定の時間にサーバーにデータを送信する接続が1つだけである場合、select/pollはループを実行する必要があります 1001万回のうち、1回のみがヒットし、残りの99million9999回はすべて無効でしたテッド CPU リソース

Linux 2.6になって初めて、ポーリングなしで無制限の接続を維持できる新しいepollシステムコールがカーネルに提供され、実際に解決されましたC10K 質問です。現在、Nginx アーランゴランNode.jsのような単一プロセス、シングルスレッドのプログラムは、1百万TCPを超える接続を維持できます。これはすべてepollのおかげです。 テクノロジー。 IO複数の非同期ノンブロッキングプログラムは、古典的なReactorモデル、Reactor

を使用します。名前が示すように、これはリアクターを意味し、何も処理しませんデータの送受信自体。

socketハンドルのイベントの変更を監視するだけです。 Reactorには4のコアオペレーションがあります:

  1. addaddsocketlisten to reactor、これはlistenすることができます ソケット使用は、クライアントSocketを作成することもできますし、パイプライン、Eventfd、シグナルなどの変更イベント監視型、読み取り可能および書き込み可能などのタイプにすることもできます。読みやすくてわかりやすいので、聞いてください Socket

  2. は、新しいクライアントが接続するときに
  3. accept が必要であることを意味します。クライアント接続がデータを受信するには、recvが必要です。書き込み可能なイベントは、理解するのが少し難しくなります。 SOCKETにはキャッシュ領域があります。2Mデータをクライアント接続に送信したい場合、オペレーティングシステムのデフォルトではTCPになります。 キャッシング このエリアには256Kしかありません。一度に送信できるのは 256K だけです。キャッシュがいっぱいの場合、sendEAGAIN エラーを返します。現時点では、書き込み可能なイベントを監視する必要があります。純粋な非同期プログラミングでは、send操作が完全にノンブロッキングであることを確認する必要があります。 delreactor

  4. から削除され、イベントをリッスンしなくなりました
  5. callbackはイベント発生後の対応する処理ロジックで、通常はadd/setの際に定式化されます。 C言語は関数ポインタで実装されており、JSは匿名関数を使用でき、PHPは匿名関数、オブジェクトメソッド配列、および文字列関数名を使用できます。


Reactorは、socketハンドル上で実際に動作する単なるイベントジェネレーターです(connect/accept送信/ recv closecallbackで行われます。具体的なコーディングについては、以下の疑似コードを参照してください:


Reactorこのモデルは、マルチプロセスおよびマルチスレッドと組み合わせて、非同期ノンブロッキングを実現することもできますIO マルチコアを利用します。現在人気のある非同期サーバー プログラムはすべて次のようなものです:

  • Nginx: マルチプロセス Reactor

  • Nginx+Lua : マルチ-プロセス Reactor+Coroutine

  • Golang: シングルスレッド Reactor+マルチスレッドコルーチン

  • Swoole:多线程Reactor+多进程Worker


4. PHP socket内部源码

从PHP内部源码来看,PHP提供的socket编程是在socket,bind,listen等函数外添加了一个层,让其更加简单和方便调用。但是一些业务逻辑的程序还是需要程序员自己去实现。
下面我们以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);
}
/* }}} */
ログイン後にコピー

Zend API实际对c函数socket做了包装,供PHP使用。 而在c的socket编程中,我们使用如下方式初始化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);  
    }
ログイン後にコピー

5. socket函数

関数名 説明
socket_accept() ソケット接続を受け入れる
socket_bind() ソケットを IP アドレスとポートにバインドする
socket_clear_error() ソケットエラーまたは最後のエラーコードをクリアする
socket_close() ソケットリソースを閉じる
socket_connect() はソケット接続を開始します
socket_create_listen() は指定されたポートでリッスンするソケットを開きます
socket_create_pair() は未分化ソケットのペアを配列に生成します
socket_create() 生成する ソケットは同等ですソケットデータ構造を生成する
socket_get_option() ソケットオプションを取得する
socket_getpeername() リモートの同様のホストの IP アドレスを取得する
socket_getsockname() ローカルソケットの IP アドレスを取得する
socket_iovec _追加する( ) 新しいベクトルを分散/集合配列に追加します
socket_iovec_alloc() この関数は、送信、受信、読み取り、書き込みが可能な iovec データ構造を作成します
socket_iovec_delete() 割り当てられた iovec を削除します
socket_iovec_fetch()指定されたiovecリソースのデータを返す
socket_iovec_free() iovecリソースを解放する
socket_iovec_set() iovecデータの新しい値を設定する
socket_last_error() 現在のソケットの最後のエラーコードを取得する
socket_listen () 指定されたソケットのすべての接続によって監視されます
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() 写数据到分散/聚合数组

6. PHP Socket模拟请求

我们使用stream_socket来模拟:

/**
 * 
 * @param $data= array=array(&#39;key&#39;=>value)
 */
function post_contents($data = array()) {
    $post = $data ? http_build_query($data) : &#39;&#39;;
    $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 = &#39;&#39;;
    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 Socket编程起步

以上がPHP での SOCKET プログラミングの詳細な説明の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート