[C#] MVC Framework에서 WebSocket을 사용하는 방법(SignalR)


Development note/C#  2019. 11. 29. 09:00

안녕하세요. 명월입니다.


이 글은 C#의 MVC Framework에서 WebSocket을 사용하는 방법(SignalR)에 대한 글입니다.


예전에 Java에서 WebSocket을 사용하는 방법에 대해 소개한 적이 있습니다.

링크 - [Java] 웹 소켓 (WebSocket)


Java의 WebSocket보다는 SignalR이 살짝 복잡하네요.. 그러나 처음 설정만 잘하면 사용하기는 오히려 더 편할 수도 있습니다.


MVC Framework를 생성하는 것은 아래의 링크를 참조해 주세요.

링크 - [C# 강좌 - 67] MVC Framework 프로젝트 생성하기


그럼 MVC Framework를 하나 생성하겠습니다.

그리고 SignalR 라이브러리를 Nuget을 통해 다운로드 받습니다.

그리고 검색 창에 SignalR라고 검색을 하면 여러가지가 뜨는 데 우리는 Microsoft.AspNet.SignalR를 다운 받습니다.

주의점은 우리는 Net Core를 사용하는 것이 아니기 때문에 Microsoft.AspNetCore.SignalR가 아닌 Microsoft.AspNet.SignalR를 다운 받아야 합니다.

라이브러리 추가가 끝났으면 Startup파일에 app.MapSignalR()를 추가해야 합니다.

using System;
using System.Threading.Tasks;
using Microsoft.Owin;
using Owin;

[assembly: OwinStartup(typeof(WebSocketExample.Startup))]

namespace WebSocketExample
{
  public class Startup
  {
    public void Configuration(IAppBuilder app)
    {
      app.MapSignalR();
    }
  }
}

이번에는 웹 소켓을 사용할 Hub클래스를 만듭니다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.AspNet.SignalR;

namespace WebSocketExample
{
  public class TestHub : Hub
  {
    public void Send(string message)
    {
      // 전 유저에게 송신된다.
      Clients.All.Receive("echo - " + message);
    }
  }
}

저는 TestHub라는 이름으로 작성했습니다. 일단 위 예제는 제가 Send함수를 넣었습니다. 이 설정은 javascript을 작성할 때 설명하겠습니다.

여기까지가 SignalR 설정이 완료되었습니다.


이제는 Home/Index 화면에 웹 소켓을 사용할 화면을 만들겠습니다.

@{
  Layout = null;
}
<!DOCTYPE>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Insert title here</title>
</head>
<body>
  <input id="textMessage" type="text">
  <input id="sendmessage" value="Send" type="button">
  <br />
  <textarea id="messageTextArea" rows="10" cols="50"></textarea>
  <!-- jquery 링크 -->
  <script src="~/Scripts/jquery-3.3.1.min.js"></script>
  <!-- signalR 링크 -->
  <script src="~/Scripts/jquery.signalR-2.4.1.min.js"></script>
  <script src="~/signalr/hubs"></script>
  <script>
    $(document).ready(function () {
      // $.connection에서 뒤 testHub는 위에서 제가 작성한 TestHub 클래스란 의미입니다. C#에서 첫글자 대문자로 클래스를 작성하면 여기서는 첫글자를 소문자로 변환하는 것으로 확인이 됩니다.
      var $ws = $.connection.testHub;
      // client receive함수 설정은 Clients.All.Receive로 호출될 때 불리는 함수이다.
      $ws.client.receive = function (message) {
        document.getElementById("messageTextArea").value += "Recieve From Server => " + message + "\n";
      };
      // 이 함수는 웹 소켓이 개시되면 호출된다.
      $.connection.hub.start().done(function () {
        // sendmessage 클릭 이벤트 등록이다.
        $('#sendmessage').click(function () {
          var message = document.getElementById("textMessage");
          document.getElementById("messageTextArea").value += "Send to Server => " + message.value + "\n";
          // TestHub 클래스의 Send함수가 호출된다. 역시 함수명의 첫글자는 대문자이지만 여기서는 소문자로 치환되서 호출할 수 있다.
          $ws.server.send(message.value);
          message.value = "";
        });
      });
    });
  </script>
</body>
</html>

작성이 완료되었습니다. 이제 실행해 보겠습니다.

ASP.Net은 Jsp와 다르게 내부적으로 어떻게 돌아가는지 보이지가 않아서 에러가 발생했을 때 찾기가 참 힘드네요. 특히 저같은 경우는 자바스크립트에서 server.send와 connection.testHub가 어떻게 돌아가는지 이해가 잘 안되서 한참을 헤맸습니다.

첨부 파일 - WebSocketExample.zip


참조 - https://docs.microsoft.com/ko-kr/aspnet/core/fundamentals/websockets?view=aspnetcore-3.0

참조 - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.0

참조 - https://peterdaugaardrasmussen.com/2019/01/20/how-to-use-websockets-with-asp-net-core-with-an-example/

참조 - https://stackoverflow.com/questions/17395201/call-a-hub-method-from-a-controllers-action


여기까지 C#의 MVC Framework에서 WebSocket을 사용하는 방법(SignalR)에 대한 설명이었습니다.


-----추가 2020년 6월 3일-----

MVC에서 Controller에서 SingalR을 호출해서 웹 브라우저로 메시지를 보내는 방법에 대한 질문을 추가합니다.

Controller에서 TestHub를 사용하기 위해서는 Controller에 Context를 바인딩하거나 기타 여러가지의 방법이 있습니다. 그러나 가장 간단한 방법은 GlobalHost.ConnectionManager를 이용하는 게 가장 간단합니다.

using System.Web.Mvc;
using Microsoft.AspNet.SignalR;

namespace WebSocketExample.Controllers
{
  // Home 컨트럴러
  public class HomeController : Controller
  {
    // Index 페이지
    public ActionResult Index()
    {
      return View();
    }
    // Send 페이지
    public void Send(string message)
    {
      // TestHub를 취득
      var ws = GlobalHost.ConnectionManager.GetHubContext<TestHub>();
      // Receive함수로 에코를 보냅니다.
      ws.Clients.All.Receive("echo - " + message);
    }
  }
}

Home/Send를 호출해서 파라미터로 message를 넣었습니다.

Send 함수에서는 Websocket를 접속한 클라이언트에 echo 메시지를 보냅니다.


두번째, SignalR이 끊겼을 때 재 접속 하는 방법에 대한 질문입니다.

기본적으로 SingalR은 접속이 끊기면 재 접속 시도를 합니다만, 어느 정도 시간이 지나면 그대로 접속 끊김을 선언합니다.

@{
  Layout = null;
}
<!DOCTYPE>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Insert title here</title>
</head>
<body>
  <input id="textMessage" type="text">
  <input id="sendmessage" value="Send" type="button">
  <br />
  <textarea id="messageTextArea" rows="10" cols="50"></textarea>
  <script src="~/Scripts/jquery-3.3.1.min.js"></script>
  <script src="~/Scripts/jquery.signalR-2.4.1.min.js"></script>
  <script src="~/signalr/hubs"></script>
  <script>
    // 페이지 로드가 끝나면 함수 실행
    $(function () {
      // SignalR 커넥션 가져오기
      var $ws = $.connection;
      // Receive함수 설정
      $ws.testHub.client.receive = function (message) {
        // 메시지 박스에 출력한다.
        document.getElementById("messageTextArea").value += "Recieve From Server => " + message + "\n";
      };
      // 버튼을 누를 때 이벤트
      $('#sendmessage').click(function () {
        // 텍스트 박스 취득
        var message = document.getElementById("textMessage");
        // 메시지 박스에 출력
        document.getElementById("messageTextArea").value += "Send to Server => " + message.value + "\n";
        // 웹 소켓에 메시지 송신
        $ws.testHub.server.send(message.value);
        // 텍스트 박스 초기화
        message.value = "";
      });
      // 접속이 끊기면 호출
      $ws.hub.disconnected(function () {
        // 병렬 식으로 웹 소켓을 다시 실행
        setTimeout(function () {
          $ws.hub.start();
        });
      });
      // 웹 소켓 실행
      $ws.hub.start();
    });
  </script>
</body>
</html>

접속 될 때까지 계속 호출합니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.