본문 바로가기
GameMaker강좌[GMS2]/네트워크강좌

[게임메이커 강좌-네트워킹][GMS2] 채팅 프로그램 만들기-2-서버/클라이언트

by 타락카얀 2024. 12. 23.
728x90

 

 

GAME MAKER 강좌

 

KAYAN

 

 

 

 

 

 

◈ 서버 만들기

 

서버와 클라이언트는 따로 작동해야 하기 때문에, 오브젝트를 2개 추가하고 각각 따로 설정하도록 합니다.

강좌에서는 obj_server(서버 오브젝트), obj_client (클라이언트 오브젝트)로 나누겠습니다.

 

먼저 서버 오브젝트부터 설정해봅시다.

 

(▲ 서버 오브젝트)

 

[Create 이벤트]를 추가하고, 필요한 기능을 추가합니다.

서버에는 클라이언트 정보를 저장할 리스트 구조체와 맵 구조체를 추가합니다.

그리고 서버와 클라이언트간의 통신에 사용할 버퍼를 만들어야 합니다.

 
obj_server 오브젝트 - Create 이벤트
 
 
global.select_server = 1; //서버
 
global.player_max = 30; //채팅 참가 최대 인원
 
global.socketlist = ds_list_create( ); //접속한 클라이언트 소켓 저장
global.player_map = ds_map_create( ); //클라이언트 세부 정보 저장
 
global.net_buffer = buffer_create( 256, buffer_grow, 1 ); //통신에 필요한 버퍼
 
global.local_socket = -1;
global.local_server_IP = "127.0.0.1"; //현재 서버 IP. 기본값은 로컬 "127.0.0.1"

 

다음은 서버를 생성하는 것입니다.

서버는 network_create_server 함수로 만들 수 있습니다.

 

◎ network_create_server(type, port, max_client)
   - type : [소켓 유형 상수] 생성할 서버의 유형
   - port : [실수] 서버가 사용할 포트. 0~65535.
   - max_client :[실수] 한 번에 연결할 수 있는 최대 클라이언트 수
   
   
   [소켓 유형 상수]
   - network_socket_tcp : TCP를 사용하여 소켓을 만듭니다.
   - network_socket_udp : UDP를 사용하여 소켓을 만듭니다.
   
   - network_socket_ws : TCP를 사용하여 웹소켓을 만듭니다.
   - network_socket_wss * : TCP를 사용하여 보안 웹소켓을 만듭니다.

 

 

 

 

생성 타입은 TCP, 포트는 0~65535까지 지정할 수 있지만, 게임메이커 예문에서 주로 사용하는 6510으로 설정하겠습니다.

포트는 중요한데, 미리 등록된 포트, 또는, 사용중인 포트(같은 게임메이커 게임이더라도) 경우 서버가 생성되지 않습니다.

 

※ [참고] 대부분의 유닉스 계열 운영 체제의 경우, 잘 알려진 포트를 열려면 루트 권한이 있어야 합니다.

※ - 0번 ~ 1023번: 잘 알려진 포트 (well-known port)

※ - 1024번 ~ 49151번: 등록된 포트 (registered port)

※ - 49152번 ~ 65535번: 동적 포트 (dynamic port)

※ TCP/UDP의 포트 목록 https://ko.wikipedia.org/wiki/TCP/UDP%EC%9D%98_%ED%8F%AC%ED%8A%B8_%EB%AA%A9%EB%A1%9D

 

서버 생성에 실패하면 0보다 작은 값이 반환되고 성공하면 서버를 식별하는데 사용할 수 있는 소켓 ID 가 반환됩니다.

 
obj_server 오브젝트 - Create 이벤트
 
 
global.server_socket = network_create_server( network_socket_tcp, 6510, global.player_max );
if ( global.server_socket < 0 ){
//서버 생성 실패했을 때
 
}
else{
//서버 생성 성공했을 때
}

서버 생성에 성공했을 때, 또는, 실패했을 때 각각 이벤트를 구성합니다.

강좌에서는 실패하면, 서버 오브젝트를 파기한 다음, "서버 생성에 실패했습니다" 메시지를 표시할 겁니다.

