php 套接字_使用PHP Web套接字构建实时得分的小部件

php 套接字

The introduction of web sockets makes it possible for web applications to handle near real-time data without resorting to "hacks" such as long-polling.

Web套接字的引入使Web应用程序可以处理近乎实时的数据,而无需诉诸长轮询等“ hacks”。

One example of an application requiring up-to-the-minute data is sports scores. Even now, many websites which display this information use Flash applications, since Actionscript provides the facility to communicate over socket-based connections. However, web sockets allow us to replicate this functionality using only HTML and Javascript. That's what we're going to build in this tutorial, along with a lightweight "server" in PHP.

需要最新数据的应用程序的一个示例是运动成绩。 即使现在,由于Actionscript提供了通过基于套接字的连接进行通信的功能,因此许多显示此信息的网站都使用Flash应用程序。 但是,Web套接字允许我们仅使用HTML和Javascript复制此功能。 这就是我们将在本教程中构建的内容,以及PHP中的轻量级“服务器”。

image

安装与设定 (Installation and Setup)

We'll base the example around the Ratchet library, which provides a PHP implementation of web sockets.

我们将基于Ratchet库创建示例,该提供Web套接字PHP实现。

Create the following composer.json file, which both installs this dependency and sets up an autoloader for the code we're going to write:

创建以下composer.json文件,该文件将安装此依赖项并为我们将要编写的代码设置自动加载器:

{
    "require": {
        "cboden/Ratchet": "0.2.*"
    },
    "autoload": {
        "psr-0": {
            "LiveScores": "src"
        }
    }    
}

Now set up the directory structure:

现在设置目录结构:

[root]
    bin
    src
        LiveScores
    public
        assets
            css
                vendor
            js
                vendor
    vendor

You'll probably want to clone the repository, which contains a number of CSS / JS / image assets, as well as all the code from this tutorial. If you'd like to build it from scratch alongside this article, all you need to do is copy the public/assets/*/vendor folders from the cloned/downloaded package into your own at the appropriate locations.

您可能需要克隆存储库 ,其中包含许多CSS / JS /图像资产以及本教程中的所有代码。 如果您想与本文一起从头开始构建它,那么您要做的就是将克隆/下载包中的public/assets/*/vendor文件夹复制到您自己的适当位置。

Naturally, don't forget to run php composer.phar update, preceded by curl -sS https://getcomposer.org/installer | php if you don't have composer installed.

自然,不要忘记运行php composer.phar update ,其后是curl -sS https://getcomposer.org/installer | php curl -sS https://getcomposer.org/installer | php如果您没有安装作曲家。

We'll start by building a class which resides on the server and acts as a sort of message broker – accepting connections and sending messages. Later, we'll also use it to maintain information about the games in progress. This is a skeleton implementation, to show how a generic message broker might operate:

我们将首先构建一个驻留在服务器上的类,该类充当一种消息代理-接受连接并发送消息。 稍后,我们还将使用它来维护有关正在进行的游戏的信息。 这是一个基本的实现,以显示通用消息代理可能如何运行:

// src/LiveScores/Scores.php

<?php namespace LiveScores;

use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Scores implements MessageComponentInterface {

    private $clients;    

    public function __construct() 
    {    
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) 
    {
        $this->clients->attach($conn);
    }

    public function onMessage(ConnectionInterface $from, $msg) 
    {            
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // The sender is not the receiver, send to each client connected
                $client->send($msg);
            }
        }
    }

    public function onClose(ConnectionInterface $conn) 
    {
        $this->clients->detach($conn);
    }

    public function onError(ConnectionInterface $conn, \Exception $e) 
    {     
        $conn->close();
    }


}

Important points to note;

