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

  • 相关阅读:
    标签的讲解
    属性分类
    LeetCode 003. 无重复字符的最长子串 双指针
    Leetcode 136. 只出现一次的数字 异或性质
    Leetcode 231. 2的幂 数学
    LeetCode 21. 合并两个有序链表
    象棋博弈资源
    acwing 343. 排序 topsort floyd 传播闭包
    Leetcode 945 使数组唯一的最小增量 贪心
    Leetcode 785 判断二分图 BFS 二分染色
  • 原文地址:https://www.cnblogs.com/7qin/p/13510494.html
Copyright © 2011-2022 走看看