zoukankan      html  css  js  c++  java
  • websocket webworker

    对我来说最快的学习途径是实践,所以找两个东西来练手。一个是websocket一个是webwoker,今天先说第一个。

    要理解socket就要先理解http和tcp的区别,简单说就是一个是短链,一个是长链,一个是去服务器拉数据,一个是服务器可以主动推数据。

    而socket就是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。-来自网络。

    那么如何用php+js做到服务器推呢?

    客户端

    客户端非常简单,利用现代浏览器的WebSocket API,这里介绍的很详细:http://msdn.microsoft.com/zh-cn/library/ie/hh673567

    核心代码:

    JAVASCRIPT
    1
    2
    3
    4
    5
    var wsServer = 'ws://127.0.0.1:8080'; 
    var ws = new WebSocket(wsServer);
    ws.onmessage = function (evt) { 
        do sth
    };

    前两行会向指定服务器发送一个握手请求,如果服务器返回合法的http头,则握手成功,之后可通过监听onmessage事件来处理服务器发来的消息。还有很多其他事件可监听,见前面的url。

    服务器

    思路

    难点是服务器,没有了apache和nginx这些http服务器在前面顶着,只用php该怎么写?

    这里有个教程讲的很深入 http://blog.csdn.net/shagoo/article/details/6396089

    写之前捋一捋思路:

    1 监听:首先要挂起一个进程来监听来自客户端的请求 
    2 握手:对于第一次合法的请求,发送合法的header回去 
    3 保持连接:有新消息到了就广播出去。直到客户端断开 
    4 接受另一个请求,重复2和3

    代码如下:

    PHP
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    public function start_server() {
        $this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
        //允许使用本地地址
        socket_set_option($this->socket, SOL_SOCKET, SO_REUSEADDR, TRUE); 
        socket_bind($this->socket, $this->host, $this->port);
        //最多10个人连接,超过的客户端连接会返回WSAECONNREFUSED错误
        socket_listen($this->socket, $this->maxuser); 
        while(TRUE) {
            $this->cycle = $this->accept;
            $this->cycle[] = $this->socket;
            //阻塞用,有新连接时才会结束
            socket_select($this->cycle, $write, $except, null);
            foreach ($this->cycle as $k => $v) {
                if($v === $this->socket) {
                    if (($accept = socket_accept($v)) < 0) {
                        continue;
                    }
                    //如果请求来自监听端口那个套接字,则创建一个新的套接字用于通信
                    $this->add_accept($accept);
                    continue;
                }
                $index = array_search($v, $this->accept);
                if ($index === NULL) {
                    continue;
                }
                if (!@socket_recv($v, $data, 1024, 0) || !$data) {//没消息的socket就跳过
                    $this->close($v);
                    continue;
                }
                if (!$this->isHand[$index]) {
                    $this->upgrade($v, $data, $index);
                    if(!empty($this->function['add'])) {
                        call_user_func_array($this->function['add'], array($this));
                    }
                    continue;
                }
                $data = $this->decode($data);
                if(!empty($this->function['send'])) {
                    call_user_func_array($this->function['send'], array($data, $index, $this));
                }
            }
            sleep(1);
        }
    }
    //增加一个初次连接的用户
    private function add_accept($accept) {
        $this->accept[] = $accept;
        $index = array_keys($this->accept);
        $index = end($index);
        $this->isHand[$index] = FALSE;
    }
    //关闭一个连接
    private function close($accept) {
        $index = array_search($accept, $this->accept);
        socket_close($accept);
        unset($this->accept[$index]);
        unset($this->isHand[$index]);
        if(!empty($this->function['close'])) {
            call_user_func_array($this->function['close'], array($this));
        }
    }
    //响应升级协议
    private function upgrade($accept, $data, $index) {
        if (preg_match("/Sec-WebSocket-Key: (.*)
    /",$data,$match)) {
            $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
            $upgrade  = "HTTP/1.1 101 Switching Protocol
    " .
                    "Upgrade: websocket
    " .
                    "Connection: Upgrade
    " .
                    "Sec-WebSocket-Accept: " . $key . "
    
    ";  //必须以两个回车结尾
            socket_write($accept, $upgrade, strlen($upgrade));
            $this->isHand[$index] = TRUE;
        }
    }

    关键地方有那么几个,一是while(true)挂起进程,不然执行一次后进程就退出了。二是socket_select和socket_accept函数的使用。三是客户端第一次请求时握手。

    socket_select

    这个函数是同时接受多个连接的关键,我的理解它是为了阻塞程序继续往下执行。

    socket_select ($sockets, $write = NULL, $except = NULL, NULL);

    $sockets可以理解为一个数组,这个数组中存放的是文件描述符。当它有变化(就是有新消息到或者有客户端连接/断开)时,socket_select函数才会返回,继续往下执行。 
    $write是监听是否有客户端写数据,传入NULL是不关心是否有写变化。 
    $except是$sockets里面要被排除的元素,传入NULL是”监听”全部。 
    最后一个参数是超时时间 
    如果为0:则立即结束 
    如果为n>1: 则最多在n秒后结束,如遇某一个连接有新动态,则提前返回 
    如果为null:如遇某一个连接有新动态,则返回

    socket_accept

    此函数接受唯一参数,即前面socket_create创建的socket文件(句柄)。返回一个新的资源,或者FALSE。本函数将会通知socket_listen(),将会传入一个连接的socket资源。一旦成功建立socket连接,将会返回一个新的socket资源,用于通信。如果有多个socket在队列中,那么将会先处理第一个。关键就是这里:如果没有socket连接,那么本函数将会等待,直到有新socket进来。

    如果前面不用socket_select在没有socket的时候阻塞住程序,那么就卡在这里永远无法结束了。

    后面的流程就很清晰了,当有一个新的客户端请求到达,用socket_accept创建一个资源,并加入到$this->accept连接池里面。并将它的标示isHand设为false,那么下次循环(因为$this->cycle[] = $this->socket;$this->cycle有变化,所以socket_select会返回)的时候就会执行upgrade握手。然后等待它的新消息即可。

    程序经调试可以成功运行,php5.3+websocket13。有兴趣的同学可以下载:文件地址

  • 相关阅读:
    BIND_MISMATCH导致过多VERSION COUNT的问题
    Using dbms_shared_pool.purge to remove a single task from the library cache
    SQL Server 2012 新的分页函数 OFFSET & FETCH NEXT
    How to delete expired archive log files using rman?
    Oracle利用external table 查看trace文件
    全栈开发经验
    ASP.NET Core教程:使用Supervisor做ASP.NET Core应用程序守护进程
    ASP.NET Core教程:ASP.NET Core程序部署到Linux
    ASP.NET Core教程:ASP.NET Core 程序部署到Windows系统
    C#:窗体传值
  • 原文地址:https://www.cnblogs.com/liangxiaofeng/p/5206370.html
Copyright © 2011-2022 走看看