자바스크립트에서 공식적으로 제공되는 Websocket 객체는 클라이언트 와 서버 간 데이터를 주고 받을 수 있도록 연결 스트림을 만들어 주는 기술로 일반 소켓통신은 서버간 통신였다면 웹 소켓은 클라이언트 to 서버간 통신으로 볼 수 있다. 그렇다고 해서 클라이언트만 이용가능한건 아니며 서버간 통신도 가능하지만 서버간 통신이라면 굳이 복잡한 웹 소켓을 보단 그냥 일반 소켓이 더 좋지 않을까 생각한다.
이번편에서 소개하는 자바스크립트 Websocket 연결 방법은 앞서 작성한 웹 소켓 서버 기본설치 방법을 기반으로 진행되기 때문에 이전 포스팅을 보지 못한분들은 아래 포스팅을 먼저 확인하도록 하자.
<script>
var eventScroll;
var socket;
// 웹소켓연결함수
function WebSocketConnect(){
socket = new WebSocket("ws://chat.redinfo.co.kr:4343");
socket.onopen = function(e){ view('접속이 연결되었습니다.');}
socket.onmessage = function(e){ view(e.data); };
socket.onclose = function(e){
view('연결종료');
if(confirm("연결이 종료되었습니다.\n다시 접속하시겠습니까?")){
WebSocketConnect();
}
};
socket.onerror = function(e){
view('오류로 인한 소켓연결 종료');
console.log(e.message);
};
}
// 메시지 발송
function send(){
// 메시지를 보내는 구간
var $msgEle = document.querySelector('.msg');
var msg = $msgEle.value;
if( msg == ''){ return false; }
socket.send(msg);
$msgEle.value = '';
}
// 메시징 뷰
function view(msg){
if( typeof msg == 'undefined'){ return false; }
var scrollBottom = 0;
var $viewEle = document.querySelector('.view');
// 메시지 출력 구간
$viewEle.innerHTML += '<div class="line">'+msg+'</div>';
// 스크롤 이벤트가 없을 경우에만 실행
if( eventScroll != true){
// 스크롤 하단 위치 계산
scrollBottom = $viewEle.scrollHeight - $viewEle.clientHeight;
$viewEle.scrollTop = scrollBottom;
}
}
// 스크롤 이벤트 등록 -- 스크롤이 맨 하단에 위치하지 않을 경우 자동 하단 이동을 방지 하기 위핸 처리
window.onload = function(){
WebSocketConnect(); // 웹 소켓 연결
document.querySelector('.view').addEventListener('scroll', function() {
var $viewEle = document.querySelector('.view');
var scrollBottom = $viewEle.scrollHeight - $viewEle.clientHeight;
if( $viewEle.scrollTop != scrollBottom){ eventScroll = true; }
else{ eventScroll = false; }
});
}
</script>
<style>
#chat-box{width:100%; max-width:300px; border:solid 1px #f00; margin:0 auto;}
#chat-box .view{ height:350px; overflow-y:auto; font-size:12px; margin:2px 0;}
#chat-box .view .line{ margin:4px 5px; }
#chat-box .input{ border-top:solid 1px #f00;}
#chat-box .input input{ width:100%; border:none; padding:10px 5px; outline: none;}
#chat-box .btn{ border-top:solid 1px #f00;}
#chat-box .btn input{ width:100%; border:none; background:chocolate; color:#fff; padding:10px 0; cursor: pointer; }
</style>
<div id="chat-box">
<div class="view"></div>
<div class="input">
<input type="text" class="msg" placeholder="메시지를 입력해 주세요." onkeyup="if(event.keyCode == 13){send();}">
</div>
<div class="btn">
<input type="button" value="메시지전송" onclick="send();">
</div>
</div>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
| 실행결과
위의 소스에서 중요한 부분은 아래와 같다.
var socket = new WebSocket("ws://chat.redinfo.co.kr:4343");
이메일 lcy@redinfo.co.kr
위의 소스는 웹 소켓 서버로 접속하는 구간이며 앞선 포스팅에서 웹 소켓 서버가 정상적으로 실행되고 있다면 오류 없이 연결 완료 메시지를 받을 수 있다. 하지만 앞선 포스팅에서는 웹 소켓 서버 에서 별도의 응답 처리를 하지 않았기때문에 아무런 기능도 사용할 수 없다. 따라서 아래와 같이 웹 소켓 서버 프로그램에 응답처리를 추가해주어야 한다.
웹 소켓 서버 응답 처리는 앞선 포스팅에서 생성했던 Chat.php 파일에서 처리한다. 경로는 아래와 같다.
/library/Ratchet/src/Chat.php
Chat.php 파일에는 아래와 같이 작성해 보자. (설명은 주석을 참고하도록 하자)
<?php # 파일경로: /library/Ratchet/src/Chat.php
namespace MyApp;
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class Chat implements MessageComponentInterface{
protected $clients;
// 생성자
public function __construct() {
// 내장 SQL객체스토리지 연결 (접속한 클라이언트의 정보가 이곳에 계속 담긴다.)
$this->clients = new \SplObjectStorage();
}
// 유저 접속 시
public function onOpen(ConnectionInterface $conn)
{
// 접속한 사용자 커넥션 추가
$this->clients->attach($conn);
// 접속한 클라이언트 총 수
$clientCount = count($this->clients);
// 환영 메시지 전송
$conn->send('환영합니다 (고유번호: '.$conn->resourceId.', '.number_format($clientCount).'명 접속중)');
// 현재 접속한 클라이언트에게 상대방 입장 메시지를 전달 (접속한 사용자는 제외)
foreach ($this->clients as $client) {
// 접속한 사용자는 제외
if( $client != $conn){
$resMsg = '';
$client->send('상대방 입장 (고유번호: '.$conn->resourceId.', '.number_format($clientCount).'명 접속중)');
}
}
}
// 응답전문: 메세지 수신
public function onMessage(ConnectionInterface $from, $msg)
{
// 현재 접속한 클라이언트에게 상대방 입장 메시지를 전달 (상대방과 나를 분리, $form의 경우 메시지 전달한 사람의 정보다.)
foreach ($this->clients as $client) {
$resMsg = '';
if( $from == $client){ $resMsg = '[나('.$from->resourceId.')] '; }
else{ $resMsg = '[상대방('.$from->resourceId.')] '; }
$resMsg .= $msg;
$client->send($resMsg);
}
}
// 응답전문: 사용자 웹 소켓 종료 시
public function onClose(ConnectionInterface $conn)
{
// 연결이 종료되면 해당 사용자의 연결을 내장 SQL객체스토리지에서 제외한다.
$this->clients->detach($conn);
// 클라이언트 총 개수
$clientCount = count($this->clients);
// 현재 접속한 클라이언트에게 상대방 퇴장 메시지를 전달 (퇴장한 사용자는 제외)
foreach ($this->clients as $client) {
if( $client != $conn){
$resMsg = '';
$client->send('상대방 퇴장 (고유번호: '.$conn->resourceId.', '.number_format($clientCount).'명 접속중)');
}
}
}
// 응답전문: 사용자 웹 소켓 오류 발생 시
public function onError(ConnectionInterface $conn, \Exception $e)
{
// 해당 커넥션을 끊는다.
$conn->close();
}
}
위와 같이 작성한 후 서버에 접속하여 아래의 명령어를 통해 RatchetRun.php 파일을 실행해 보자
# php RatchetRun.php
그다음 웹 소켓 클라이언트가 있는 웹 url로 접속하면 간단한 채팅을 이용할 수 있다. 이처럼 자바스크립트 Websocket 을 이용한 간단한 채팅 프로그램에 대해 알아보았는데 여기까지 구현한 내용물로는 실제 서비스 하기는 어렵다.
그이유중 하나가 본 예제는 1시간이면 구현되는 간단한 예제로 만든 소스이고 실 서비스시에는 보안도 중요하고 데이터를 가공하여 주고 받아야 하기때문에 위의 형태로는 웹 소켓이 어떻게 연결되고 어떤 방식으로 받는지에 대해서만 확인이 가능하다.
웹 소켓 보안같은 경우 서버 처리 보안도 중요하지만 기본적으로 ws 프로토콜이 아닌 wss 프로토콜을 사용해야한다. wss 프로토콜의 경우 기본 보안서버 기반 프로토콜이며 이는 SSL 인증서를 통해 연결이 가능하다.
다행히도 우리가 설치한 PHP의 Ratchet 라이브러리 에서는 SSL 인증서만 있다면 이를 연결할 수 있는 기능이 모두 지원되기 때문에 큰 걱정은 할 필요가 없다.
추가로 메시지 가공의 경우 간단한 예제를 위해 포스팅에 게시된 소스상에서는 입력된 원문으로 주고 받았지만 실제 운영상에서 채팅 프로그램을 만들기 위해서는 입력된 원문이 아닌 json string 으로 가공하여 요청/응답을 받아야 한다.
이러한 과정들이 담긴 웹 소켓 채팅 포스팅은 앞으로 계속 작성해 나갈 예정이니 궁금한 사항이나 문의 사항은 lcy@redinfo.co.kr 로 문의바란다.