Flutter로 라이어 게임 앱 만들기
💡 2022.03 코드 리팩토링 및 이미지 파일 변경
이미지 파일이 변경되기 전에 포스팅을 했기 때문에 포스팅 이미지와 현재 이미지가 다른 점 양해를 구함
🕵️ Info
Project Title: 라이어게임(Liar Game)
Description: Flutter를 이용하여 Liar 게임 만들기
Developed: [2021.02.01 ~ 2021.02.28]
Development Environment: Flutter 1.22.6, Android Studio 4.1.0
Version: 0.3
Github Link
핸드폰 4대로 플레이한 영상
🤔 네트워크가 통신하는 방법
인터넷 상에서는 서로 다른 기기(혹은 PC)끼리 통신을 할 때, 아이피가 필요하다.
IP: 컴퓨터 네트워크에서 장치들이 서로를 인식하고 통신을 하기 위해서 사용하는 특수한 번호
출처: 위키백과
아이피는 다시 공인 아이피(Public IP)와, 사설 아이피(Private IP)로 나뉜다.
공인 IP(Public IP)
- 인터넷 사용자의 Local Network를 식별하기 위해 ISP(Internet Service Provider)가 제공하는 IP 주소.
- 세상에서 단 하나뿐인 IP
- 외부에도 공개되어 있기 때문에, 인터넷이 연결된 다른 PC로부터의 접근이 가능.
사설 IP(Private IP)
- 내부망 전용 IP
- 일반 가정이나 회사에 할당된 네트워크의 IP
- IPv4의 IP Address 부족 때문에 서브넷팅된 IP
- 내부망에서만 사용되기 때문에, 서로 다른 내부망에서 동일한 IP를 구성하더라도 상관 없다.
- 보통 192.168.x.x로 구성됨.
고정 IP
- 컴퓨터에 고정적으로 부여된 IP
- 한번 부여되면 IP를 반납하기 전까지는 다른 장비에 할당되지 않음.
유동 IP
- 고정적이지 않은 IP
- 컴퓨터를 사용할 때 남아있는 IP중 돌아가면서 부여
위 그림을 보자.출처
사설망1에 있는 핸드폰은 사설망2에 있는 핸드폰과 통신을 할 수 없다.
또, 인터넷에서는 공인 IP 178.145.57.144에 접속할 수 있지만, 사설IP인 192.168.x.x에는 접속을 할 수 없다.
이렇게 사설 IP가 할당된 장비는 공유기를 통해 인터넷에 접속을 할 수 있게 된다.
그리고 사설망1과 사설망2끼리의 통신도 이런식으로 외부로 다시 갔다가 내부로 들어와야한다.
또한, 인터넷 상에서 서버를 운영하고자 할 때는 공인 IP를 고정 IP로 부여해야 한다.
공인 IP -> 나만의 서버를 만들고,
고정 IP -> 내 서버가 아닌 다른 사람의 IP로 접속될 수 있음을 방지
현재 가정에서 사용하는 인터넷 서비스는 각 가정마다 공인 IP를 유동 IP로 부여, 공유기 내에서도 사설 IP를 유동 IP로 부여하고 있다.
채팅(정보 통신)을 위한 서버는 대부분 고정 IP를 사용해야한다.
하지만 고정 IP의 서버를 구축하려면 비용이 발생한다.
본 프로젝트에서는 서버를 구축하는 비용을 없애기 위해 같은 공유기 내에서만 통신이 가능하도록 구현할 것이다.
본 프로젝트에서는 외부와 통신이 필요없지만, 만약 외부에서 내부로 통신을 해야하는 경우에는 port forwarding을 해야한다.
🔍 c언어에서의 socket 통신
지금 언급하는건 1:1 통신이 아닌, 서버를 1개로 두고 다수의 클라이언트가 통신하는 상황이다.
그림으로 더 쉽게 설명을 하자면 아래와 같다.
단순히, 이것은 커넥션에 대해 이해하기 쉽게 그림으로 나타낸 것이다.
이제 구현에 대해 전체적인 틀을 보자.
C언어에서의 TCP Socket Programming 과정은 이렇다.
여기서 서버측에서 while문으로 client의 커넥션을 계속 accept해주면 iterative서버가 된다.
글로 적어보자면,
- socket을 생성(서버용)
- 각 구조체를 정의해줌(주소나 포트같은것들)
- bind
- listen
- while으로 루프를 돌면서 client의 connection 요청을 계속 accept해주며 다수의 클라이언트와 연결을 한다.(iterative 서버)
- 서버가 연결된 클라이언트들에게 broadcast해줌.(아래 그림 참고)
- 이 방식을 적용했을 때의 문제점
- 만약 3명이 게임을 하고싶다면, 4명이 필요함(1명은 서버를 열어주고 데이터를 처리해야 함. 이 때, 서버는 데이터를 뿌려주기만 하기에 아무 역할을 하지 않음.)
물론 서버가 행동을 하게끔 만들어주면 된다.
🔗 flutter(dart)의 socket 통신
flutter에서는 c언어에서의 socket programming과 다르게, 서버가 iterative하게 작동하려면 서버소켓의 bind함수의 파라미터에 shared: true
를 적어주면 된다.(while문으로 accept를 계속 받을 필요가 없음)
실제로, ServerSocket은 수신 소켓에 연결된 각 소켓에 하나씩 소켓 객체의 스트림을 제공한다고 한다. 출처
- fllutter는 ip와 port정보로 ServerSocket을 바로 bind한다.
- ServerSocket.listen(Function)만으로 listen과 accept과정을 다 수행해주는 것 같다.
이하는 소스를 참조하자(1 = Server, 2 = Client)
실제로 이 짧은 코드로 다중 클라이언트들의 connection을 받고 통신이 가능하였다.(client가 connection을 요청하면 listen에 물린 함수가 실행됨)
client가 connection요청을 할 때, ServerSocket.listen(Function)의 파라미터인 Function함수가 실행이 되는데, 이 함수는 다시(Socket s)라는 파라미터를 전달하면서 실행이 된다.
(즉, connection이 되면, Function이 실행되고, Function은 Socket s의 정보를 가지면서 실행됨)
여기서 Socket s가 클라이언트의 정보다.
즉, ServerSocket은 ServerSocket.listen(Function)
를 통해 Function(Socket s){} 함수를 실행함.
그렇다면, 연결된 client의 정보들만 가지고 있다면, data값을 서버에서 보내는 것이 가능할 것이고,
위에서 언급한 문제점은 서버가 broadcast를 해주면서 data를 동시에 처리하게끔만 만들면 될 것이다.
-> List로 클라이언트 정보를 저장해줌.
이제 해야할 일은 방만들기 기능, 자동으로 IP셋팅, 한글 지원정도이다.
++ 클라이언트 하나가 disconnect 하면 전체가 통신이 안되는 에러가 있음.
-> 해결함.
아이피를 자동으로 뽑는건 두 개의 패키지를 사용해봄.
- import ‘package:get_ip/get_ip.dart’;
- import ‘package:seeip_client/seeip_client.dart’;
근데 어떤 기기에는 IP를 잘 뽑는데, 어떤 기기는 아니었다.
(실험환경: Android Galaxy Note9, S10+, S6, A6) Note9랑 10+가 안뽑힘.
현재는 수동으로 IP를 설정해주고 있고, 현재 통신은 잘 된다.
데이터를 넘길 때 String을 조작을 해서, IP주소와 닉네임, 메세지를 함께 넘겨주고, 받을때는 다시 파싱을 하는 방법으로 세 가지 정보를 받을 수 있게 해준다.
그리고 Route간에 data를 이동 가능하게 하여, 메인 화면에서 필요한 정보들을 다 받고, 실제 화면에서는 채팅창만 보이도록 설정할 것이다.
마지막으로, 게임 알고리즘을 적용하여 라이어게임을 완성시키면 얼추 프로젝트가 끝날 것 같다.
💬 기능 구현에 대한 간단한 설명.
아주 간단한 부분(버튼 onPressed 이런 부분은 설명하지 않겠다.)
내가 구현했을 때 어려웠거나 의미있는 부분만 담아보겠다.
🍒 Route to Route 데이터전달
아래 그림과 같이 Route간에 데이터값을 전달하기 위해, Navigator.pushNamed
를 사용했다.
Route의 이름을 설정하여, Route간에 이동이 가능하게 하였고, argument를 넘겨주며 route간에 데이터를 넘겨주었다.
-> 이는 초기화면에서 데이터를 받아 게임화면으로 전달하기 위함으로 사용하였다.
이런 느낌이고, 이것은 옛날 UI다.
적용은 여기에다가 했다.
🍇 통신 로직
위 그림처럼 다수의 클라이언트와 서버가 통신을 해야하는 상황이다.
먼저 서버를 열어주고, 클라이언트가 들어올때마다 client에 대한 정보를 담아준다.(아래 그림 참고)
또한, 가지고 있는 client정보로 서버가 모든 client들에게 broadcast하면서 다수의 클라이언트와 통신을 가능하게 하였다.(아래 그림 참고)
서버는 받은 정보를 가지고 action을 하게 만들어서 위에서 언급한, 서버가 아무일도 안하는 상태는 되지 않는다.
클라이언트는 아래 그림과 같이 동작하게 된다.
현재 1:1로 묶인 클라와 서버는 서로에 대한 정보를 알기에, 나머지 클라이언트들에게 서버가 정보를 뿌려준다.
📝 (utf-8 인코딩 방식)
data송수신은 채팅창에 쓴 내용과 함께 자신의 아이피+닉네임 정보를 함께 encoding 및 decoding하면서 서로 넘기고 받는다.
그 중, 인코딩/디코딩 과정은 한글을 처리하기 위함인데, 그냥 String 정보로 data를 주고받으면 한글이 깨진다.
주고받는 character set이 맞지 않음이 이유였고, utf-8로 인코딩 및 디코딩을 하며 받았을 때, 문제가 없었다.(서로 인코딩 방식을 맞춰서 주고받음)
위의 소스코드에서 List<int> Data
부분의 이유가 여기 있다.
인코딩한 정보를 int형태의 List로 받아 decode하면 한글도 깨지지 않는다.
설명이 어렵다면, 아래 그림을 참고하면 더욱 이해하기 쉬울 것이다.
이렇듯, 인코딩 및 디코딩 과정이 없으면, ASCII코드 범위의 값들은 전송이 되나, 그걸 벗어나는 유니코드는 깨져서 표현이 된다.
따라서, utf-8의 표현법으로 인코딩 및 디코딩을 해주어, ASCII코드 범위 밖의 data도 해결을 하였다.
🗳️ 정답 처리 및 라이어 투표
게임 특성상, 라이어 한명을 정하고, 나머지는 제시어를 알려줘야한다.
이러한 경우, 모든 클라이언트 정보를 가지고 있는 server가 broadcast를 해주었고, 투표나 어떤 특정한 기능들을 수행할 때마다 server가 관련 정보를 msg로 만들어서 broadcast해주게끔 로직을 짰다.(아래 그림 참고)
클라이언트에서 msg를 받을때는 msg에 담긴 특수문자로 parsing을 하여 msg를 처리하였다.
사실 서버를 하나 두고 그 서버가 모든 클라이언트를 관리하는식으로 갔으면 best였는데, 그렇게는 하지 않았다.
조금 더 프로그램이 커지면 서버 하나를 두는게 낫다고 생각한다.
🍐 끝으로
일단 이 게임의 핵심은 서버와 클라이언트간의 통신이다.
실제로 통신만 되고 data만 송수신이 잘 된다면, 어려움 없이 코드를 짤 수 있었다.
이렇게 채팅프로그램에 게임 알고리즘을 적용하여 게임을 만들어보았고, 유익한 시간이 되었다.
다음에는 큰 틀을 전부 구상해놓고 코드를 짜도 훨씬 괜찮을 것 같다고 생각했다.
이번 프로젝트는 생각만 해놓고, 계획없이 바로바로 코드를 작성했는데, 좀 더 차근차근 코드를 짜는 것이 더 효율적이라는 것을 느꼈다.
현재 통신 잘 되고, 투표 등 모든 기능이 다 잘 된다.
이 게임은 예전 스타크래프트 UDP처럼 같은 공유기를 사용하고 있는 친구끼리 게임을 해보자는 생각에서 만들어졌고, 현재 막힘없이 모든 부분이 잘 돌아간다.
핸드폰 4대로 게임 플레이하는 영상을 끝으로 포스트를 마치겠다.
서버 핸드폰에서 녹화영상
이 포스트에 대한 피드백은 언제나 환영이다.
Leave a comment