zoukankan      html  css  js  c++  java
  • php socket编程:使用socket_recv而不是socket_read

    socket_recv和socket_read都可以用于读取socket数据,不过二者有差别,推荐使用socket_recv。
    原因如下:
    1.socket_recv支持多种flag,用于不同场景
    2.socket_recv可以检测socket关闭的情况(例如对端关闭了socket)
    返回值:$return_value=socket_recv(...)
    含义: >0 表示接收到的字节数;
    ===0, 发生了错误,socket closed;
    ===false,无数据,socket not closed。

    socket_read不能判断socket是否已经断开。
    测试流程:
    启动server端,再启动client端:可以正常通信。
    kill掉client端,结果server端只能读取到空字符串。

    测试代码如下:

    file: bug1_server.php

    <?php
    /**
     * file: bug1_server.php
     * socket server
     * 基于php socket函数族
     * IO模型:同步阻塞
     * 粘包处理:固定长度
     * 连接数:1个socket连接
     *
     * 测试目标:模拟client crash时,server无法判断socket是否断开
     * 测试结果:kill杀掉client进程后,server进程socket_last_error()返回为0,无法判断socket是否关闭
     *
     * @author davidyanxw
     * @date 2018.04.27
     */
    
    set_time_limit(0);
    
    //创建服务端的socket套接流,net协议为IPv4,protocol协议为TCP
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    // reuse address
    socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
    
    /*绑定接收的套接流主机和端口,与客户端相对应*/
    if (socket_bind($socket, '127.0.0.1', 8801) == false) {
        echo 'server bind fail:' . socket_strerror(socket_last_error());
    }
    //监听套接流
    if (socket_listen($socket, 4) == false) {
        echo 'server listen fail:' . socket_strerror(socket_last_error());
    }
    
    $accept_resource = socket_accept($socket);
    if($accept_resource === false) {
        echo "accept connection failed".PHP_EOL;
        exit;
    }
    // 读写超时时间:0.8s
    socket_set_option($accept_resource, SOL_SOCKET,     SO_RCVTIMEO, array("sec" => 0, "usec" => 800000));
    socket_set_option($accept_resource, SOL_SOCKET,         SO_SNDTIMEO, array("sec" => 0, "usec" => 800000));
    
    // stream固定长度
    $len = 100;
    
    //让服务器不停获取客户端传过来的信息
    while (true) {
        $string_read = socket_read($accept_resource, $len);
        if($string_read === false) {
            echo "socket error:" . socket_last_error() . ",error msg:" . socket_strerror(socket_last_error()) . PHP_EOL;
            break;
        }
        elseif($string_read == '') {
            if(in_array(socket_last_error(), [SOCKET_EPIPE, SOCKET_ECONNRESET])) {
                echo "socket error:".socket_last_error().",error msg:".socket_strerror(socket_last_error()).PHP_EOL;
                break;
            }
            if(in_array(socket_last_error(), [SOCKET_EAGAIN])) {
            // EAGAIN, retry later
            usleep(500);
            continue;
        }
        echo "server receive empty:" . socket_last_error() . ",error msg:" . socket_strerror(socket_last_error()) . PHP_EOL;
    }
    else {
        $string = trim($string_read);
        echo 'server receive success,msg:['.$string.'],time:' . microtime(true) . PHP_EOL;
        }
    } ;
    
    // 先shutdown,后close
    @socket_shutdown($accept_resource);
    socket_close($accept_resource);
    
    @socket_shutdown($socket);
    socket_close($socket);
    
    /**
     * 生成php随机串
     * @param $length
     * @return string
     */
    function randomkeys($length){
        $output='';
        for ($a = 0; $a<$length; $a++) {
            $output .= chr(mt_rand(33, 126));
        }
        return $output;
    }
    ?>

    file:bug1_client.php

    <?php
    /**
     * file:bug1_client.php
     * socket client
     * 基于php socket函数族
     * IO模型:同步阻塞
     * 粘包处理:固定长度
     * 连接数:1个socket连接
     *
     * @author davidyanxw
     * @date 2018.04.27
     */
    set_time_limit(0);
    
    //创建一个socket套接流
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    //接收套接流的最大超时时间(800ms)
    //发送套接流的最大超时时间(800ms)
    socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 0, "usec" => 800000));
    socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 0, "usec" => 800000));
    
    $len = 100;
    
    //连接服务端的套接流,这一步就是使客户端与服务器端的套接流建立联系
    if (socket_connect($socket, '127.0.0.1', 8801) == false) {
        echo 'connect fail massege:' . socket_strerror(socket_last_error());
    } else {
        while(1){
            $ori_msg = 'Hello, server!'.randomkeys(8);
            $message_write = str_pad($ori_msg, $len);
    
            //向服务端写入字符串信息
            $sent = @socket_write($socket, $message_write, $len);
    
            if ($sent === false) {
                if(in_array(socket_last_error(), [SOCKET_EPIPE, SOCKET_ECONNRESET])) {
                    echo "socket error:".socket_last_error().",error msg:".socket_strerror(socket_last_error()).PHP_EOL;
                    break;
                }
                echo "socket error:".socket_last_error().",error msg:".socket_strerror(socket_last_error()).PHP_EOL;
            }
            else{
                echo 'client write success,msg:['.$ori_msg.'],time:' . microtime(true).PHP_EOL;
            }
    //        break;
        }
    }
    @socket_shutdown($socket);
    socket_close($socket);
    
    /**
     * 生成php随机串
     * @param $length
     * @return string
     */
    function randomkeys($length){
        $output='';
        for ($a = 0; $a<$length; $a++) {
            $output .= chr(mt_rand(33, 126));
        }
        return $output;
    }
    ?>

    正确的代码是:(file: debug1_server.php)

    <?php
    /**
     * file: debug1_server.php
     * socket server
     * 基于php socket函数族
     * IO模型:同步阻塞
     * 粘包处理:固定长度
     * 连接数:1个socket连接
     *
     * 测试目标:模拟client crash时,server无法判断socket是否断开
     * 测试结果:kill杀掉client进程后,server进程socket_last_error()返回为0,无法判断socket是否关闭
     *
     * @author davidyanxw
     * @date 2018.04.27
     */
    
    set_time_limit(0);
    
    //创建服务端的socket套接流,net协议为IPv4,protocol协议为TCP
    $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    
    // reuse address
    socket_set_option($socket, SOL_SOCKET, SO_REUSEADDR, 1);
    
    /*绑定接收的套接流主机和端口,与客户端相对应*/
    if (socket_bind($socket, '127.0.0.1', 8801) == false) {
        echo 'server bind fail:' . socket_strerror(socket_last_error());
    }
    //监听套接流
    if (socket_listen($socket, 4) == false) {
        echo 'server listen fail:' . socket_strerror(socket_last_error());
    }
    
    $accept_resource = socket_accept($socket);
    if($accept_resource === false) {
        echo "accept connection failed".PHP_EOL;
        exit;
    }
    // 读写超时时间:0.8s
    socket_set_option($accept_resource, SOL_SOCKET, SO_RCVTIMEO, array("sec" => 0, "usec" => 800000));
    socket_set_option($accept_resource, SOL_SOCKET, SO_SNDTIMEO, array("sec" => 0, "usec" => 800000));
    
    // stream固定长度
    $len = 100;
    
    //让服务器不停获取客户端传过来的信息
    while (true) {
        /* 使用socket_recv */
        $len_read = socket_recv($accept_resource, $string_read, $len, 0);
        
        if ($len_read === false) {
            // no data
            echo "no data".PHP_EOL;
            continue;
        }
        elseif($len_read === 0 ) {
            // socket closed
            echo "socket error:" . socket_last_error() . ",error msg:" . socket_strerror(socket_last_error()) . PHP_EOL;
            break;
        }
        else {
            $string = trim($string_read);
            echo 'server receive success,msg:['.$string.'],time:' . microtime(true) . PHP_EOL;
        }
    } ;
    // 先shutdown,后close
    @socket_shutdown($accept_resource);
    socket_close($accept_resource);
    
    @socket_shutdown($socket);
    socket_close($socket);
    
    /**
     * 生成php随机串
     * @param $length
     * @return string
     */
    function randomkeys($length){
        $output='';
        for ($a = 0; $a<$length; $a++) {
            $output .= chr(mt_rand(33, 126));
        }
        return $output;
    }
    ?>

    https://www.jianshu.com/p/fb20c931920f

  • 相关阅读:
    Daliy Algorithm (dp,模拟)-- day 80
    Daliy Algorithm (dp,堆)-- day 79
    Mybatis一级缓存和二级缓存 Redis缓存
    简单排序
    java一个大接口拆用多线程方式拆分成多个小接口
    集群环境下Shiro Session的管理
    递归和快速排序
    分布式定时任务
    Redis集群架构
    IO流
  • 原文地址:https://www.cnblogs.com/7qin/p/13510494.html
Copyright © 2011-2022 走看看