寫了websocket個聊天室,然後終於弄懂了php的socket

字號+ 編輯: 种花家 修訂: 听风就是我 來源: 网络转载 2023-09-10 我要說兩句(0)

經朋友推薦去一家手遊公司面試,原諒我不厚道的只是好奇手遊公司到底是啥樣的才去的。工作雖然沒找到,但是跟他們的技術總監套近乎聊了幾乎一晚上,受益良多,知道了運營多個手遊大體需要的技術,當然還是厚道的不爆料了。面試中被問及socket和多線程編程,對這兩個知識點完全是空白,回來果斷開始研究。還是那句話,不懂裁縫的廚師不是好司機。何況這兩個知識也在前耑開發的範疇之内。

對我來說最快的學習途徑是實踐,所以找兩個東西來練手。一個是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

核心代碼:

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

代碼如下:

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: (.*)\r\n/", $data, $match)) {
        $key = base64_encode(sha1($match[1] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
        $upgrade = "HTTP/1.1 101 Switching Protocol\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "Sec-WebSocket-Accept: " . $key . "\r\n\r\n"; // 必須以兩個回車結尾
        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握手。然後等待它的新消息即可。


閲完此文,您的感想如何?
  • 有用

    0

  • 沒用

    0

  • 開心

    1

  • 憤怒

    0

  • 可憐

    1

1.如文章侵犯了您的版權,請發郵件通知本站,該文章將在24小時内刪除;
2.本站標注原創的文章,轉發時煩請注明來源;
3.交流群: 2702237 13835667

相關課文
  • mac開發接入微信公衆號接口返回報錯 cURL error 56: SSLRead() return error -9806

  • PHP的換行符是什麽

  • pecl安裝程序時報錯Array and string offset access syntax with curly braces is no longer supported

  • 由於商家傳入的H5交易參數有誤,該筆交易暫時無法完成,請聯繫商家解決

我要說說
網上賓友點評