重要注意事项;

  • The class needs to implement MessageComponentInterface in order to act as a "message broker"

    该类需要实现MessageComponentInterface才能充当“消息代理”

  • We're maintaining a list of all clients that have connected to the server as a collection

    我们将所有已连接到服务器的客户端的列表作为一个集合进行维护
  • When a client connects, the onOpen event gets fired, where we add the client to our collection

    当客户端连接时,会触发onOpen事件,将客户端添加到集合中

  • When a client disconnects (onClose), we do the opposite

    当客户端断开连接( onClose )时,我们执行相反的操作

  • The interface also requires us to implement a simple error handler (onError)

    该接口还要求我们实现一个简单的错误处理程序( onError )

Next up, we need to create a server daemon to instantiate our new class and start listening to connections. Create the following file:

接下来,我们需要创建一个服务器守护程序来实例化我们的新类并开始侦听连接。 创建以下文件:

// bin/server.php

<?php
use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use LiveScores\Scores;

require dirname(__DIR__) . '/vendor/autoload.php';

$server = IoServer::factory(
    new WsServer(
        new Scores()
    )
    , 8080
);

$server->run();

This should all be pretty self-explanatory; WsServer is an implementation of the more generic IoServer which communicates using web sockets, and we'll set it listening on port 8080. You're free to choose a different port, of course – provided it's not blocked by your firewall – but 8080 is usually a pretty safe bet.

这一切都是不言自明的; WsServer是更通用的IoServer的实现,该IoServer使用Web套接字进行通信,我们将其设置为侦听端口8080。当然,您可以自由选择其他端口-只要它不受防火墙的阻止-8080通常是一个非常安全的选择。

维持状态 (Maintaining State)

We'll let the server keep track of the current state of the games; no need to commit it to storage, we'll simply keep it in memory for optimum performance. Each time an event takes place in one of the games, we'll update the scores on the server and then broadcast the event to all listening clients.

我们将让服务器跟踪游戏的当前状态; 无需将其提交到存储,我们只需将其保存在内存中即可获得最佳性能。 每次在其中一个游戏中发生事件时,我们都会在服务器上更新比分,然后将该事件广播给所有正在监听的客户端。

First, though, we need to generate the fixtures (i.e. the list of games). For simplicity we'll do it at random, and just keep this set of fixtures active for the duration of the daemon's execution.

但是,首先,我们需要生成固定装置(即游戏列表)。 为简单起见,我们将随机执行此操作,并仅在守护程序执行期间使这组固定装置保持活动状态。

// src/LiveScores/Fixtures.php
<?php namespace LiveScores;

class Fixtures {

    public static function random()
    {
        $teams = array("Arsenal", "Aston Villa", "Cardiff", "Chelsea", "Crystal Palace", "Everton", "Fulham", "Hull", "Liverpool", "Man City", "Man Utd", "Newcastle", "Norwich", "Southampton", "Stoke", "Sunderland", "Swansea", "Tottenham", "West Brom", "West Ham");

        shuffle($teams);

        for ($i = 0; $i <= count($teams); $i++) {
            $id = uniqid();
            $games[$id] = array(
                'id' => $id,
                'home' => array(
                    'team' => array_pop($teams),
                    'score' => 0,
                ),
                'away' => array(
                    'team' => array_pop($teams),
                    'score' => 0,
                ),
            );
        }

        return $games;
    }


}

Note that we're assigning each game a unique identifier, which we'll use later to indicate which game an event has taken place in. Going back to our Scores class:

请注意,我们为每个游戏分配了一个唯一的标识符,我们将在以后使用该标识符来指示事件发生在哪个游戏中。回到我们的Scores类:

// src/LiveScores/Scores.php

public function __construct() {

    // Create a collection of clients
    $this->clients = new \SplObjectStorage;

    $this->games = Fixtures::random();
}

Because a client could call upon our widget at any stage during a game, it's important that they get up-to-the-minute information. One way to do this is simply to "reply" to a new connection request by sending the current state of the games, then rendering the list of games and their scores client-side.

因为客户可以在游戏过程中的任何阶段调用我们的小部件,所以获取最新信息很重要。 一种方法是简单地通过发送游戏的当前状态来“回复”新的连接请求,然后在客户端呈现游戏列表及其得分。

Here's the onOpen implementation, which does just that:

这是onOpen实现,它就是这样做的:

// src/LiveScores/Scores.php

public function onOpen(ConnectionInterface $conn) {
    // Store the new connection to send messages to later
    $this->clients->attach($conn);

    // New connection, send it the current set of matches
    $conn->send(json_encode(array('type' => 'init', 'games' => $this->games)));

    echo "New connection! ({$conn->resourceId})\n";
}

Note that the message we're sending is actually a JSON object, with the type of event set as a property. There's no requirement to send messages using JSON – you can send any format you wish – but doing it in this way allows us to send different types of structured messages.

请注意,我们发送的消息实际上是一个JSON对象,事件类型设置为属性。 不需要使用JSON发送消息-您可以发送所需的任何格式-但通过这种方式,我们可以发送不同类型的结构化消息。

HTML (The HTML)

Because we're going to load in the current scores over a web socket and render them using Javascript, the HTML for the page to start with is very simple:

因为我们要通过网络套接字加载当前分数并使用Javascript渲染它们,所以页面HTML非常简单:

<div id="scoreboard">

    <table>

    </table>

</div>

Once rendered, a row in the score-table will look like this:

渲染后,得分表中的一行将如下所示:

<tr data-game-id="SOME-IDENTIFIER">
    <td class="team home">
        <h3>HOME TEAM NAME</h3>
    </td>
    <td class="score home">
        <div id="counter-0-home"></div>
    </td>
    <td class="divider">
        <p>:</p>
    </td>
    <td class="score away">
        <div id="counter-0-away"></div>
    </td>
    <td class="team away">
        <h3>AWAY TEAM NAME</h3>
    </td>
</tr>

The counter-*-* elements are placeholders for a JS plugin we're going to use to render a fancy score widget later.

counter-*-*元素是JS插件的占位符,我们将在稍后使用JS插件渲染花式得分小部件。

JavaScript (The JavaScript)

Now let's start building the JS. The first thing to do is open a web socket:

现在让我们开始构建JS。 首先要做的是打开一个Web套接字:

var conn = new WebSocket('ws://localhost:8080');

You may need to substitute the hostname and / or the port number, depending on where your "server" is running.

您可能需要替换主机名和/或端口号,具体取决于“服务器”的运行位置。

Next, attach an event handler to the connection, which fires whenever a message is received:

接下来,将事件处理程序附加到连接,该事件处理程序将在收到消息时触发:

conn.onmessage = function(e) {

The message itself is provided as a data property to the event e. Because we're sending messages in JSON format, we'll need to parse it first:

消息本身作为事件edata属性提供。 因为我们以JSON格式发送消息,所以我们需要先对其进行解析:

var message = $.parseJSON(e.data);

Now we can examine the type, and call the appropriate function:

现在我们可以检查type ,并调用适当的函数:

switch (message.type) {
    case 'init':
        setupScoreboard(message);
        break;
    case 'goal':
        goal(message);
        break;
}

The setupScoreboard function is pretty straightforward:

setupScoreboard函数非常简单:

function setupScoreboard(message) {

    // Create a global reference to the list of games
    games = message.games;

    var template = '<tr data-game-id="{{ game.id }}"><td class="team home"><h3>{{game.home.team}}</h3></td><td class="score home"><div id="counter-{{game.id}}-home" class="flip-counter"></div></td><td class="divider"><p>:</p></td><td class="score away"><div id="counter-{{game.id}}-away" class="flip-counter"></div></td><td class="team away"><h3>{{game.away.team}}</h3></td></tr>';

    $.each(games, function(id){        
        var game = games[id];                
        $('#scoreboard table').append(Mustache.render(template, {game:game} ));        
        game.counter_home = new flipCounter("counter-"+id+"-home", {value: game.home.score, auto: false});
        game.counter_away = new flipCounter("counter-"+id+"-away", {value: game.away.score, auto: false});
    });

}

In this function we're simply iterating through the array of games, using Mustache to render a new row to be added to the scoreboard table, and instantiating a couple of animated counters for each one. The games array is going to store the current state of the games client-side, and includes references to those counters so we can update them as required.

在此功能中,我们只是简单地遍历游戏数组,使用Mustache渲染要添加到记分牌表的新行,并为每个实例实例化几个动画计数器。 games数组将存储游戏客户端的当前状态,并包括对这些计数器的引用,因此我们可以根据需要对其进行更新。

Next up, the goal function. The message we receive over the web socket to indicate a goal will be a JSON object with the following structure:

接下来, goal功能。 我们通过Web套接字收到的指示目标的消息将是具有以下结构的JSON对象:

{
    type: 'goal',
    game: 'UNIQUE-ID',
    team: 'home'
}

The game property contains the unique identifier, and team is either "home" or "away". Using these bits of information, we can update the relevant score in the games array, find the appropriate counter object and increment it.

game属性包含唯一标识符,并且team为“主场”或“离开”。 使用这些信息,我们可以更新games数组中的相关分数,找到合适的计数器对象并将其递增。

function goal(message) {    
    games[message.game][message.team]['score']++;
    var counter = games[message.game]['counter_'+message.team];
    counter.incrementTo(games[message.game][message.team]['score']);
}

All that remains is some way of indicating that a goal has been scored. In order to keep things simple, we'll just add that to the client; clicking a team's name will indicate that they've scored. In practice you'd have a separate application or page, but the principle is the same. We'll simply add a click handler as follows, which sends a simple JSON message over the web socket:

剩下的只是某种方式来表明已经进球。 为了简化操作,我们将其添加到客户端。 点击球队的名称将表明他们已经得分。 实际上,您将有一个单独的应用程序或页面,但是原理是相同的。 我们将简单地添加一个单击处理程序,如下所示,该处理程序将通过Web套接字发送一条简单的JSON消息:

$(function () {

    $(document).on('click', '.team h3', function(e){
        var game = $(this).parent().parent().attr('data-game-id');        
        var team = ($(this).parent().hasClass('home')) ? 'home' : 'away';
        conn.send(JSON.stringify({ type: 'goal', team: team, game: game }));
    });

});

The server "listens" for these messages, and if it receives word of a goal it updates its record. All messages received are immediately re-broadcast to all connected clients.

服务器“监听”这些消息,并且如果收到目标消息,它将更新其记录。 收到的所有消息将立即重新广播到所有连接的客户端。

// src/LiveScores/Scores.php
public function onMessage(ConnectionInterface $from, $msg) {

    foreach ($this->clients as $client) {        
        $client->send($msg);            
    }

    $message = json_decode($msg);

    switch ($message->type) {
        case 'goal':
            $this->games[$message->game][$message->team]['score']++;
            break;
    }

}

Finally, to get it up-and-running, you'll need to launch the server from the command-line:

最后,要使其启动并运行,您需要从命令行启动服务器:

php bin/server.php

That's it – try opening a couple of windows side-by-side, and clicking a team name to indicate a goal. You should see the scoreboard update straight away!

就是这样–尝试并排打开几个窗口,然后单击团队名称以指示目标。 您应该立即看到计分板更新!

结论 (Conclusion)

In this article, I've demonstrated a simple HTML and Javascript "live scores" widget using web sockets. It has its limitations; normally you'd expect to see the goalscorer and the time each goal was scored, as well as additional information such as bookings and sending-offs. However, because we're using a JSON object to represent an event, such features should be relatively straightforward to add. A live demo of this tutorial is available.

在本文中,我演示了使用Web套接字的简单HTML和Javascript“实时得分”窗口小部件。 它有其局限性。 通常,您希望看到进球者和每个进球的得分时间,以及诸如预订和送出等其他信息。 但是,由于我们使用JSON对象表示事件,因此添加这些功能应该相对简单。 提供了本教程的现场演示

(Note: The Javascript and styles for the counters are thanks to Chris Nanney, and come from this post.)

(注意:计数器的Javascript和样式要感谢Chris Nanney,并且来自于此帖子 。)

翻译自: https://www.sitepoint.com/building-live-score-widget-using-php-web-sockets/

php 套接字

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值