그리고, 메인 화면으로 돌아가는 겁니다.

서버 생성에 성공하면 "채팅을 시작합니다"라고 채팅글로 추가할 거에요.

 

obj_server 오브젝트 - Create 이벤트
 
 
global.server_socket = network_create_server( network_socket_tcp, 6510, global.player_max );
if ( global.server_socket < 0 ){
//서버 생성 실패했을 때
instance_destroy( );
show_message( "서버 생성에 실패했습니다." );
room_goto( room_open );
}
else{
//서버 생성 성공했을 때
ds_list_add( global.chat_list, "채팅을 시작합니다." );
}

 

현재 사용하려 하는 서버의 IP를 모르기 때문에, 클라이언트에게 알려줄 수 가 없죠.

따라서 서버 IP를 얻기 위해 [네트워크 비동기 이벤트]를 발동시킵니다.

 
obj_server 오브젝트 - Create 이벤트
 
 
global.server_socket = network_create_server( network_socket_tcp, 6510, global.player_max );
if ( global.server_socket < 0 ){
//서버 생성 실패했을 때
instance_destroy( );
show_message( "서버 생성에 실패했습니다." );
room_goto( room_open );
}
else{
//서버 생성 성공했을 때
ds_list_add( global.chat_list, "채팅을 시작합니다." );
 
//---------- ▼ 서버 IP 갱신 전용( 로컬 )
global.local_socket = network_create_socket_ext( network_socket_udp, 6511 );
alarm[5] = 1;
//---------- ▲ 서버 IP 갱신 전용( 로컬 )
 
}

 

클라이언트 소켓을 만들어 서버로 로컬 접속하면 [네트워크 비동기 이벤트]가 발동됩니다.

 

(▲ 네트워크 비동기 이벤트)

 

여기서 현재 서버의 IP를 얻을 수 있습니다.

로컬 접속은 아래 함수로 처리할 수 있습니다.


◎ network_send_broadcast(socket, port, bufferid, size);


   - socket : [네트워크 소켓 ID] 사용할 소켓의 ID.
   - port : [Real] 서버가 사용할 포트.
   - bufferid : [버퍼] 데이터를 가져올 버퍼.
   - size : [Real] 데이터의 크기(바이트).


 

서버 오브젝트의 [Alarm 5 이벤트]를 추가하고, 서버에 접속하도록 합니다.

 
obj_server 오브젝트 - Alarm 5 이벤트
 
 
//현재 서버의 네트워크 이벤트 트리거( 서버 IP 갱신 )
buffer_seek( global.net_buffer, buffer_seek_start, 0 );
buffer_write( global.net_buffer , buffer_string, "check" ); //메시지
network_send_broadcast( global.local_socket, 6511, global.net_buffer, buffer_tell( global.net_buffer ) );

 

이제 [네트워크 비동기 이벤트]를 추가하고,

(▲ 네트워크 비동기 이벤트)

 

async_load 맵에서 IP를 받아오도록 합니다.

 
obj_server 오브젝트 - Async - Networking 이벤트
 
 
var n_id = ds_map_find_value( async_load, "id" );
 
if n_id = global.local_socket{
var _ip = ds_map_find_value( async_load, "ip" );
global.local_server_IP = _ip;
exit;
}

 

다음 [Destroy 이벤트]를 추가하고, 메모리에서 제거해야하는 기능은 파기하도록 합니다.

 
obj_server 오브젝트 - Destroy 이벤트
 
 
ds_list_destroy( global.socketlist ); //클라이언트 소켓 목록 파기
 
ds_map_destroy( global.player_map ); //클라이언트 정보 파기
 
network_destroy( global.server_socket ); //네트워크 서버 파기
network_destroy( global.local_socket ); //네트워크 소켓 파기( 서버 IP )
 
buffer_delete( global.net_buffer ); //네트워크용 버퍼 파기

 

마찬가지로 [Game End 이벤트]를 추가하고, 같은 내용을 추가합니다.

 
obj_server 오브젝트 - Game End이벤트
 
 
ds_list_destroy( global.socketlist ); //클라이언트 소켓 목록 파기
 
