개요
기존에 Flask를 활용하여 실시간 채팅 기능을 설명 및 구현한 포스팅을 작성한 적이 있다.
이 기능을 이용하여 사내에서 간단히 직원들끼리 사용할 채팅 프로그램을 만들었었는데,
이번에는 조금 더 욕심을 부려서 다양한 플랫폼에서도 사용할 수 있도록
Flutter로도 다시 재구현해 보고자 이 포스팅을 작성하게 되었다.
관련 내용들을 찾아보니 대부분이 NodeJs나 각각의 WebSocket서버를 구축하고 사용하는 것 같더라.
Dart언어를 사용하는 Flutter인 만큼, 이번 포스팅에서는 Dart를 이용하여 웹 소켓 서버를 구축해보고자 한다.
또한 웹 소켓 서버 구축에 대한 각각의 메서드 설명을 같이 포함하고 있다.
정말 급하게 기능 구현 코드만 필요하다면 서운하겠지만.. 각 챕터의 가장 아래 예시 코드만 봐도 된다 :-(
[ 시리즈 포스팅 내용 보기 ]
포스팅에서 설명하는 프로젝트는 깃허브에서 다운로드할 수 있다.
- https://github.com/luvris2/flutter_chatting_app
- 하위 디렉토리 : 01_websocket_server_client_setting
서버 (Server)
절차
웹 소켓 서버를 구성하려면 dart.io 라이브러리가 추가로 필요하다.
dart.io 라이브러리는 다트 언어 설치 시 기본으로 제공되는 라이브러리이다.
import 'dart:io';
- 서버 만들기 (bind)
- 서버를 열려면 우선 HTTP 서버를 바인딩하여 요청을 수신할 수 있도록 한다.
- 클라이언트 요청 웹 소켓으로 변경하기 (upgrade)
- HTTP 서버에 수신된 요청을 소켓으로 변환하여 소켓 수신을 할 수 있어야 한다.
- 클라이언트의 요청 데이터 수신 처리하기 (listen)
- 핸들러를 정의하여 클라이언트에서 보낸 요청 데이터를 처리할 수 있도록 해야 한다.
- 클라이언트에게 요청 데이터 송신 처리하기 (add)
- 데이터를 클라이언트에게 보낸다.
- dart:io를 이용하는 WebSocket은 send 메서드가 아닌 add 메드를 사용하여 데이터를 송신한다.
서버 바인딩 (HttpServer.bind)
지정된 주소(address) 및 포트(port)에서 HTTP 요청 수신 대기를 시작한다.
[ 구문 ]
// bind static method
Future<HttpServer> bind(
dynamic address,
int port,
{int backlog = 0,
bool v6Only = false,
bool shared = false}
)
[ 속성 설명 ]
- address
- 서버를 바인딩할 주소
- 일반적으로 IP 주소 또는 호스트 이름
- port
- 서버를 바인딩할 포트 번호
- 값이 0일 경우 시스템에서 임시 포트 사용
- backlog
- 소켓 대기열의 최대 길이, 기본값은 0
- 값이 0일 경우 대기열에 연결 요청하는 대기 시간이 없고 클라이언트 요청 즉시 처리
- 서버에 너무 많은 처리 요청이 있을 경우를 방지하기 위한 속성
- v6Only
- IPv6만 연결할 수 있도록 설정, 기본값은 false
- true일 경우, IPv4 연결을 허용하지 않음
- shared
- 포트가 다른 서비스와 공유될 수 있는지 여부 지정, 기본값은 false
- 일반적으로 특정 포트에 하나의 서비스를 제공하므로 특별한 이유가 없으면 사용을 권장하지 않음
[ 메서드 설명 ]
서버가 성공적으로 바인딩되면 Future<HttpServer> 객체를 반환한다.
반환된 객체를 사용하여 HTTP 요청을 수신하고 처리할 수 있다.
Http 요청을 웹 소켓으로 변환 (WebSocketTransformer.upgrade)
HttpRequest를 WebSocket 연결로 업그레이드한다.
[ 구문 ]
// upgrade static method
Future<WebSocket> upgrade(
HttpRequest request,
{dynamic protocolSelector(
List<String> protocols
)?,
CompressionOptions compression = CompressionOptions.compressionDefault}
)
/* Sample
// binding server
HttpServer server;
server.listen((request) {
if (...) {
WebSocketTransformer.upgrade(request).then((websocket) {
...
});
} else {
// Do normal HTTP request processing.
}
});
*/
[ 속성 설명 ]
- request
- Websocket으로 업그레이드할 HTTP 요청
- protocolSelector
- 서버와 클라이언트 간 통신에 사용할 서브 프로토콜 선택
- 값이 null이면 프로토콜을 따로 선택하지 않음
- compression
- 웹 소켓 압축 옵션, 기본값은 CompressionOptions.compressionDefault
- 대부분 기본값이면 충분하지만 일부 상황에서는 다른 값을 사용할 수도 있음
[ 메서드 설명 ]
HTTP 또는 HTTPS 서버의 각 HttpRequest를 WebSocket 프로토콜로 업그레이드한다.
업그레이드된 HttpRequest Stream은 WebSocket Stream으로 변환된다.
웹 소켓의 웹 소켓 프로토콜 RFC6455를 참고하여 구현 및 변환된다.
* RFC6455 : TCP 기반의 양방향 통신을 위한 표준 프로토콜
클라이언트의 데이터 수신 처리 (StreamSubscriptiion<List<int>> listen)
스트림에서 생성되는 새로운 데이터를 수신하고 이에 대한 반응 리스너를 등록한다.
[ 구문 ]
// listen method
StreamSubscription<List<int>> listen(
void onData(
List<int> event
)?,
{Function? onError,
void onDone()?,
bool? cancelOnError}
)
[ 속성 설명 ]
- onData
- 데이터 이벤트가 발생할 때 호출되는 콜백 함수
- 스트림에서 수신된 데이터를 처리
- onError
- 에러 이벤트가 발생할 때 호출되는 콜백 함수
- 발생한 에러를 처리
- 생략 가능
- onDone
- 스트림이 닫히고 완료 이벤트가 발생할 때(종료될 때) 호출되는 콜백 함수
- 생략 가능
- cancelOnError
- 에러가 발생하면 스트림을 자동 종료 여부
- 기본값은 false, 에러 발생 시에도 스트림 유지
[ 메서드 설명 ]
제공된 핸들러를 사용하여 스트림의 이벤트를 처리하는 StreamSubscription을 반환한다.
스트림에서 데이터를 수신하고 처리하기 위해 사용한다.
클라이언트로 데이터 송신 처리 (WebSocket.add)
웹 소켓에 연결된 클라이언트에 데이터를 보낸다.
void add(
dynamic data
)
웹 소켓 서버 구성 예시
클라이언트로부터 데이터를 받으면 모든 클라이언트에게 받은 데이터를 다시 출력하는 예시 소스
- 서버 구성 설정
- 로컬 호스트 아이피 : 192.168.0.103
- 포트 : 4001
import 'dart:io';
void main() async {
// 서버 설정
HttpServer server = await HttpServer.bind('192.168.10.103', 4001);
print("서버가 생성되었습니다.");
// 클라이언트 요청 비동기 처리
await for (var req in server) {
// HTTP 요청을 웹 소켓 프로토콜로 업그레이드
await WebSocketTransformer.upgrade(req).then((WebSocket websocket) {
// 디버그용 클라이언트 식별 print
print("클라이언트 접속 : ${req.connectionInfo!.remoteAddress.address}");
// 클라이언트로 받은 데이터 수신 처리
websocket.listen((data) {
// 연결되어 있는 클라이언트에게 보낼 데이터 송신 처리
websocket.add(data);
});
});
}
}
클라이언트 (Client)
절차
웹 소켓 서버를 연결하려면 이것 또한 dart.io 라이브러리가 추가로 필요하다.
import 'dart:io';
- 서버 연결하기 (connect)
- 웹 소켓 서버를 먼저 연결하여야 한다.
- 서버로부터 받은 데이터 수신 처리하기 (listen)
- 핸들러를 정의하여 서버로부터 받은 데이터를 처리할 수 있도록 해야 한다.
- 서버로부터 보낼 데이터 송신 처리하기 (add)
- 데이터를 서버로 보낸다.
서버 연결 (WebSocket.connect)
웹 소캣 연결을 설정한다.
[ 구문 ]
Future<WebSocket> connect(
String url,
{Iterable<String>? protocols,
Map<String, dynamic>? headers,
CompressionOptions compression = CompressionOptions.compressionDefault,
HttpClient? customClient}
)
[ 속성 설명 ]
- url
- WebSocket 서버의 URL
- 서버의 주소와 포트 포함
- URL은 ws 또는 wss 체계를 사용해야 함
* ws : RFC 6455 명세서에 정의된 프로토콜인 웹 소켓을 사용하여 웹 소켓 커넥션을 만들 때 사용되는 특수 프로토콜
* wss : TSL(전송 계층 보안)을 사용하여 보안이 적용된 웹 소켓 커넥션 특수 프로토콜 (http / https 관계와 비슷)
- protocols
- 클라이언트가 지원하는 서브 프로토콜 목록
- headers
- WebSocket 연결에 포함될 헤더 정보를 지하는 맵
- 사용자 지정 HTTP 헤더를 추가할 수 있음
- compression
- 웹 소켓 압축 옵션, 기본값은 CompressionOptions.compressionDefault
- 대부분 기본값이면 충분하지만 일부 상황에서는 다른 값을 사용할 수도 있음
- customClient
- 사용자 지정 HttpClient를 지정, 연결에 대한 사용자 지정 구성 제공
- 연결 시간 초과, 인증서 구성, 프록시 설정 등 다양한 네트워크 설정 조정
[ 메서드 설명 ]
주어진 url에 대한 WebSocket 연결을 설정한다.
클라이언트가 서버에 연결하고 통신을 시작할 수 있도록 하는 기능을 제공한다.
서버로부터 받은 데이터 수신 처리 (StreamSubscriptiion<List<int>> listen)
서버 챕터에서 이미 설명하였으므로 생략한다.
서버로 데이터 송신 처리 (WebSocket.add)
서버 챕터에서 이미 설명하였으므로 생략한다.
클라이언트 서버 연결 구성 예시
웹 소켓 서버 연결 시, 서버 측으로 'Hello?'라는 메시지를 보내고 서버로부터 받은 데이터를 출력하는 예시 소스
import 'dart:io';
void main() async {
// 웹 소켓 서버 연결
WebSocket socket = await WebSocket.connect('ws://192.168.10.103:4001');
// 소켓 서버에 데이터 송신
socket.add("Hello?");
socket.listen((data) {
print("서버로부터 받은 값 : $data");
});
}
웹 소켓 서버 / 클라이언트 접속 테스트
디렉터리 구성
- 서버 : server.dart
- 클라이언트 : lib/main.dart
서버 생성 (바인딩)
- 터미널에서 생성한 서버 다트 파일 실행 (server.dart)
dart server.dart
클라이언트 연결 (서버 접속)
- 터미널에서 생성한 클라이언트 다트 파일 실행 (lib/main.dart)
- 주의할 점은 반드시 서버를 실행한 터미널을 그대로 둔 채 클라이언트 접속을 시도해야 한다.
dart lib/main.dart
다음 포스팅으로 이동하기
Flutter/Dart - 채팅 앱 만들기(2) - 앱 UI 레이아웃 디자인 및 기능 설계하기
참고
- 플러터 공식 문서 - dart:io
- 플러터 공식 문서 - dart:io - HttpServer - bind static method
- 플러터 공식 문서 - dart:io - WebSocketTransformer class
- 플러터 공식 문서 - dart:io - WebSocketTransformer - upgrade static method
- 플러터 공식 문서 - dart:io - CompressionOptions class
- 플러터 공식 문서 - dart:io - Stdin - listen method
- 플러터 공식 문서 - dart:io - WebSocket - add abstract method
- 플러터 공식 문서 - dart:io - WebSocket - connect static method
- 위키백과 - 웹소켓
- 모던 Javascript 튜토리얼 - 웹소켓