Рубрики
Uncategorized

php+websocket для чатов

1. Конфигурация Open socket build, в противном случае фатальная ошибка: при вызове неопределенной функции socket_create() будет сообщено об ошибке 1. Откройте конфигурационный файл php.ini и выполните поиск.dll, чтобы удалить предыдущую точку с запятой ‘;’. Перезапустите службу после внесения изменений. Примечание: Если у вас много символов UTF-8…

1. Конфигурация Open socket build, в противном случае фатальная ошибка: при вызове неопределенной функции socket_create() будет сообщено об ошибке 1. Откройте конфигурационный файл php.ini и выполните поиск.dll, чтобы удалить предыдущую точку с запятой ‘;’. Перезапустите службу после внесения изменений. Примечание: Если у вас много версий php, важно отметить, какую версию файла php.ini вы хотите изменить. Чтобы открыть сокет с помощью wamp, вам нужен apache и php.ini ниже php, тогда как phpstudy нужно изменить только один php.ini. 2. Проверьте, запущена ли сборка сокета phpinfo.php чтобы убедиться, что если поддержка сокетов => включена, запуск прошел успешно.

3. Установите cmd для запуска php-файла В разделе Мой компьютер – > Свойства -> Дополнительные системные настройки – > Дополнительно – > Переменные среды добавьте путь к php в ПУТЬ к переменной пользователя (обратите внимание, что версия та же) и добавьте путь в переменную среды, как указано выше.

4. Проверьте, успешно ли настроены сокет и php, Создайте новый файл с именем start.php в рамках проекта

 if(extension_loaded('sockets')){
      echo "1";
    }else{
      echo "0";
    }

Введите PHP d:\phpstudy\wwwstart.php в cmd, если вывод 1, конфигурация правильная, если вывод 0, конфигурация неправильная и нуждается в тщательной перенастройке

2. Процесс внедрения

Интерфейсная реализация относительно проста, сложна и фоновая. Его логика заключается в следующем: php в основном получает ключи шифрования и возвращает их для завершения создания сокета и операции рукопожатия

Процессы на стороне обслуживания: 1. Приостановите процесс сокета сокета и дождитесь подключения 2. Пересеките массив сокетов после подключения сокета 3. Если рукопожатия нет, выполняется операция рукопожатия, и если рукопожатие уже было выполнено, полученные данные будут проанализированы и записаны в буфер для вывода.

3. Интерфейсный код



    
    
    
    
    websocket chat room
    
    
    
    
    
    
    
    
    
Number of people online:0 people
Tips: PHP of websocket chat room

4. Внутренний код

