zoukankan      html  css  js  c++  java
  • PHP Socket 编程之9个主要函数的使用之测试案例

    php的socket编程算是比较难以理解的东西吧,不过,我们只要理解socket几个函数之间的关系,以及它们所扮演的角色,那么理解起来应该不是很难了,在笔者看来,socket编程,其实就是建立一个网络服务的客户端和服务端,这和mysql的客户端和服务端是一样的,你只要理解mysql的客户端和服务端是怎么一回事,你就应该能够理解下面我要讲的东西吧。

    关于socket编程所涉及到的网络协议,什么TCP啊,UDP啊,什么socket三次握手等等,这些网络协议网上有很详细的解释,这里不讲,只截个socket建立套接的过程图让你瞧瞧:

    socket是怎么建立连接的呢?上面已经提到过了,它建立连接的过程是与mysql的客户端和服务端的连接本质是一样的。而它与mysql不同的是,mysql的服务端和客户端都已经为我们编辑好了,我们只要应用就行了。但是,关键时刻来啦,socket它什么东西都没有提供给我们,唯一提供给我们的就是:几十个socket函数。

    参考Socket根据官方文档:http://php.net/manual/zh/book.sockets.php

    PHP Socket 编程之8个主要函数分别是:

    • socket_create — 创建一个套接字(通讯节点)作用:创建一个socket套接字,说白了,就是一个网络数据流。返回值:一个套接字,或者是false,参数错误发出E_WARNING警告socket_create创建并返回一个套接字,也称作一个通讯节点。一个典型的网络连接由2个套接字构成,一个运行在客户端,另一个运行在服务器端。
    • socket_bind — 给套接字绑定名字
    • socket_connect — 开启一个套接字连接 返回true 或false
    • socket_listen —监听一个套接字,返回值为true或者false
    • socket_accept — 接收套接字的资源信息,成功返回套接字的信息资源,失败为false
    • socket_read — 作用:读取套接字的资源信息,返回值:成功把套接字的资源转化为字符串信息,失败为false
    • socket_send — 发送数据
    • socket_write — 作用:把数据写入套接字中  返回值:成功返回字符串的字节长度,失败为false
    • socket_close — 关闭套接字资源

     一、服务端php代码:server.php

     1 <?php 
     2 /*
     3  * AF_INET    IPv4 网络协议。TCP 和 UDP 都可使用此协议。
     4  * SOCK_STREAM    提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
     5  * tcp    Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。
     6  * TCP 能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失,
     7  * TCP 将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因,TCP 在数据传输层使用 8bit 字节边界。
     8  * 因此,TCP 应用程序必须允许传送部分报文的可能。
     9  * */
    10 $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    11 /*绑定接收的套接流主机和端口,与客户端相对应*/
    12 if(socket_bind($socket,'localhost',8888) == false){
    13     echo 'server bind faild:'.socket_strerror(socket_last_error());
    14     /*这里的127.0.0.1是在本地主机测试,你如果有多台电脑,可以写IP地址*/
    15 }
    16 //监听套接流 允许多少个客户端来排队连接
    17 if(socket_listen($socket,4)==false){
    18     echo 'server listen fail:'.socket_strerror(socket_last_error());
    19 }
    20 //让服务器无限获取客户端传过来的信息
    21 do{
    22     /*接收客户端传过来的信息*/
    23     $accept_resource = socket_accept($socket);
    24     /*socket_accept的作用就是接受socket_bind()所绑定的主机发过来的套接流*/
    25 
    26     if($accept_resource !== false){
    27 
    28         socket_getpeername($accept_resource, $addr, $port);  //获取连接过来的客户端ip地址和端口
    29         echo "client connect server: IP=$addr, Port=$port " . PHP_EOL;
    30 
    31         while (1)
    32         {
    33             /*读取客户端传过来的资源,并转化为字符串*/
    34             $string = socket_read($accept_resource,1024);
    35             /*socket_read的作用就是读出socket_accept()的资源并把它转化为字符串*/
    36             echo 'read data from client:'.$string.PHP_EOL;//PHP_EOL为php的换行预定义常量
    37             if($string != false){
    38                 //回写给客户端
    39                 $data = 'server receive data is: '.$string;
    40                 $data = strtoupper($data);  //小写转大写
    41                 /*向socket_accept的套接流写入信息,也就是回馈信息给socket_bind()所绑定的主机客户端*/
    42                 //socket_write($accept_resource,$data,strlen($data));
    43                 socket_write($accept_resource,$data);
    44                 /*socket_write的作用是向socket_create的套接流写入信息,或者向socket_accept的套接流写入信息*/
    45             }else{
    46                 //客户端关闭
    47                 socket_close($accept_resource);
    48                 echo "client close" . PHP_EOL;
    49                 break;
    50             }
    51         }
    52         /*socket_close的作用是关闭socket_create()或者socket_accept()所建立的套接流*/
    53        // socket_close($accept_resource);
    54     }
    55 
    56 }while(true);
    57 
    58 socket_close($socket);
    59 ?>

    开始启动服务:

    我们用telnet来测试:

    但其实这个TCP服务器是有问题的,它一次只能处理一个客户端的连接和数据传输,这是因为一个客户端连接过来后,进程就去负责读写客户端数据,当客户端没有传输数据时,tcp服务器处于阻塞读状态,无法再去处理其他客户端的连接请求了。

    1)解决这个问题的一种办法就是采用多进程服务器,每当一个客户端连接过来,服务器开一个子进程专门负责和该客户端的数据传输,

    而父进程仍然监听客户端的连接,但是起进程的代价是昂贵的,这种多进程的机制显然支撑不了高并发。

    2)另一个解决办法是使用IO多路复用机制,使用php为我们提供的socket_select方法,它可以监听多个socket,

    如果其中某个socket状态发生了改变,比如从不可写变为可写,从不可读变为可读,这个方法就会返回,从而我们就可以去处理这个socket,处理客户端的连接,读写操作等等。

    来看php文档中对该socket_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  ---  在给定的几组sockets数组上执行 select() 系统调用,用一个特定的超时时间。

    socket_select() 接受几组sockets数组作为参数,并监听它们改变状态

    这些基于BSD scokets 能够识别这些socket资源数组实际上就是文件描述符集合。

    三个不同的socket资源数组会被同时监听。

    这三个资源数组不是必传的, 你可以用一个空数组或者NULL作为参数,不要忘记这三个数组是以引用的方式传递的,在函数返回后,这些数组的值会被改变。

    socket_select() 调用成功返回这三个数组中状态改变的socket总数,如果设置了timeout,并且在timeout之内都没有状态改变,这个函数将返回0,出错时返回FALSE,

    可以用socket_last_error() 获取错误码。

    二、使用 socket_select() 优化之前 phptcpserver.php 代码:

     1 <?php 
     2 /*
     3  * AF_INET    IPv4 网络协议。TCP 和 UDP 都可使用此协议。
     4  * SOCK_STREAM    提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
     5  * tcp    Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。
     6  * TCP 能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失,
     7  * TCP 将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因,TCP 在数据传输层使用 8bit 字节边界。
     8  * 因此,TCP 应用程序必须允许传送部分报文的可能。
     9  * */
    10 $servsock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    11 /*绑定接收的套接流主机和端口,与客户端相对应*/
    12 if(socket_bind($servsock,'localhost',8888) == false){
    13     echo 'server bind faild:'.socket_strerror(socket_last_error());
    14     /*这里的127.0.0.1是在本地主机测试,你如果有多台电脑,可以写IP地址*/
    15 }
    16 //监听套接流 允许多少个客户端来排队连接
    17 if(socket_listen($servsock,4)==false){
    18     echo 'server listen fail:'.socket_strerror(socket_last_error());
    19 }
    20 
    21 /* 要监听的三个sockets数组 */
    22 $read_socks     = array();
    23 $write_socks    = array();
    24 $except_socks   = NULL;  // 注意 php 不支持直接将NULL作为引用传参,所以这里定义一个变量
    25 
    26 $read_socks[] = $servsock;
    27 //让服务器无限获取客户端传过来的信息
    28 do{
    29         
    30     /* 这两个数组会被改变,所以用两个临时变量 */
    31     $tmp_reads  = $read_socks;
    32     $tmp_writes = $write_socks;
    33     // int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
    34     $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);  // timeout 传 NULL 会一直阻塞直到有结果返回
    35     
    36     foreach ($tmp_reads as $read)
    37     {
    38         if ($read == $servsock)
    39         {
    40             /* 有新的客户端连接请求 */
    41             $connsock = socket_accept($servsock);  //响应客户端连接, 此时不会造成阻塞
    42             if ($connsock)
    43             {
    44                 socket_getpeername($connsock, $addr, $port);  //获取远程客户端ip地址和端口
    45                 echo "client connect server: ip = $addr, port = $port" . PHP_EOL;
    46 
    47                 // 把新的连接sokcet加入监听
    48                 $read_socks[]  = $connsock;
    49                 $write_socks[] = $connsock;
    50             }
    51         }else{
    52             /* 客户端传输数据 */
    53             $data = socket_read($read, 1024);  //从客户端读取数据, 此时一定会读到数组而不会产生阻塞
    54             if($data === ''){
    55                 //移除对该 socket 监听
    56                 foreach ($read_socks as $key => $val)
    57                 {
    58                     if ($val == $read) unset($read_socks[$key]);
    59                 }
    60                 //移除回写 socket 监听
    61                 foreach ($write_socks as $key => $val)
    62                 {
    63                     if ($val == $read) unset($write_socks[$key]);
    64                 }
    65                 socket_close($read);
    66                 echo "client close" . PHP_EOL;
    67             }else{
    68                 //如果data不为空 返回给对方客户端
    69                 socket_getpeername($read, $addr, $port);  //获取远程客户端ip地址和端口
    70                 echo "read from client # $addr:$port # " . $data;
    71                 
    72                 $data = strtoupper($data);  //小写转大写
    73                 $data = "from server:".$data;
    74                 if (in_array($read, $tmp_writes))
    75                 {
    76                     //如果该客户端可写 把数据回写给客户端
    77                     socket_write($read, $data);
    78                 }
    79             }
    80         }
    81     }
    82     
    83 
    84 }while(true);
    85 //最后关闭连接
    86 socket_close($socket);
    87 ?>

    现在,这个TCP服务器就可以支持多个客户端同时连接了!分别启用2个 telnet 来测试

    测试服务输出如下:

    三、稍微修改上面的服务器返回,返回一个HTTP响应头和一个简单的HTTP响应体,这样就摇身一变成了一个最简单的HTTP服务器

      1 <?php 
      2 /*
      3  * AF_INET    IPv4 网络协议。TCP 和 UDP 都可使用此协议。
      4  * SOCK_STREAM    提供一个顺序化的、可靠的、全双工的、基于连接的字节流。支持数据传送流量控制机制。TCP 协议即基于这种流式套接字。
      5  * tcp    Transmission Control Protocol 是一个可靠的、基于连接的、面向数据流的全双工协议。
      6  * TCP 能够保障所有的数据包是按照其发送顺序而接收的。如果任意数据包在通讯时丢失,
      7  * TCP 将自动重发数据包直到目标主机应答已接收。因为可靠性和性能的原因,TCP 在数据传输层使用 8bit 字节边界。
      8  * 因此,TCP 应用程序必须允许传送部分报文的可能。
      9  * */
     10 $servsock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
     11 /*绑定接收的套接流主机和端口,与客户端相对应*/
     12 if(socket_bind($servsock,'localhost',8888) == false){
     13     echo 'server bind faild:'.socket_strerror(socket_last_error());
     14     /*这里的127.0.0.1是在本地主机测试,你如果有多台电脑,可以写IP地址*/
     15 }
     16 //监听套接流 允许多少个客户端来排队连接
     17 if(socket_listen($servsock,4)==false){
     18     echo 'server listen fail:'.socket_strerror(socket_last_error());
     19 }
     20 
     21 /* 要监听的三个sockets数组 */
     22 $read_socks     = array();
     23 $write_socks    = array();
     24 $except_socks   = NULL;  // 注意 php 不支持直接将NULL作为引用传参,所以这里定义一个变量
     25 
     26 $read_socks[] = $servsock;
     27 //让服务器无限获取客户端传过来的信息
     28 do{
     29         
     30     /* 这两个数组会被改变,所以用两个临时变量 */
     31     $tmp_reads  = $read_socks;
     32     $tmp_writes = $write_socks;
     33     // int socket_select ( array &$read , array &$write , array &$except , int $tv_sec [, int $tv_usec = 0 ] )
     34     $count = socket_select($tmp_reads, $tmp_writes, $except_socks, NULL);  // timeout 传 NULL 会一直阻塞直到有结果返回
     35     
     36     foreach ($tmp_reads as $read)
     37     {
     38         if ($read == $servsock)
     39         {
     40             /* 有新的客户端连接请求 */
     41             $connsock = socket_accept($servsock);  //响应客户端连接, 此时不会造成阻塞
     42             if ($connsock)
     43             {
     44                 socket_getpeername($connsock, $addr, $port);  //获取远程客户端ip地址和端口
     45                 echo "client connect server: ip = $addr, port = $port" . PHP_EOL;
     46 
     47                 // 把新的连接sokcet加入监听
     48                 $read_socks[]  = $connsock;
     49                 $write_socks[] = $connsock;
     50             }
     51         }else{
     52             /* 客户端传输数据 */
     53             $data = socket_read($read, 1024);  //从客户端读取数据, 此时一定会读到数组而不会产生阻塞
     54             if($data === ''){
     55                 //移除对该 socket 监听
     56                 foreach ($read_socks as $key => $val)
     57                 {
     58                     if ($val == $read) unset($read_socks[$key]);
     59                 }
     60                 //移除回写 socket 监听
     61                 foreach ($write_socks as $key => $val)
     62                 {
     63                     if ($val == $read) unset($write_socks[$key]);
     64                 }
     65                 socket_close($read);
     66                 echo "client close" . PHP_EOL;
     67             }else{
     68                 //如果data不为空 返回给对方客户端
     69                 socket_getpeername($read, $addr, $port);  //获取远程客户端ip地址和端口
     70                 echo "read from client # $addr:$port # " . $data;
     71                 
     72                 $data = strtoupper($data);  //小写转大写
     73                 $data = "from server:".$data;
     74                 $len  = strlen($data)+1;
     75                 //增加http响应头 和响应体
     76                 $response = "HTTP/1.1 200 OK
    ";
     77                 $response .= "Server: DaoKrHttpServer
    ";
     78                 $response .= "Content-Type: text/html
    ";
     79                 $response .= "Content-Length: $len
    
    ";
     80                 $response .= "$data
    ";
     81                 
     82                 if (in_array($read, $tmp_writes))
     83                 {
     84                     //如果该客户端可写 把数据回写给客户端
     85                     //socket_write($read, $data);
     86                     //如果该客户端可写 把数据回写给客户端
     87                     socket_write($read, $response);
     88                     socket_close($read);  // 主动关闭客户端连接
     89                     //关闭后删掉当前的读取监听
     90                     foreach ($read_socks as $key => $val)
     91                     {
     92                         if ($val == $read) unset($read_socks[$key]);
     93                     }
     94                     //关闭后删掉当前的回写监听
     95                     foreach ($write_socks as $key => $val)
     96                     {
     97                         if ($val == $read) unset($write_socks[$key]);
     98                     }
     99                 }
    100             }
    101         }
    102     }
    103     
    104 
    105 }while(true);
    106 //最后关闭连接
    107 socket_close($socket);
    108 ?>

    重新启动该服务器,用curl模拟请求该http服务器:

    1 root@DK:/home/daokr# curl 127.0.0.1:8888
    2 from server:GET / HTTP/1.1
    3 HOST: 127.0.0.1:8888
    4 USER-AGENT: CURL/7.47.0
    5 ACCEPT: */*

    服务端返回:

    4)使用apache 的ab命令来压力测试下我们的程序;如果没有安装请安装

     1 nginx压力测试方法:  
     2 #ab命令  
     3 #安装ab  
     4 #Centos系统  
     5 yum install apr-util  
     6 #Ubuntu系统  
     7 sudo apt-get install apache2-utils  

    执行命令:

    ab -c 1000 -n 5000 http://127.0.0.1:8888/

    输出结果:

    root@DK:/home/daokr# ab -n 100 -c 10 http://127.0.0.1:8888/
    This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
    Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
    Licensed to The Apache Software Foundation, http://www.apache.org/

    Benchmarking 127.0.0.1 (be patient).....done


    Server Software: DaoKrHttpServer
    Server Hostname: 127.0.0.1
    Server Port: 8888

    Document Path: /
    Document Length: 95 bytes

    Concurrency Level: 10
    Time taken for tests: 0.212 seconds
    Complete requests: 100
    Failed requests: 0
    Total transferred: 18400 bytes
    HTML transferred: 9500 bytes
    Requests per second: 471.32 [#/sec] (mean)
    Time per request: 21.217 [ms] (mean)
    Time per request: 2.122 [ms] (mean, across all concurrent requests)
    Transfer rate: 84.69 [Kbytes/sec] received

    Connection Times (ms)
    min mean[+/-sd] median max
    Connect: 0 0 0.7 0 7
    Processing: 0 6 28.6 1 205
    Waiting: 0 6 28.5 1 204
    Total: 0 6 28.6 1 205

    Percentage of the requests served within a certain time (ms)
    50% 1
    66% 2
    75% 2
    80% 2
    90% 4
    95% 6
    98% 204
    99% 205
    100% 205 (longest request)
    root@DK:/home/daokr#

    5)另一个压力测试工具

    debian和ubuntu用户可以通过apt-get install siege来安装siege.

    siege是一个跟ab.exe相似的http压力测试软件。

    我们可以用siege来测试我们的网站和服务器性能。

    siege -r 100 -c 10 http://www.domain.com/test.php

    -r 是 repeat , -r 100是重复100次测试

    -c 10是表示模拟10个用户同时并发连接

    最后面是要测试的URL地址。

    测试过程中可以随时按CTRL+C中止进程,siege会生成一个报告给我们。

    我们可以同时根据siege的测试结果和监视服务器的负载情况,对系统压力状况进行一个深入了解和分析。接下来可以帮助我们判断该如何进行下一步操作,是继续优化配置,还是升级硬件。

    6)客户端测试代码 client.php

     1 <?php 
     2 //创建一个socket套接流
     3 $socket = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
     4 /****************设置socket连接选项,这两个步骤你可以省略*************/
     5 //接收套接流的最大超时时间1秒,后面是微秒单位超时时间,设置为零,表示不管它
     6 socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 10, "usec" => 0));
     7 //发送套接流的最大超时时间为6秒
     8 socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 10, "usec" => 0));
     9 /****************设置socket连接选项,这两个步骤你可以省略*************/
    10 
    11 //连接服务端的套接流,这一步就是使客户端与服务器端的套接流建立联系
    12 if(socket_connect($socket,'localhost',8888) == false){
    13     echo 'connect fail massege:'.socket_strerror(socket_last_error());
    14 }else{
    15     //像服务器写数据
    16     $message = 'l love you my socket';
    17     //转为GBK编码,处理乱码问题,这要看你的编码情况而定,每个人的编码都不同
    18     $message = mb_convert_encoding($message,'GBK','UTF-8');
    19     //向服务端写入字符串信息
    20 
    21     if(socket_write($socket,$message,strlen($message)) == false){
    22         echo 'fail to write'.socket_strerror(socket_last_error());
    23     }else{
    24         echo 'client write  data success'.PHP_EOL;
    25         //读取服务端返回来的套接流信息
    26         while($callback = socket_read($socket, 1024)){
    27             echo '服务器响应返回:'.PHP_EOL.$callback;
    28         }
    29     }
    30 }
    31 socket_close($socket);//工作完毕,关闭套接流
    32 
    33 ?>
  • 相关阅读:
    JobRunShell.cs not FOUND
    Manjaro Linux 更新后无法启动问题
    VMware Workstation 16 启动虚拟机失败(vmmon 版本问题)
    sql生成表模型字段
    【短道速滑六】古老的视频去噪算法(FLT_GradualNoise)解析并优化,可实现1920*1080 YUV数据400fps的处理能力。
    Mvc Redis 相关资料
    Android Handler内存泄露
    ANDROID中HANDLER使用浅析
    Android并发编程之白话文详解Future,FutureTask和Callable
    企业微信考勤接口返回的秒数与统计天数关系
  • 原文地址:https://www.cnblogs.com/wanglijun/p/8819526.html
Copyright © 2011-2022 走看看