ds_map_destroy( global.player_map ); //클라이언트 정보 파기
 
network_destroy( global.server_socket ); //네트워크 서버 파기
network_destroy( global.local_socket ); //네트워크 소켓 파기( 서버 IP )
 
buffer_delete( global.net_buffer ); //네트워크용 버퍼 파기

 

※[주의] 게임을 종료하거나, 재시작, 또는, 네트워크가 필요 없는 다른 룸으로 이동할 때, 오류를 피하기 위해서

 생성된 네트워크 서버나, 소켓은 network_destroy 함수를 사용해 완전히 파기하는 것이 좋습니다.

 

 

 

이제 IP를 받아오는지 테스트 해봅시다.

 
obj_server 오브젝트 - Draw 이벤트
 
 
draw_set_font( font0 );
draw_set_halign( fa_left );
draw_set_valign( fa_top );
 
var t, c;
t = "채팅 예제 ( 서버 )"+$" IP:{ global.local_server_IP}";
c = c_black; draw_text_color( 80, 16, t, c, c, c, c, 1 );

 

 

 

 

 

 

◈ 클라이언트 연결 설정

 

다음은 클라이언트 설정입니다.

클라이언트 오브젝트에 [Create 이벤트]를 추가하고, 필요한 기능을 추가합니다.

 

(▲ 클라이언트 오브젝트)

 

서버와 마찬가지로 서버와의 통신을 위한 버퍼와, 다른 클라이언트의 정보를 저장할 맵 데이터 구조체를 생성하도록 합니다.

 
obj_client 오브젝트 - Create 이벤트
 
 
global.select_server = 0; //클라이언트
 
global.net_buffer = buffer_create( 256, buffer_grow, 1 ); //버퍼
 
global.player_map = ds_map_create( ); //클라이언트 정보

 

서버와의 통신을 위해선 소켓을 먼저 만들어야 합니다.

소켓은 아래 함수로 만들 수 있습니다.


◎ network_create_socket(type);
   - type : [소켓 유형 상수] 생성할 서버의 유형


 

 

 

기본적으로 이 함수는 동기식이므로 연결이 이루어질 때까지 게임이 "멈춰" 보일 수 있습니다.

그럴 때는 network_set_config 함수를 사용하여 연결에 대한 시간 초과 값을 설정하는 것이 좋습니다.

 
obj_client 오브젝트 - Create 이벤트
 
 
global.select_server = 0; //클라이언트
 
global.net_buffer = buffer_create( 256, buffer_grow, 1 ); //버퍼
 
global.player_map = ds_map_create( ); //클라이언트 정보
 
//---------- ▼ 서버에 접속
 
global.client_socket = network_create_socket( network_socket_tcp ); //클라이언트 소켓 생성
 
network_set_config( network_config_connect_timeout, 1000 ); //서버 연결 초과 시간
 
//서버에 접속
var server = network_connect( global.client_socket , "127.0.0.1", 6510 );
 
if server < 0{
//연결 실패했을 때
}
else{
//연결 되었을 때
}
 
//---------- ▲ 서버에 접속

 

서버에 접속하려면 network_connect 함수로 서버의 IP로 접속하면 됩니다.


◎ network_connect(socket, url, port);
   - socket :[네트워크 소켓 ID] 사용할 소켓의 ID.
   - url :[문자열] 연결할 URL 또는 IP(문자열).
   - port :[실수] 연결할 포트.


 

함수는 연결에 실패하면 0보다 작은 값이 반환되고, 성공하면 소켓 ID가 반환됩니다.

 

연결 성공했을 때와 실패 했을 떄, 각각 이벤트를 구성합니다.

 
obj_client 오브젝트 - Create 이벤트
 
 
global.select_server = 0;
 
global.net_buffer = buffer_create( 256, buffer_grow, 1 );
global.player_map = ds_map_create( );
 
global.client_socket = network_create_socket( network_socket_tcp );
 
network_set_config( network_config_connect_timeout, 1000 );
 
 
var server = network_connect( global.client_socket , "127.0.0.1", 6510 );
 
