레이블이 flutter인 게시물을 표시합니다. 모든 게시물 표시

Flutter는 하나의 코드 베이스를 사용하여 여러 플랫폼(Android, IOS, Desktop, Web) 애플리케이션을 구축할 수 있는 강점을 가지고 있습니다. Flutter를 활용해서 Mobile to Mobile, PC to Mobile 서로 ...

Flutter는 하나의 코드 베이스를 사용하여 여러 플랫폼(Android, IOS, Desktop, Web)

애플리케이션을 구축할 수 있는 강점을 가지고 있습니다.

Flutter를 활용해서 Mobile to Mobile, PC to Mobile 서로 간단하게 메세지를 주고받는

WiFi 소켓 통신 예제를 작성하고 확인한 내용을 정리합니다.

 







1. dart:io 라이브러리

소켓 통신을 하기 위해 Flutter에서 지원 해주는 dart:io 라이브러리를 사용합니다.

 

import 'dart:io';

 

dart:io를 사용하면 파일, 디렉터리, 서버 및 클라이언트, 소켓, HTTP 등을 작업할 수 있습니다.

(단, 브라우저 기반 앱은 dart:io 사용을 못 한다고 하네요)








2. 서버(Server)

"flutter_socket_server" 이름으로 새로운 프로젝트를 만들었습니다.

주요 소스는 아래와 같습니다.

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
final _msgController = TextEditingController();
ServerSocket? serverSocket;
Socket? clientSocket;
int port = 5555;
StringBuffer logcat = StringBuffer();

void startServer() async {
setState(() {
logcat.write("Start Server : waiting client...\n");
});

serverSocket = await ServerSocket.bind(InternetAddress.anyIPv4, port, shared: true);
serverSocket?.listen((socket) {
setState(() {
clientSocket = socket;
logcat.write(
"Socket Connected - ${socket.remoteAddress.address}:${socket.remotePort}\n");
});

socket.listen(
(onData) {
setState(() {
logcat.write("Receive : ${utf8.decode(onData)}\n");
});
},
onDone: () {
disconnect();
},
onError: (e) {
logcat.write("exception : $e\n");
disconnect();
},
);
});
}

void stopServer() {
serverSocket?.close();

setState(() {
serverSocket = null;
logcat.write("Stop Server\n");
});
}

void disconnect() {
clientSocket?.close();

setState(() {
clientSocket = null;
logcat.write("Socket Disconnected\n");
});
}

void sendMessage() {
if (_msgController.text.isEmpty) return;

clientSocket?.write(_msgController.text);

setState(() {
logcat.write("Send : ${_msgController.text}\n");
_msgController.clear();
});
}

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

startServer();
}

@override
void dispose() {
super.dispose();

stopServer();

_msgController.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Socket Server(서버)')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
controllerView(),
logcatView(),
sendMessageView(),
],
),
),
);
}

Widget controllerView() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15),
Text(
clientSocket == null ? 'DisConnected' : 'Connected',
style: TextStyle(
color: clientSocket == null ? Colors.red : Colors.green,
),
),
const SizedBox(height: 15),
ElevatedButton(
onPressed: () {
if (serverSocket == null) {
startServer();
} else {
stopServer();
}
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: Text(serverSocket == null ? '서버 시작' : '서버 중지'),
),
],
);
}

Widget logcatView() {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: SizedBox(
width: double.infinity,
child: Text(
logcat.toString(),
),
),
),
),
);
}

Widget sendMessageView() {
return Card(
child: ListTile(
title: TextField(
controller: _msgController,
),
trailing: IconButton(
icon: const Icon(Icons.send),
color: Colors.blue,
disabledColor: Colors.grey,
onPressed: (clientSocket != null) ? sendMessage : null,
),
),
);
}
}


모바일 실행 모습







3. 클라이언트(Client)

"flutter_socket_client" 이름으로 새로운 프로젝트를 만들었습니다.

주요 소스는 아래와 같습니다.

import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';

class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});

@override
State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
final _msgController = TextEditingController();
final _ipAddressController = TextEditingController();
Socket? clientSocket;
int port = 5555;
StringBuffer logcat = StringBuffer();

