티스토리 뷰

Flutter는 빠르고 효율적인 실시간 데이터 통신을 위해 WebSocket 기능을 지원합니다. 이 블로그에서는 Flutter에서 WebSocket을 설정하고 사용하는 방법을 단계별로 알아보고, 이를 활용한 실시간 애플리케이션 구축 전략을 제시하겠습니다.

참고. Communicate with WebSockets

WebSocket이란 무엇인가?

WebSocket은 클라이언트와 서버 간의 양방향 통신을 지원하는 프로토콜입니다. HTTP 기반의 전통적인 요청-응답 모델과 달리, WebSocket은 연결을 지속적으로 유지하며 데이터를 실시간으로 주고받을 수 있습니다. 따라서 채팅 애플리케이션, 실시간 알림 시스템, 주식 거래 앱과 같은 애플리케이션에서 많이 사용됩니다.

Flutter에서 WebSocket 사용 방법

1. WebSocket 연결 설정

Flutter에서 WebSocket을 사용하려면 dart:io 패키지에 포함된 WebSocket 클래스를 활용할 수 있습니다. 아래는 WebSocket을 설정하는 기본 코드입니다:

import 'dart:io';

void connectToWebSocket() async {
  final socket = await WebSocket.connect('ws://example.com/socket');
  print('WebSocket 연결 성공!');
  socket.listen(
    (data) => print('데이터 수신: $data'),
    onDone: () => print('WebSocket 연결 종료'),
    onError: (error) => print('에러 발생: $error'),
  );
}

2. 데이터 전송 및 수신

WebSocket 연결이 설정된 후, 데이터를 쉽게 송수신할 수 있습니다. 아래 코드는 WebSocket을 통해 데이터를 보내고 받는 방법을 보여줍니다:

void sendMessage(WebSocket socket, String message) {
  socket.add(message);
  print('보낸 메시지: $message');
}

3. 연결 종료 처리

WebSocket 연결이 더 이상 필요하지 않을 때 연결을 종료해야 합니다. 이는 리소스를 효율적으로 관리하는 데 중요합니다.

void closeWebSocket(WebSocket socket) {
  socket.close();
  print('WebSocket 연결이 종료되었습니다.');
}

Flutter WebSocket 구현 사례: 채팅 애플리케이션

Flutter를 사용해 간단한 채팅 애플리케이션을 개발한다고 가정해 봅시다.

주요 단계:

  1. WebSocket 연결 설정: 사용자가 앱에 접속하면 WebSocket 서버에 연결합니다.
  2. UI와 데이터 연동: 사용자가 입력한 메시지를 WebSocket을 통해 전송하고, 서버에서 받은 메시지를 UI에 업데이트합니다.
  3. 에러 및 연결 종료 처리: 서버와의 연결이 끊어지거나 오류가 발생했을 때 이를 사용자에게 알립니다.
import 'dart:io';
import 'package:flutter/material.dart';

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

class ChatApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ChatScreen(),
    );
  }
}

class ChatScreen extends StatefulWidget {
  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  late WebSocket _socket;
  List<String> _messages = [];

  @override
  void initState() {
    super.initState();
    _connectWebSocket();
  }

  void _connectWebSocket() async {
    _socket = await WebSocket.connect('ws://example.com/chat');
    _socket.listen((data) {
      setState(() {
        _messages.add(data);
      });
    });
  }

  void _sendMessage(String message) {
    _socket.add(message);
    setState(() {
      _messages.add('나: $message');
    });
  }

  @override
  void dispose() {
    _socket.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Flutter WebSocket Chat')),
      body: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: _messages.length,
              itemBuilder: (context, index) {
                return ListTile(title: Text(_messages[index]));
              },
            ),
          ),
          TextField(
            onSubmitted: _sendMessage,
            decoration: InputDecoration(labelText: '메시지를 입력하세요'),
          ),
        ],
      ),
    );
  }
}

WebSocket 사용 시 주의 사항

  1. 보안 강화: WebSocket 통신에는 ws://wss:// 두 가지 프로토콜이 있습니다. 보안을 위해 HTTPS에 해당하는 wss://를 사용하는 것이 좋습니다.
  2. 에러 핸들링: 서버 연결이 끊어지거나 네트워크 오류가 발생할 가능성을 고려하여 적절한 에러 처리 코드를 작성해야 합니다.
  3. 데이터 압축 및 최적화: 대량의 데이터를 송수신할 경우 압축을 고려해 네트워크 효율성을 높이세요.

WebSocket의 장점과 Flutter에서의 활용성

장점:

  • 실시간 통신: 데이터를 실시간으로 주고받아 빠른 반응성을 제공합니다.
  • 효율성: HTTP 요청보다 더 적은 오버헤드로 데이터를 처리합니다.
  • 다양한 애플리케이션: 채팅, 실시간 알림, 주식 데이터 스트리밍 등 광범위한 용도로 활용됩니다.

Flutter와의 궁합:

Flutter는 WebSocket과 같은 실시간 통신 기술을 활용하기에 적합한 프레임워크입니다. 높은 성능과 빠른 개발 속도를 제공하며, WebSocket과 결합하면 더욱 강력한 애플리케이션을 만들 수 있습니다.


결론

Flutter에서 WebSocket을 활용하면 실시간 데이터 통신을 필요로 하는 애플리케이션을 효과적으로 개발할 수 있습니다. 본문에서 설명한 기본 구현 방법과 코드를 바탕으로 여러분의 프로젝트에 WebSocket을 통합해 보세요. 실시간 채팅, 알림, 데이터 스트리밍 등 다양한 애플리케이션에서 그 가능성을 체감할 수 있을 것입니다.

 

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

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const title = 'WebSocket Demo';
    return const MaterialApp(
      title: title,
      home: MyHomePage(
        title: title,
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    super.key,
    required this.title,
  });

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final TextEditingController _controller = TextEditingController();
  final _channel = WebSocketChannel.connect(
    Uri.parse('wss://echo.websocket.events'),
  );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Form(
              child: TextFormField(
                controller: _controller,
                decoration: const InputDecoration(labelText: 'Send a message'),
              ),
            ),
            const SizedBox(height: 24),
            StreamBuilder(
              stream: _channel.stream,
              builder: (context, snapshot) {
                return Text(snapshot.hasData ? '${snapshot.data}' : '');
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _sendMessage,
        tooltip: 'Send message',
        child: const Icon(Icons.send),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }

  void _sendMessage() {
    if (_controller.text.isNotEmpty) {
      _channel.sink.add(_controller.text);
    }
  }

  @override
  void dispose() {
    _channel.sink.close();
    _controller.dispose();
    super.dispose();
  }
}