if server < 0{
//---------- ▼ 연결 실패
//연결 실패했을 때
 
instance_destroy( );
show_message( "서버 연결에 실패했습니다." );
room_goto( room_open );
//---------- ▲ 연결 실패
}
else{
//---------- ▼ 연결 성공
//연결 되었을 때
 
ds_list_add( global.chat_list, "서버에 연결 되었습니다!" );
//---------- ▲ 연결 성공
}

 

강좌에서는 연결 실패 했을 때는 클라이언트 오브젝트를 파기하고, "서버 연결에 실패했습니다" 메시지를 띄울 것입니다.

그리고 메인화면으로 돌아가는 거죠.

연결 성공했을 때는 "서버에 연결 되었습니다!"를 채팅글로 추가합니다.

 

 

클라이언트가 서버에 연결되면 서버에서는 [네트워크 비동기 이벤트]가 발동 됩니다.

서버 오브젝트의 [네트워크 비동기 이벤트]에 서버에 접속한 클라이언트 정보를 처리합니다.

클라이언트의 접속 여부는 async_load 맵의 "type"키로 확인할 수 있습니다.


   - network_type_connect : 네트워크 연결로 인해 발생합니다.
   - network_type_disconnect : 네트워크 연결 해제로 인해 발생합니다.


 

클라이언트가 접속했을 때(network_type_connect)는 클라이언트가 접속한 소켓을 리스트에 저장하도록 합니다.

그리고, 클라이언트가 채팅을 나갔을 때(network_type_disconnect)는 해당 소켓을 리스트에서 삭제합니다.

 
obj_server 오브젝트 - Async - Networking 이벤트
 
 
if n_id == global.server_socket{
//--------- 클라이언트 연결/해제 체크
var type = ds_map_find_value( async_load, "type" );
 
switch( type ){
case network_type_connect://현재 소켓에 새로운 클라이언트 접속
//-------------
var sock = ds_map_find_value( async_load, "socket" ); //클라이언트 소켓
 
ds_list_add( global.socketlist, sock ); //접속한 클라이언트 소켓 저장함
//-------------
break;
 
case network_type_disconnect://현재 소켓에 클라이언트 연결 종료
//-------------
var sock = ds_map_find_value( async_load, "socket" ); //클라이언트 소켓
 
var pos = ds_list_find_index( global.socketlist, sock );
 
ds_list_delete( global.socketlist, pos ); //연결 종료된 클라이언트 소켓 삭제
//-------------
break;
 
}
//--------- 클라이언트 연결/해제 체크
}

 

서버와 클라이언트간의 통신할 준비가 끝났습니다.

물론 이것만으로 통신이 잘 되는지 확인할만한게 별로 없습니다.

서버에 클라이언트가 접속할 뿐, 메시지를 주고 받아야 어느정도 확인이 가능하죠.

 

어째든 여기까지 채팅 오브젝트의 [Create 이벤트]에 서버 오브젝트와 클라이언트 오브젝트를 생성하도록 추가합니다.

 
obj_chat 오브젝트 - Create 이벤트
 
 
keyboard_string = "";
 
//--------- ▼ 서버/클라이언트 생성
if ( global.select_server == 1 ){
instance_create_depth( 0, 0, -100, obj_server );
}
else{
instance_create_depth( 0, 0, -100, obj_client );
}
//--------- ▲ 서버/클라이언트 생성

 

이렇게 하면 메인화면의 메뉴를 눌러 채팅룸으로 갔을 때, 메뉴 종류에 따라 서버 또는 클라이언트를 활성화하게 될 거에요.

 

 

 

 

이제부터 테스트는 빌드된 실행 파일(exe)이 필요합니다.

게임메이커는 프로젝트 1개만 실행할 수 있어서, 실행 파일로 빌드하고, 2번 실행해서 하나는 서버,

다른 하나는 클라이언트로 테스트하시면 됩니다.

또한 네트워크 기능을 사용하면 윈도우 방화벽 허용 여부를 묻는데, 방화벽 허용해야 정상적으로 작동합니다.

 

 

 

 

 

 

 

 

 

 

300x250

댓글