반응형

채팅같은거 만들어보려고 하는데 nodejs나 파이썬등도 기웃거려 봤는데 영 맘에 안들어서 php의 웹소켓을 이용해서 채팅을 만들어 보려고 한다. Ratchet이란 놈을 이용해서 만들어보자.

 

우선 설치를 한다. 나는 우분투 22에 composer를 이용해 설치했다.

 

일단 현재 운영하고 있는 사이트가 있었기때문에 해당 경로로 이동해서 그곳에 설치했다.

 

$cd /var/www/html

$composer init

$composer require cboden/ratchet

 

이렇게 하면 기본적인 파일들이 설치가 된다. 그리고 필요한 파일을 만들어준다. 경로는 같다.

 

$vi chat_server.php

<?php

require 'vendor/autoload.php';

use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
use MyApp\Chat;

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

$server->run();
?>

 

그리고 폴더를 하나 만든다. 같은 경로에서 만든다.

 

$mkdir MyApp

$cd MyApp

$vi Chat.php

<?php

namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class Chat implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
$this->subscriptions = [];
        $this->users = [];
$this->usernames = [];
$this->channelcnt = [];
    }

    public function onOpen(ConnectionInterface $conn)
    {
        $this->clients->attach($conn);
        $this->users[$conn->resourceId] = $conn;
echo "New connection! ({$conn->resourceId})\n";
    }

public function onMessage(ConnectionInterface $conn, $msg)
    {
$numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $conn->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');

        $data = json_decode($msg);
        switch ($data->command) {
            case "subscribe":
                $this->subscriptions[$conn->resourceId] = $data->channel;
$this->usernames[$conn->resourceId] = $data->uname;
$this->channelcnt = array_count_values($this->subscriptions);
$target = $this->subscriptions[$conn->resourceId];
foreach ($this->subscriptions as $id=>$channel) {
                        if ($channel == $target && $id != $conn->resourceId) {
$sendmessage='{"uname" : "SERVER","msg" : "'.$data->uname.'님이 대화방에 들어오셨습니다. 현재 인원은 '.$this->channelcnt[$data->channel].'명 입니다."}';
                            $this->users[$id]->send($sendmessage);
                        }
if ($channel == $target && $id == $conn->resourceId) {
$sendmessage='{"uname" : "SERVER","msg" : "'.$channel.' 대화방에 입장하셨습니다. 현재 인원은 '.$this->channelcnt[$data->channel].'명 입니다."}';
                            $this->users[$id]->send($sendmessage);
                        }
                    }
                break;
            case "message":
$sendmessage='{"uname" : "'.$data->uname.'","msg" : "'.$data->message.'"}';
                if (isset($this->subscriptions[$conn->resourceId])) {
                    $target = $this->subscriptions[$conn->resourceId];
                    foreach ($this->subscriptions as $id=>$channel) {
if ($channel == $target) {//같은 채널에 모두에게 보냄
                            $this->users[$id]->send($sendmessage);
                        }
                    }
                }
        }
    }

public function onClose(ConnectionInterface $conn)
    {
if (isset($this->subscriptions[$conn->resourceId])) {
                    $target = $this->subscriptions[$conn->resourceId];
                    foreach ($this->subscriptions as $id=>$channel) {
                        if ($channel == $target && $id != $conn->resourceId) { //나 말고 다른 상대에게만 보냄
$sendmessage='{"uname" : "SERVER","msg" : "'.$this->usernames[$conn->resourceId].'님이 대화방에서 나가셨습니다. 현재 인원은 '.($this->channelcnt[$target]-1).'명 입니다."}';
                            $this->users[$id]->send($sendmessage);
                        }
                    }
         }

        $this->clients->detach($conn);
        unset($this->users[$conn->resourceId]);
        unset($this->subscriptions[$conn->resourceId]);
echo "Connection {$conn->resourceId} has disconnected\n";
    }

    public function onError(ConnectionInterface $conn, \Exception $e)
    {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }


}
?>

 

이렇게 하면 기본적인 서버용 파일들은 모두 작업을 완료했다. 사용자가 접속할 사용자 파일을 만들어보자. 경로는 원하는 곳에 만들면 된다.

 

$vi chat.php

<?php
$uname="거북이";
?>
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>WebSocket Chat</title>
  <style>
    /* Add your CSS styles here */
  </style>
</head>
<body>
  <div id="chat"></div>
  <input type="text" id="uname" value="<?php echo $uname;?>" placeholder="이름을 입력하세요.">
  <input type="text" id="message">
  <button id="send">Send</button>

  <script>
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
    const chat = document.getElementById('chat');
const uname = document.getElementById('uname');
    const messageInput = document.getElementById('message');
    const sendButton = document.getElementById('send');

    const socket = new WebSocket('ws://localhost:8080');

function subscribe(channel) {
socket.send(JSON.stringify({command: "subscribe", channel: channel, uname:'<?php echo $uname;?>'}));
}

function sendMessage(uname,msg) {
socket.send(JSON.stringify({command: "message", uname:uname, message: msg}));
}

socket.onopen = function(e) {
  console.log("Connection established!");
  var channel = getParameterByName('channel');
  subscribe(channel);
};

    socket.onmessage = (event) => {
  var rmsg=JSON.parse(event.data);
  var li = document.createElement('li');
  li.innerHTML = rmsg.uname + ' : ' + rmsg.msg;
      chat.appendChild(li);
    };

    sendButton.addEventListener('click', () => {
const uname = document.getElementById('uname').value?document.getElementById('uname').value:'익명';
const message = messageInput.value;
      sendMessage(uname,message);
      messageInput.value = '';
    });
  </script>
</body>
</html>

 

websocket주소와 포트는 각자에게 맞추면 된다.

 

이렇게 하고 브라우저를 여러개 띄우자. 그리고 본인의 사이트 접속 주소에 접속을 하면 된다. 단 이때 channel명을 같이 보내야 한다.

 

http://localhost/chat.php?channel=test1

 

이런식이다. 

 

아 맞다. 이렇게 하기전에 소켓서버를 먼저 실행해야 한다.

 

$php chat_server.php

 

이렇게 해주고 사이트에 접속해 보자. 그런데 이렇게 실행하면 오류가 나는 경우가 있다. 이때는 composer.json 파일을 수정해 주어야한다.

 

$vi composer.json

{
    "autoload": {
        "psr-4": {
            "MyApp\\": "MyApp/"
        }
    },
    "require": {
        "cboden/ratchet": "^0.4.4"
    }
}

 

json파일이 이렇게 똑같이 생기진 않았을테지만 

 

"autoload": {
        "psr-4": {
            "MyApp\\": "MyApp/"
        }
    }

이부분은 이렇게 맞춰 주어야 한다. MyApp을 불러올때 어디서 불러올지를 정해주어야 한다. 임의로 추가했기때문에 수정해야한다. 그리고 한번더 작업이 남았다.

 

$composer update

 

이렇게 한번 해주고 다시 소켓서버를 실행해보자

 

$php chat_server.php

 

이번엔 잘 될것이다.

 

설치만 하려고 했는데 너무 많이 했다. 쩝

 

아참 아래 사이트를 참고했다.

 

https://reintech.io/blog/guide-websockets-realtime-communication-php-ratchet

 

반응형

+ Recent posts