V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
flowfire
V2EX  ›  PHP

在 php 中创建并打开了 websocket 服务端,然而客户端提示 403 错误是怎么回事

  •  
  •   flowfire · 2015-05-07 14:52:21 +08:00 · 4298 次点击
    这是一个创建于 3282 天前的主题,其中的信息可能已经有所发展或是发生改变。

    找了一些websocket的资料,但是无论是使用他们提供的代码还是我自己写的代码,在客户端连接的时候都显示403错误、、
    我用的是 apache2.4 x64 for windows + php
    代码应该是没错的。。。
    然后在浏览器中打开 server.php
    在另一个窗口中使用 var ws = new WebSocket("ws://path:port")
    返回的错误为
    Error during WebSocket handshake: Unexpected response code: 403
    求解。。。

    9 条回复    2015-05-08 18:33:56 +08:00
    feiyuanqiu
        1
    feiyuanqiu  
       2015-05-08 04:34:05 +08:00
    websocket 需要先握手建立连接之后才能通信,你握手那里是怎么处理的呢
    server.php 只是一个验证性的 demo 吧? 如果不长,可以发出来看看
    ericls
        2
    ericls  
       2015-05-08 04:57:26 +08:00
    看看服务器那边的日志呢?
    flowfire
        3
    flowfire  
    OP
       2015-05-08 12:30:49 +08:00
    @feiyuanqiu
    代码。。。。我一开始就是嫌太长了所以没发。。。。

    <?php
    set_time_limit(0);
    class ws{
    public $sock;
    public $socks;
    public $users;
    function send($socket,$msg){
    if($socket==="all"){
    foreach ($this->user as $key => $value) {
    socket_write($this->user[$key]['client'],$msg,strlen($msg));
    }
    }
    socket_write($socket,$msg,strlen($msg));
    }
    function handshake($socket,$data){
    $secretkey = substr($buffer,strpos($data,'Sec-WebSocket-Key:')+18);
    $skey = trim(substr($secretkey,0,strpos($secretkey,"\r\n")));
    $newskey = base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));//生成返回的握手包key,后面的字符串是固定的,不知道谁规定的。。。
    $httpheader = "HTTP/1.1 101 Switching Protocols\r\n";
    $httpheader .= "Upgrade: websocket\r\n";
    $httpheader .= "Connection: Upgrade\r\n";
    $httpheader .= "Sec-WebSocket-Accept: ".$newskey."\r\n\r\n";
    send($socket,$httpheader);
    $key = search($socket);
    $this->users[($key-1)]["new"]=false;
    return true;
    }
    function search($socket){
    foreach ($this->socks as $key => $value) {
    if($socket===$value)
    return $key;
    }
    }
    function close($socket){
    socket_close($socket);
    $key = search($socket);
    unset($this->socks[$key]);
    if($key!==0)
    unset($this->user[($key-1)]);
    return true;
    }
    function __construct($ip,$port){
    $this->sock = socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
    socket_bind($this->sock,$ip,$port);
    socket_listen($this->sock);
    $this->socks[] = $this->sock;
    while(1){
    $sockscache = $this->socks;
    socket_select($sockscache,$write = null,$expect =null,null);
    foreach($sockscache as $sockcache){
    if($sockcache === $this->sock){
    $client = socket_accept($this->sock);
    $this->socks[] = $client;
    $this->users[] = array("client"=>$client,"name"=>"","new"=>true);
    }else{
    $length = socket_recv($sockcache,$data,2048,0);
    $key = search($sockcache);
    if($length<7){
    $name = $this->users[($key-1)]["name"];
    send($sockcache,"$name 已经退出。");
    close($sockcache);
    }else{
    if($this->user[($key-1)]["new"]){
    handshake($sockcache,$data);
    }else{
    //信息处理
    echo $data;
    die();
    }
    }
    }
    }
    }
    }
    }
    $websocket = new ws("127.0.0.1","1077");
    feiyuanqiu
        4
    feiyuanqiu  
       2015-05-08 16:44:13 +08:00   ❤️ 1
    嗯调了一下,怎么说呢,这个代码里面有很多小问题,比如:
    1、类方法调用没有用 this
    2、$this->user,$this->users 混乱
    3、handshake 方法里面的变量 $buffer 没有声明赋值,实际上它应该是参数 $data
    4、handshake 方法里面的 $skey 变量在后面的调用中写错了
    ...

    后面的我都没记录了,都是小问题,改了就能运行了:



    附上代码
    服务端:
    <?php
    error_reporting(E_ERROR);
    class ws
    {
    public $sock;
    public $socks;
    public $users;

    function send($socket, $msg)
    {
    if ($socket === "all") {
    foreach ($this->users as $key => $value) {
    socket_write($this->users[ $key ]['client'], $msg, strlen($msg));
    }
    }
    socket_write($socket, $msg, strlen($msg));
    }

    function handshake($socket, $data)
    {
    $buffer = $data;
    $secretkey = substr($buffer, strpos($data, 'Sec-WebSocket-Key:') + 18);
    $skey = trim(substr($secretkey, 0, strpos($secretkey, "\r\n")));
    //生成返回的握手包key,后面的字符串是固定的,不知道谁规定的。。。
    $newskey = base64_encode(sha1($skey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", TRUE));
    $httpheader = "HTTP/1.1 101 Switching Protocols\r\n";
    $httpheader .= "Upgrade: websocket\r\n";
    $httpheader .= "Connection: Upgrade\r\n";
    $httpheader .= "Sec-WebSocket-Accept: " . $newskey . "\r\n\r\n";
    $this->send($socket, $httpheader);
    $key = $this->search($socket);
    $this->users[$key-1]["new"] = FALSE;

    return TRUE;
    }

    function search($socket)
    {
    return array_search($socket, $this->socks);
    }

    function close($socket)
    {
    socket_close($socket);
    $key = $this->search($socket);
    unset($this->socks[ $key ]);
    if ($key !== 0) {
    unset($this->users[$key-1]);
    }

    return TRUE;
    }

    function decode($buffer) {
    $decoded = null;
    $len = ord($buffer[1]) & 127;
    if ($len === 126) {
    $masks = substr($buffer, 4, 4);
    $data = substr($buffer, 8);
    } else if ($len === 127) {
    $masks = substr($buffer, 10, 4);
    $data = substr($buffer, 14);
    } else {
    $masks = substr($buffer, 2, 4);
    $data = substr($buffer, 6);
    }
    for ($index = 0; $index < strlen($data); $index++) {
    $decoded .= $data[$index] ^ $masks[$index % 4];
    }
    return $decoded;
    }

    function __construct($ip, $port)
    {
    f('start...');
    $this->sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
    f('create socket...');
    socket_set_option($this->sock, SOL_SOCKET, SO_REUSEADDR, 1);
    f('set option...');
    socket_bind($this->sock, $ip, $port);
    f('bind socket...');
    socket_listen($this->sock);
    f('listen socket...');
    $this->socks[] = $this->sock;
    while (1) {
    $write = NULL;
    $expect = NULL;
    $sockscache = $this->socks;
    if (socket_select($sockscache, $write, $expect, 0) === false) {
    die("socket_select() failed, reason: " . socket_strerror(socket_last_error()) . "\n");
    }

    foreach ($sockscache as $sockcache) {
    if ($sockcache === $this->sock) {
    if (($client = socket_accept($this->sock)) !== false) {
    f('accept socket...');
    $this->socks[] = $client;
    $this->users[] = array("client" => $client, "name" => "", "new" => TRUE);
    }
    } else {
    $length = socket_recv($sockcache, $data, 2048, 0);
    f('receive socket...');
    $key = $this->search($sockcache);
    if ($length < 7) {
    $name = $this->users[$key-1]["name"];
    $this->send($sockcache, "$name 已经退出。");
    $this->close($sockcache);
    f('close socket...');
    } else {
    if ($this->users[$key-1]["new"]) {
    $this->handshake($sockcache, $data);
    f('handshake...');
    } else {
    //信息处理
    $data = $this->decode($data);
    echo $data;
    die();
    }
    }
    }
    }
    usleep(500);
    }
    }
    }

    function f($msg = '')
    {
    static $start = false;
    if (!$start) {
    ob_start();
    $start = true;
    }
    echo date('Y-m-d H:i:s') . ":{$msg}\n";
    ob_flush();
    }

    $websocket = new ws('127.0.0.1', '10077');


    客户端:
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Document</title>
    </head>
    <body>
    <script type="text/javascript">
    //<![CDATA[
    var ws = new WebSocket('ws://localhost:10077/phpstorm/test2.php');

    ws.onopen = function (event) {
    var data = event.data;
    console.log('onopen:', data, event);
    };
    ws.onerror = function (event) {
    var data = event.data;
    console.log('onerror:', data, event);
    };
    ws.onclose = function (event) {
    var data = event.data;
    console.log('onclose:', data, event);
    };
    ws.onmessage = function (event) {
    var data = event.data;
    console.log('onmessage:', data, event);
    };
    //]]>
    </script>
    </body>
    </html>
    flowfire
        5
    flowfire  
    OP
       2015-05-08 18:03:05 +08:00
    @feiyuanqiu 我自己再去查一遍好了。。。。
    我这两天才刚学websocket。。。。有些太长但是不涉及socket函数的代码基本上都是复制别人的- -
    其他的。。。大概是手抖写错了吧。。。
    flowfire
        6
    flowfire  
    OP
       2015-05-08 18:17:13 +08:00
    @feiyuanqiu 但是我用上别人提供的代码也是403错误啊。。。他说已经测试成功了
    flowfire
        7
    flowfire  
    OP
       2015-05-08 18:17:40 +08:00
    @ericls 我去找找。。。。服务器日志太长。。。不怎么想看啊。。。
    flowfire
        8
    flowfire  
    OP
       2015-05-08 18:32:10 +08:00
    @feiyuanqiu 我大概找到原因了。。。。我果然是傻得。。。请求ws连接不能带path。。。。直接ip:portj就好了
    flowfire
        9
    flowfire  
    OP
       2015-05-08 18:33:56 +08:00
    @flowfire 不对。。。是我端口位置写错了。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1066 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 23:51 · PVG 07:51 · LAX 16:51 · JFK 19:51
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.