29. 플러터 : 서버/클라이언트 연동 (4)

이전 글인 "28. 플러터 : 서버/클라이언트 연동 (3)"에서 언급한
사설 IP 주소로 파이썬 애니웨어를 대신하는 것에 대한 설명이다.
중요한 건 이전 글에 나왔으니까
여기서는 달라진 부분에 대해서만 언급하도록 한다.

이전 예제에서는 클라이언트에 해당하는 앱은 플러터로 구현하고
서버는 만들어져 있는 것을 가져다 사용했다.
이제 우리만의 서버를 직접 만들어서 연동하도록 하자.

윈도우에서는 cmd, 맥과 리눅스에서는 터미널을 실행하도록 한다.
cmd에서는 ipconfig, 터미널에서는 ifconfig 명령을 입력하면 IP 주소를 확인할 수 있다.
대부분 공유기에 연결된 네트웍을 사용하기 때문에 192로 시작하는 주소가 할당된다.
192로 시작하는 주소를 사설 IP 주소라고 부르고
공유기를 통해서만 외부로 나갈 수 있고, 외부에서 직접 연결할 수는 없는 주소를 말한다.

아래 화면은 맥북에서 캡쳐했고, "192.168.35.125"를 코드에서 사용한다.
이 부분은 반드시 자신의 IP 주소를 사용해야 한다.



이전 글과 달라진 부분은 마지막 줄의 run 함수 호출이다.
막혀 있던 주석을 풀었고, host 매개변수에 "192.168.35.125"를 전달했다.

from flask import Flask
import json

app = Flask(__name__)


# 루트 인터페이스. 서버 주소(127.0.0.1:5000)의 루트 폴더.
@app.route('/')
def index():
return 'home. nothing.'


# 루트 아래의 string 폴더에 접근할 때 보여줄 내용
@app.route('/string')
def send_string():
return '취미 : 서핑, 스노보드, 배드민턴'


# 사진은 앱에서 파일에 직접 접근하기 때문에 이름만 알려주면 된다.
@app.route('/image')
def send_image():
return 'book.jpg'


# 배열이나 사전 등의 프로그래밍 언어 객체는 문자열로 변환해서 전달해야 하고
# json 또는 xml 등을 사용하게 된다. 여기서는 json 사용.
@app.route('/array')
def send_array():
items = [
{'id': 12, 'content': '세상은 호락호락하지 않다. 괜찮다. 나도 호락호락하지 않다.'},
{'id': 45, 'content': '공부를 많이 하면 공부가 늘고 걱정을 많이 하면 걱정이 는다.'},
{'id': 78, 'content': '참아야 한다고 배워 힘든 걸 참고 행복도 참게 되었다.'},
]
return json.dumps(items)


if __name__ == '__main__':
app.run(host='192.168.35.125', debug=True)


결과 화면은 똑같다. 달라진 것이 없다.



파이썬 애니웨어의 주소만 "192.168.35.125"로 수정했다. 수정한 곳은 두 군데.


import 'dart:async';
import 'dart:convert'; // json 관련

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;


void main() => runApp(MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('서버/클라이언트')),
body: MyApp(),
))
);

// 수신 데이터의 종류를 상수로 설정
enum DataKind { NONE, STRING, IMAGE, ARRAY }

// 3가지 형태의 문자열 수신 : 단순 문자열, json 문자열, 이미지 주소 문자열.
Future<String> fetchString(DataKind kind) async {
if(kind == DataKind.NONE)
return '';

final details = ['', 'string', 'image', 'array'];
final urlPath = 'http://192.168.35.125/' + details[kind.index];

final response = await http.get(urlPath);

if (response.statusCode == 200)
return response.body;

throw Exception('데이터 수신 실패!');
}

class MyApp extends StatefulWidget {
@override
State createState() {
return MyAppState();
}
}

class MyAppState extends State<MyApp> {
DataKind mKind = DataKind.NONE;

Widget makeChild(String str) {
switch (mKind) {
case DataKind.NONE:
return Placeholder(); // 아무 것도 수신하지 않았을 때 사용
case DataKind.STRING:
return Text(str, style: TextStyle(fontSize: 25),);
case DataKind.IMAGE:
return Image.network('http://192.168.35.125/static/' + str);
default: // str : 사전을 담고있는 배열 형태의 json 문자열.
final items = json.decode(str);
final textStyle = TextStyle(fontSize: 21, fontWeight: FontWeight.bold);

List<Container> widgets = [];
for(var item in items) {
widgets.add( // 여백을 통해 위젯을 보기좋게 배치하기 위해 Container 사용
Container(
child: Column(
children: <Widget>[
Text(item['id'].toString(), style: textStyle),
Text(item['content'], style: textStyle, softWrap: true),
],
),
padding: EdgeInsets.all(20.0),
),
);
}
return Column(children: widgets);
}
}

@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
FutureBuilder(
future: fetchString(mKind),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return Expanded(
child: Center(
child: makeChild(snapshot.data),
),
);
}
else if (snapshot.hasError) {
return Text('${snapshot.error}');
}

return Expanded(
child: Center(
child: CircularProgressIndicator(),
),
);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
child: Text('수신 안함'),
onPressed: () {
setState(() {
mKind = DataKind.NONE;
});
},
),
RaisedButton(
child: Text('문자열'), // 정직하게 함수를 두 번 호출하는 코드
onPressed: () {
setState(() {
mKind = DataKind.STRING;
});
},
),
RaisedButton(
child: Text('사진'), // 한 번은 정직하게, 한 번은 => 문법 사용
onPressed: () {
setState(() => mKind = DataKind.IMAGE);
},
),
RaisedButton(
child: Text('배열'), // 두 번 모두 => 문법 사용. 간단하지만 가독성에서는 어렵다.
onPressed: () => setState(() => mKind = DataKind.ARRAY),
),
],
),
],
);
}
}