꿈꾸는 시스템 디자이너

Flutter 강좌 - [Networking] 웹소켓(WebScoket) 사용법 | StreamBuilder 본문

Development/Flutter

Flutter 강좌 - [Networking] 웹소켓(WebScoket) 사용법 | StreamBuilder

독행소년 2019. 7. 10. 00:29

Flutter 강좌 목록 : https://here4you.tistory.com/120

 

 

지난 강좌에서는 Http 프로토콜을 이용해서 온라인상의 데이터를 수신하고 처리하는 방법에 대해서 알아봤다. 이번 강좌에서는 웹소켓(WebSocket)의 사용법에 대해서 알아본다.

우선 Http와 웹소켓의 차이에 대해서 생각해 보자.

서버와 통신을 하는 앱을 개발하고자 할 때 사용할 수 있는 통신 방식은 Http 통신 혹은 소켓 통신이다. 거의 대부분의 경우 이 두 종류 중 하나를 이용하거나 필요에 따라 두 종류를 동시에 사용하기도 한다.

Http 통신 방식은 연결 지향성이 없는 통신 방식이다. 클라이언트는 서버로 요청 메시지를 전달하고, 서버로부터 해당 응답 메시지를 수신하면 서버-클라이언트간 통신은 종료된다. 통신 관련 제어 포인트가 적기 때문에 개발이 쉽다. 단점은 모든 Http 통신은 클라이언에서 시작하고 클라이언트로부터 응답을 수신하는 동기식 방식만 가능하다. 다시 말해 서버가 필요에 의해 클라이언트에게 요청 메시지를 전송할 수 없다.

소켓 통신 방식은 연결 지향성 통신 방식이다. 서버-클라이언트간 연결이 맺어지면 어느 누구든 상대방에게 메시지를 전송할 수 있다. 단점은 Http 통신 방식이 비해 상대적으로 제어 포인트가 많기 때문에 개발이 어렵다는 점이다.

통신 대상의 차이점에 대해서도 생각해 보자

Http 통신은 웹서버와 통신을 하고, 소켓 통신은 소켓 서버와 통신을 한다. 만약 Http 통신과 소켓 통신을 동시에 이용하는 앱을 개발하고자 한다면 웹서버와 소켓서버를 모두 개발해야하는 부담이 발생한다. 두 서버간의 협업도 큰 골치거리가 된다. 

하지만 웹소켓 기술의 출현으로 이제는 웹서버에서도 소켓통신이 가능해졌다. 이번 강좌에서는 이 웹소켓의 사용법에 대해서 알아본다.

 

1. 패키지 추가

웹소켓 관련 패키지를 사용하기 위해 pubspec.yaml의 dependencies 항목을 다음과 같이 수정한다.

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  web_socket_channel: ^1.0.13

 

2. 라이브러리 import

웹소켓 관련 패키지를 사용하기 위해 다음과 같이 라이브러리들을 import 한다.

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

 

3. 웹소켓서버 연결 및 채널 생성

웹소켓서버와 연결을 맺고 그에 대한 응답으로 채널을 획득하는 방법은 다음과 같다.

  // 웹소켓 채널을 생성
  final WebSocketChannel channel =
      // 웹 서버에 접속 시도
      IOWebSocketChannel.connect('ws://echo.websocket.org');

 

4. 웹소켓서버로 메시지 전송

웹소켓서버로 메시지를 전송하기 위해서는 생성한 채널을 이용해서 sink.add 함수를 이용한다.

channel.sink.add(_controller.text);

 

5. 웹소켓서버로부터 메시지 수신 처리

// Stream을 처리하기 위한 StreamBuilder 추가
            StreamBuilder(
              // 채널의 스트림을 stream 항목에 설정. widget을 통해 MyHomePage의 필드에 접근 가능
              stream: widget.channel.stream,
              // 채널 stream에 변화가 발생하면 빌더 호출
              builder: (context, snapshot) {
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 24.0),
                  // 수신 데이터가 존재할 경우 해당 데이터를 텍스트로 출력
                  child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
                );
              },
            )

웹서버로부터 수신된 응답메시지는 channel.stream으로 전달되고 stream을 통해 접근이 가능하다. stream을 처리하기 위해선 StreamBuilder 클래스를 이용한다. StreamBuilder에 stream 항목에 channel.stream을 설정하면 서버로부터 데이터가 수신될 때마다 builder가 호출된다.

 