void connectToServer(String ipAddress) async {
Socket.connect(ipAddress, port, timeout: const Duration(seconds: 5))
.then((socket) {

setState(() {
clientSocket = socket;
logcat.write("Socket Connected - ${socket.remoteAddress.address}:${socket.remotePort}\n");
});

socket.listen(
(onData) {
setState(() {
logcat.write("Receive : ${utf8.decode(onData)}\n");
});
},
onDone: () {
disconnect();
},
onError: (e) {
logcat.write("exception : $e\n");
disconnect();
},
);
}).catchError((e) {
logcat.write("exception : $e\n");
});
}

void disconnect() {
clientSocket?.close();

setState(() {
clientSocket = null;
logcat.write("Socket Disconnected\n");
});
}

void sendMessage() {
if (_msgController.text.isEmpty) return;

clientSocket?.write(_msgController.text);

setState(() {
logcat.write("Send : ${_msgController.text}\n");
_msgController.clear();
});
}

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

@override
void dispose() {
super.dispose();

disconnect();

_ipAddressController.dispose();
_msgController.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Flutter Socket Client(클라이언트)')),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
controllerView(),
logcatView(),
sendMessageView(),
],
),
),
);
}

Widget controllerView() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 15),
Text(
clientSocket == null ? 'DisConnected' : 'Connected',
style: TextStyle(
color: clientSocket == null ? Colors.red : Colors.green),
),
const SizedBox(height: 15),
TextFormField(
controller: _ipAddressController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'IP 주소',
),
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'IP 주소를 입력하세요.';
}
return null;
},
),
const SizedBox(height: 15),
ElevatedButton(
onPressed: () {
if (clientSocket == null) {
connectToServer(_ipAddressController.text);
} else {
disconnect();
}
},
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: Text(clientSocket == null ? '연결' : '연결 해제'),
),
],
);
}

Widget logcatView() {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: SizedBox(
width: double.infinity,
child: Text(
logcat.toString(),
),
),
),
),
);
}

Widget sendMessageView() {
return Card(
child: ListTile(
title: TextField(
controller: _msgController,
),
trailing: IconButton(
icon: const Icon(Icons.send),
color: Colors.blue,
disabledColor: Colors.grey,
onPressed: (clientSocket != null) ? sendMessage : null,
),
),
);
}
}


테블릿 실행 모습







4. 소스 다운로드

Github:flutter_socket_server

Github:flutter_socket_client








5. Mobile to Mobile 소켓 통신 확인하기

소켓 통신을 하기위해서는 먼저 서로 다른 기기 간에 네트워크를 연결해줘야 합니다.

하나의 단말에 모바일 핫스팟(AP)을 켜주고 나머지 단말에서 WiFi를 연결 시켜 줍니다.

기기간에 WiFi가 연결되었다면 IP가 할당되었을 것이고,

[설정] > [모바일 핫스팟] > [연결된 기기]를 통해 할당된 IP 주소를 확인할 수 있습니다.


 

연결된 IP 주소를 확인하고 아래와 같이 연결 및 간단한 소켓 통신을 확인 할 수 있었습니다.


모바일 Server(WiFi)



테블릿 Client(WiFi AP)







6. PC to Mobile 소켓 통신 확인하기

PC to Mobile도 마찬가지로 기기간에 WiFi를 통해 네트워크를 연결해 줍니다.

저 같은 경우에는 PC에서 핫스팟(AP)을 켜주고 모바일에서 WiFi로 연결해 주었습니다.

이렇게 기기간에 연결이 완료되었다면,

PC에서 [설정] > [네트워크 및 인터넷] > [모바일 핫스팟] 연결된 IP주소를 확인할 수 있습니다.



연결된 IP 주소를 확인하고 아래와 같이 연결 및 간단한 소켓 통신을 확인 할 수 있었습니다.


모바일(WiFi)

 

 

PC(WiFi AP)






[Reference]

Dart Dev - dart:io

Flutter Example - TCP Socket Server

Flutter Example - TCP Socket Client