initSocket();
    }

    // Establish WebSocket Connect
    private function initSocket()
    {
        try {
            //Establish socket socket
            $this->_master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
            // Set up IP And port reuse,Reuse this port after restarting the server;
            socket_set_option($this->_master, SOL_SOCKET, SO_REUSEADDR, 1);
            //Binding Address and Port
            socket_bind($this->_master, $this->_ip, $this->_port);
            //listen Functions use an active socket interface to become a connected socket interface, which allows a process to accept requests from other processes and become a server process.stay TCP Server programming listen Function to make a process a server and specify that the corresponding socket becomes a passive connection,The stored request is unknown socket Number.
            socket_listen($this->_master, self::LISTEN_SOCKET_NUM);
        } catch (Exception $e) {
            $this->debug(array("code: " . $e->getCode() . ", message: " . $e->getMessage()));
        }
        //take socket Save to socket Pool (put socket in array) places current user first by default
        $this->_socketPool[0] = array('resource' => $this->_master);
        $pid = getmypid();
        $this->debug(array("server: {$this->_master} started,pid: {$pid}"));
    }

    // Suspend process traverses the socket array to receive, process, and send data
    public function run()
    {
        // Dead cycle until socket To break off
        while (true) {
            try {
                
                $write = $except = NULL;
                // Remove from Array resource column
                $sockets = array_column($this->_socketPool, 'resource');

               /* 
                $sockets Is an array that holds file descriptors.
                $write Is listening for client write data, incoming NULL is not concerned about write changes
                $except Is the element in $sockets to swear, passing in null is listening on all
                The last parameter is the time-out, where 0 immediately ends n>1 and ends at most N seconds later. If a connection has a new dynamic, null is returned in advance. If a connection has a new dynamic, null is returned
                */
                // Receive socket numbers and listen for their status as new messages to or client connections/When disconnected, socket_select Function will return and continue executing
                $read_num = socket_select($sockets, $write, $except, NULL);
                if (false === $read_num) {
                    $this->debug(array('socket_select_error', $err_code = socket_last_error(), socket_strerror($err_code)));
                    return;
                }

                // Traversing socket arrays
                foreach ($sockets as $socket) {

                    // If there is a new connection in
                    if ($socket == $this->_master) {

                        // Receive one socket Connect
                        $client = socket_accept($this->_master);
                        if ($client === false) {
                            $this->debug(['socket_accept_error', $err_code = socket_last_error(), socket_strerror($err_code)]);
                            continue;
                        }
                        //Connect and place socket In pool
                        $this->connection($client);
                    } else {

                        //Receive Connected socket Data, returned from socket Number of bytes received in.
                        // The first parameter is the socket resource, the second parameter is the variable that stores the received data, and the third parameter is the length of the received data.
                        $bytes = @socket_recv($socket, $buffer, 2048, 0);

                        // If the number of bytes received is 0
                        if ($bytes == 0) {

                            // Disconnect
                            $recv_msg = $this->disconnection($socket);
                        } else {

                            // Determine if there is a handshake, no handshake for handshake, handshake for disposal
                            if ($this->_socketPool[(int)$socket]['handShake'] == false) {
                                // Handshake
                                $this->handShake($socket, $buffer);
                                continue;
                            } else {
                                // Resolve data from client
                                $recv_msg = $this->parse($buffer);
                            }
                        }

                        // echo "
";
                        // Business Processing, Assembling Return Client Data Formats
                        $msg = $this->doEvents($socket, $recv_msg);
                        // print_r($msg);

                        socket_getpeername ( $socket  , $address ,$port );
                        $this->debug(array(
                            'send_success',
                            json_encode($recv_msg),
                            $address,
                            $port
                        ));
                        // Write data returned by the server to the socket
                        $this->broadcast($msg);
                    }
                }
            } catch (Exception $e) {
                $this->debug(array("code: " . $e->getCode() . ", message: " . $e->getMessage()));
            }

        }

    }

    /**
     * data broadcasting
     * @param $data
     */
    private function broadcast($data)
    {
        foreach ($this->_socketPool as $socket) {
            if ($socket['resource'] == $this->_master) {
                continue;
            }
            // Write Socket
            socket_write($socket['resource'], $data, strlen($data));
        }
    }

    /**
     * Business processing, where you can manipulate the database and return client data; assemble data in different formats depending on different types
     * @param $socket
     * @param $recv_msg Data from client
     * @return string
     */
    private function doEvents($socket, $recv_msg)
    {
        $msg_type = $recv_msg['type'];
        $msg_content = $recv_msg['msg'];
        $response = [];
        //echo "
";
        switch ($msg_type) {
            case 'login':
            // Landing Online Information
                $this->_socketPool[(int)$socket]['userInfo'] = array("username" => $msg_content, 'headerimg' => $recv_msg['headerimg'], "login_time" => date("h:i"));
                // Get the latest name record
                $user_list = array_column($this->_socketPool, 'userInfo');
                $response['type'] = 'login';
                $response['msg'] = $msg_content;
                $response['user_list'] = $user_list;
                //print_r($response);

                break;
            case 'logout':
            // Exit Information
                $user_list = array_column($this->_socketPool, 'userInfo');
                $response['type'] = 'logout';
                $response['user_list'] = $user_list;
                $response['msg'] = $msg_content;
                //print_r($response);
                break;
            case 'user':
            // Messages sent
                $userInfo = $this->_socketPool[(int)$socket]['userInfo'];
                $response['type'] = 'user';
                $response['from'] = $userInfo['username'];
                $response['msg'] = $msg_content;
                $response['headerimg'] = $userInfo['headerimg'];
                //print_r($response);
                break;
        }

        return $this->frame(json_encode($response));
    }

    /**
     * socket Handshake
     * @param $socket
     * @param $buffer  Data received by client
     * @return bool
     */
    public function handShake($socket, $buffer)
    {
        $acceptKey = $this->encry($buffer);
        $upgrade = "HTTP/1.1 101 Switching Protocols\r\n" .
            "Upgrade: websocket\r\n" .
            "Connection: Upgrade\r\n" .
            "Sec-WebSocket-Accept: " . $acceptKey . "\r\n\r\n";

        // take socket Write Buffer
        socket_write($socket, $upgrade, strlen($upgrade));
        // Marker handshake succeeded, next time data is accepted in data frame format
        $this->_socketPool[(int)$socket]['handShake'] = true;
        socket_getpeername ( $socket  , $address ,$port );
        $this->debug(array(
            'hand_shake_success',
            $socket,
            $address,
            $port
        ));
        //Send a message to inform the client that the handshake was successful
        $msg = array('type' => 'handShake', 'msg' => 'Successful handshake');
        $msg = $this->frame(json_encode($msg));
        socket_write($socket, $msg, strlen($msg));
        return true;
    }

    /**
     * Frame Data Encapsulation
     * @param $msg
     * @return string
     */
    private function frame($msg)
    {
        $frame = [];
        $frame[0] = '81';
        $len = strlen($msg);
        if ($len < 126) {
            $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } else if ($len < 65025) {
            $s = dechex($len);
            $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
        } else {
            $s = dechex($len);
            $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
        }
        $data = '';
        $l = strlen($msg);
        for ($i = 0; $i < $l; $i++) {
            $data .= dechex(ord($msg{$i}));
        }
        $frame[2] = $data;
        $data = implode('', $frame);
        return pack("H*", $data);
    }

    /**
     * Resolve client data
     * @param $buffer
     * @return mixed
     */
    private function parse($buffer)
    {
        $decoded = '';
        $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 json_decode($decoded, true);
    }

    //extract Sec-WebSocket-Key Information and Encryption
    private function encry($req)
    {
        $key = null;
        if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $req, $match)) {
            $key = $match[1];
        }
        // encryption
        return base64_encode(sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
    }

    /**
     * Connect socket
     * @param $client
     */
    public function connection($client)
    {
        socket_getpeername ( $client  , $address ,$port );
        $info = array(
            'resource' => $client,
            'userInfo' => '',
            'handShake' => false,
            'ip' => $address,
            'port' => $port,
        );
        $this->_socketPool[(int)$client] = $info;
        $this->debug(array_merge(['socket_connect'], $info));
    }

    /**
     * Disconnect
     * @param $socket
     * @return array
     */
    public function disconnection($socket)
    {
        $recv_msg = array(
            'type' => 'logout',
            'msg' => @$this->_socketPool[(int)$socket]['userInfo']['username'],
        );
        unset($this->_socketPool[(int)$socket]);
        return $recv_msg;
    }

    /**
     * Journal
     * @param array $info
     */
    private function debug(array $info)
    {
        $time = date('Y-m-d H:i:s');
        array_unshift($info, $time);
        $info = array_map('json_encode', $info);
        file_put_contents(self::LOG_PATH . 'websocket_debug.log', implode(' | ', $info) . "\r\n", FILE_APPEND);
    }
}

// Extra-class instantiation
$sk = new socketServer();
// Function
$sk -> run();

5. Запуск php

Создайте файл start.bat, запустите php или запустите PHP, введя команды в cmd

php ./socketServer.php
pause

Результаты следующие:

Примечание: start.bat должен быть запущен постоянно. Если он выключен, это означает, что сокет также выключен и не может быть передан. Все, что нужно запустить.bat работает все время

Оригинал: "https://programmer.help/blogs/php-websocket-for-chat-rooms.html"