6. 연결 종료

웹소켓서버와의 연결을 종료하기 위해서는 채널을 이용해서 sink.close 함수를 이용한다.

channel.sink.close();

 

전체 소스코드는 다음과 같다.

import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/web_socket_channel.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final title = 'WebSocket Demo';

    return MaterialApp(
      title: title,
      home: MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final String title;

  // 웹소켓 채널을 생성
  final WebSocketChannel channel =
      // 웹 서버에 접속 시도
      IOWebSocketChannel.connect('ws://echo.websocket.org');

  MyHomePage({Key key, @required this.title}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // 상태 변화가 일어나는 필드
  TextEditingController _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      // 앱바에 타이틀 텍스트 설정. widget을 통해 MyHomePage의 필드에 접근 가능
      appBar: AppBar(title: Text(widget.title)),
      body: Padding(
        padding: const EdgeInsets.all(20.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Form(
              child: TextFormField(
                // 컨트롤러 항목에 _controller 설정
                controller: _controller,
                decoration: InputDecoration(labelText: 'Send a message'),
              ),
            ),
            // Stream을 처리하기 위한 StreamBuilder 추가
            StreamBuilder(
              // 채널의 스트림을 stream 항목에 설정. widget을 통해 MyHomePage의 필드에 접근 가능
              stream: widget.channel.stream,
              // 채널 stream에 변화가 발생하면 빌더 호출
              builder: (context, snapshot) {
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 24.0),
                  // 수신 데이터가 존재할 경우 해당 데이터를 텍스트로 출력
                  child: Text(snapshot.hasData ? '${snapshot.data}' : ''),
                );
              },
            )
          ],
        ),
      ),
      // 플로팅 버튼이 눌리면 _sendMessage 함수 호출
      floatingActionButton: FloatingActionButton(
          onPressed: _sendMessage,
          tooltip: 'Send message',
          child: Icon(Icons.send)),
    );
  }

  // 플로팅 버튼이 눌리면 호출
  void _sendMessage() {
    // TextFormField에 입력된 텍스트가 존재하면
    if (_controller.text.isNotEmpty) {
      // 해당 텍스트를 서버로 전송. widget을 통해 MyHomePage의 필드에 접근 가능
      widget.channel.sink.add(_controller.text);
    }
  }

  // 상태 클래스가 종료될 때 호출
  @override
  void dispose() {
    // 채널을 닫음
    widget.channel.sink.close();
    super.dispose();
  }
}

 

소스코드를 살펴보면,

서버로부터 응답 메시지가 수신될 때마다 페이지를 다시 구성해야 하므로 StatefulWidget와 State를 상속하는 클래스를 통해 앱을 구성해야 한다. MyHomePage 클래스에서 웹소켓 서버와 연결을 맺고 채널을 필드에 저장한다.

서버로 데이터를 전송하기 위해서 Form 위젯에 TextFormField를 등록하였고 이 폼필드에 TextEditingController를 등록하여 플로팅버튼이 눌릴 때마다 폼필드의 입력된 텍스트를 웹소켓 서버로 전송한다. 또한 StreamBuilder 클래스를 통해서 서버로부터 데이터가 수신될 때마다 화면에 새로운 텍스트를 출력한다.

웹소켓통신 방식은 Http 통신과 달리 연결지향적이므로 페이지가 종료될 때 채널을 종료해야 한다. 이를 위해 dispose 메소드를 재정의한다.

또한 StatefulWidget 위젯으로 앱을 구성할 때 State 클래스내에서 widget을 이용하면 StatefulWidget을 참조할 수 있다. 상기 코드의 line 42, 58, 84, 92가 그에 해당한다.

 

앱의 실행화면은 다음과 같다.

 

본 강좌는 Flutter 공식 사이트의 문서를 참고하여 작성되었습니다.

https://flutter.dev/docs/cookbook/networking/web-sockets

 

Work with WebSockets

In addition to normal HTTP requests, you can connect to servers usingWebSockets. WebSockets allow for two-way communication with a serverwithout polling.In this example, connect to a [test server provided bywebsocket.org](http://www.websocket.org/echo.html

flutter.dev

 


Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com

Comments