티스토리 뷰



이 자료들은 팁스소프트에서 제공하는 [ 알짜배기 ] 프로그램을 이용하면 더 편리하게 볼수 있습니다.
* 알짜배기 프로그램 받기 - http://www.tipssoft.com/bulletin/tb.php/QnA/8406
* 안드로이드 강좌 목록 - http://www.tipssoft.com/bulletin/tb.php/old_bbs/501
이번 자료에서는 PC 에서 동작하는 프로그램과 안드로이드용 프로그램이 소켓을 사용하여 정보를
주고받는 방법에 대해서 알아보도록 하겠습니다.
이론에 대한 설명은 이전 강좌에서 어느정도 언급했기 때문에 이번 강좌에서는 예제를 통해 좀더
실질적인 방법에 대해서 설명하도록 하겠습니다. 설명을 위해 사용한 예제는 다자간 채팅 프로그램이며
PC 에서 실행되는 프로그램이 서버 역할을 하도록 구성했고 안드로이드에서 동작하는 프로그램은
서버에 접속해서 채팅을 하는 형태입니다.
이 강좌에서 다루는 선행 강좌에 대해서 참고하고 싶으시면 다음의 링크를 참고하시기 바랍니다.
리스트뷰 사용하기 - 활용편 : http://www.tipssoft.com/bulletin/tb.php/FAQ/921
쓰레드(Thread) 의 이해 - 기초편 : http://www.tipssoft.com/bulletin/tb.php/FAQ/922
스트림(Stream)의 이해 - 바이트 스트림 : http://www.tipssoft.com/bulletin/tb.php/FAQ/928
소켓(Socket) 통신의 이해 - 클라이언트 : http://www.tipssoft.com/bulletin/tb.php/FAQ/930
PC 에서 동작하는 서버 프로그램은 서버 형식의 소켓을 사용하며 MFC로 개발되었습니다. 이 부분은
강좌와 직접적인 연관이 없기 때문에 예제를 테스트해볼 수 있도록 실행파일만 공개하도록 하겠습니다.
따라서 이 강좌의 대부분의 내용은 서버와 주고받는 데이터의 흐름과 구성 그리고 안드로이드에서
실행되는 클라이언트를 어떻게 개발해야 되는지에 대한 내용입니다.
1. 서버 - 클라이언트의 진행 흐름
PC 에서 실행된 서버 프로그램이 서버 서비스를 개시하면 안드로이드용으로 개발된 클라이언트
프로그램을 실행시켜 서버에 접속할 수 있습니다. 이 예제는 간단하지만 다자간 채팅 프로그램이기
때문에 여러명이 서버에 접속할수 있으며 하나의 클라이언트에서 서버로 메세지를 전송하면
서버에 접속된 모든 클라이언트에게 해당 메세지가 전달되게 됩니다.
다음은 서버와 클라이언트가 어떤 흐름으로 통신을 하는지 나타내는 그림과 그림에 대한 설명입니다.
- connect
클라이언트 프로그램이 서버 프로그램에 연결을 시도한다.
- accept
서버 프로그램이 클라이언트의 연결 시도를 수락한다.
- SEND_OS_TYPE
클라이언트 프로그램은 자신이 어떤 시스템에서 동작하는 프로그램인지 서버에 알려준다.
윈도우즈 시스템인 경우 1, 안드로이드 시스템인 경우 2 를 전송한다. 소켓 통신에서 사용하는
패킷의 프레임은 수신시 얼만큼 데이터를 읽을 것인가에 대한 문제때문에 아래의 그림과 같이
헤더에 Body 사이즈를 추가하여 전송한다.
Body 의 데이터가 2이고, 그 크기는 1 byte 이기때문에 DataSize 부분에는 1을 명시한다.
- SEND_MESSAGE
채팅을 하기 위해 구성한 문자열을 서버에 보낸다. 채팅용 문자열은 가변적이므로 통신의 효율성을
증대시키기 위하여 아래의 그림과 같이 패킷 프레임의 헤더에 문자열의 길이를 추가하여 전송한다.
Body 의 크기가 n bytes 인 경우에 Data Size 부분에는 n 을 명시하면 된다.
- broadcast
서버 프로그램은 전송받은 SEND_MESSAGE 를 접속되어있는 클라이언트들의 운영체에 맞게
재구성하여 클라이언트들에게 전송한다. 이 때 서버가 사용하는 패킷 프레임은 SEND_MESSAGE
프레임과 동일하다. 모든 클라이언트 프로그램은 서버로부터 전송된 문자열을 채팅 리스트에
추가한다.
( 클라이언트 프로그램은 자신이 적은 채팅 내용을 직접 리스트뷰에 넣지 않고 서버로 전송한후에
서버가 전달한 내용을 받아서 추가하는 형태이다. )
2. 클라이언트 프로그램의 주요 기능
이 강좌에서 다루는 예제는 소켓 통신을 중점으로한 간단한 예제이기 때문에 클라이언트 프로그램이
아래의 그림처럼 매우 단순하게 되어있습니다.
이 예제 프로그램은 서버와의 연결이 성공하거나 예외가 발생했을 때의 상황을 프로그램 사용자에게
알리기 위해 텍스트뷰(①) 를 상단에 배치하였으며, 채팅을 하기위하여 문자열을 입력할 수 있는
에디트텍스트(③) 와 입력한 문자열을 서버로 전송할 때 사용하는 버튼(④) 을 하단에 배치하였습니다.
중앙에는 서버에서 Broadcast 된 채팅 문자열이 리스트뷰(②)에 출력됩니다.
3. 클라이언트 구성
자바 언어로 개발되는 안드로이드에서 실행하는 클라이언트 프로그램은 연결과 데이터 수신시에
지속적인 작업을 하게되어 메인 쓰레드가 대기상태에 빠져버리는 경우가 존재하기때문에 새로운
쓰레드를 생성하여 그 곳에서 작업하는 것이 좋습니다.
다음은 클라이언트 채팅 프로그램을 서버 프로그램에 연결하는 방법과 데이터를 송신하는 방법,
데이터를 수신하는 방법 그리고 소켓과 스트림을 종료하는 방법을 알아보도록 하겠습니다.
3.1 서버 프로그램에 연결하기
쓰레드를 시작한 후 서버 프로그램과의 연결을 시도합니다. 연결 전에 해당 소켓에 적용할
옵션들을 설정해주어 네트워크를 사용하면서 발생할 수 있는 문제들을 최소화하고, 소켓 통신을
프로그램에 최적화 시킵니다.
// 생성된 쓰레드가 실행하는 메소드
public void run()
{

try {
// IP 주소와 포트 번호를 관리하는 객체를 생성한다.
SocketAddress sock_addr = new InetSocketAddress("127.0.0.1", 2001);
// 소켓을 생성한다.
m_client_socket = new Socket();
// 수신 버퍼 크기를 1024 바이트로 설정한다.
m_client_socket.setReceiveBufferSize(1024);
// 송신 버퍼 크기를 1024 바이트로 설정한다.
m_client_socket.setSendBufferSize(1024);
// 소켓을 닫을 때 TIME_OUT 에 저장해둔 밀리세컨만큼 대기한 후 닫는다.
m_client_socket.setSoLinger(true, TIME_OUT);
// 15분간 수신되는 데이터가 없으면 연결이 자동으로 끊긴다.
m_client_socket.setSoTimeout(1000*60*15);

// 서버와 연결을 시도한다. TIME_OUT 시간 내에 응답이 오지 않으면 연결을 포기한다.
m_client_socket.connect(sock_addr, TIME_OUT);

// 연결된 경우
if(m_client_socket != null && m_client_socket.isConnected()){
// 서버에서 보내는 데이터를 받을 수 있도록 read 상태에 들어간다.
}
} catch (Exception e) {
// ... 예외 처리
} finally {
// 최종적으로 수행해야할 마무리를 한다..
// 3.4 참고..
}
}
3.2 서버 프로그램에 채팅 데이터 송신하기
버튼에 onClickListener 를 등록하여 버튼이 클릭되면 에디트텍스트에 입력한 문자열을 서버
프로그램으로 전송합니다. 이 때 2바이트로 구성된 데이터 형은 윈도우즈 시스템과 안드로이드
시스템의 기반인 리눅스 시스템이 다른 순서로 배열되기 때문에 윈도우즈 시스템에서 동작하는
서버 프로그램이 정확하게 데이터를 읽을 수 있도록 값을 뒤집어서 전송해줍니다. 이것에 대한
자세한 사항은 아래의 자료를 참고해주시기 바랍니다.
다음 코드는 BufferedOutputStream 객체를 이용하여 문자열 데이터를 전송하는 코드입니다.
// 버튼이 클릭되면 호출되는 메소드
public void onClick(View view)
{
// 소켓이 연결되어 있는 상태인 경우
if(m_client_socket != null && m_client_socket.isConnected()){
try {
// 리소스 파일에 정의된 id_edit 라는 ID의 에디트텍스트를 얻어온다.
EditText edit = (EditText)findViewById(R.id.id_edit);

// 에디트에 적힌 문자열을 byte 배열로 변환한다.
byte[] data = edit.getText().toString().getBytes();
// 바이트의 길이를 얻는다.
int data_size = data.length;

// 바이트의 사이즈를 저장한다.
// 안드로이드의 기반인 리눅스와 윈도우즈는 Byte Ordering 이 다르기때문에
// 2바이트의 데이터를 송수신할 때 1바이트씩 값을 바꿔주어야 한다.
byte[] size = new byte[2];
size[0] = (byte)data_size;
size[1] = (byte)(data_size >> 8);

// 메세지 번호를 쓴다.
m_out_stream.write(NM_SEND_MESSAGE);
// 크기 정보를 쓴다.
m_out_stream.write(size);
// byte 배열을 쓴다.
m_out_stream.write(data);
// 스트림에 쓰여진 정보를 서버로 전송한다.
m_out_stream.flush();
} catch(IOException e) {
m_text_view.setText(e.getMessage());
}
}
}
3.3. 서버 프로그램으로부터 데이터 수신하기
서버 프로그램이 전송하는 채팅 문자열 데이터를 수신하여 리스트뷰에 추가합니다. 데이터의
전송과 마찬가지로 서버 프로그램이 전송한 2바이트 데이터 형의 데이터는 값을 뒤집어서 사용해야
합니다.
데이터를 읽을 때 사용하는 InputStream 클래스의 read 메소드들은 자신이 읽어야할 데이터의
크기만큼 읽을 때까지 대기하게 되며, 프로그램이 정지될 때까지 데이터의 수신과 대기 상태를
반복합니다.
// 추가 thread 가 서버에서 데이터를 수신하기 위해 사용하는 메소드
public void onReadStream() throws IOException
{
byte msg_id;
byte[] size = new byte[2];

// 쓰레드에 인터럽트가 발생하지 않으면 반복한다.
while (!m_client_thread.isInterrupted()) {
// 메세지 번호를 읽는다.
msg_id = (byte)m_in_stream.read();

// 채팅 정보가 수신된 거라면..
if(msg_id == NM_SEND_MESSAGE){
// 크기 정보가 저장된 2바이트를 읽는다.
if(m_in_stream.read(size) == 2){
// 안드로이드의 기반인 리눅스와 윈도우즈는 Byte Ordering 이 다르기때문에
// 2바이트의 데이터를 송수신할 때 1바이트씩 값을 바꿔주어야 한다.
int data_size = 0;
data_size = size[1];
data_size = data_size << 8;
data_size = data_size | size[0];

// 데이터 크기만큼 배열을 할당한다.
byte[] data = new byte[data_size];
// 데이터 크기만큼 데이터를 읽는다.
if(m_in_stream.read(data) == data_size){
// 바이트 데이터를 문자열로 변환한다.
m_recv_string = new String(data);
// 해당 문자열이 리스트뷰에 추가되도록 m_insert_list_run 인터페이스를
// 메인 쓰레드에 전달한다.
m_text_view.post(m_insert_list_run);
}
}
}
}
}
3.4 클라이언트 소켓 종료하기
클라이언트 소켓을 종료할 때에는 입출력 스트림과 소켓이 쓰레드에서 동작하고 있을 수도
있기때문에 정상적으로 종료될 수 있도록 신경써야 합니다.
특히 입력 스트림의 경우 프로그램의 중지 시점임에도 불구하고 read 메소드에 의해 추가된
쓰레드 내에서 대기 상태에 빠져있을 수도 있기 때문에 서버와의 연결이 바로 끊기지 않을 수도
있습니다.
그러므로 소켓 통신을 종료할 때에는 입출력 스트림부터 사용하지 않는 상태로 변경한 후에
쓰레드를 종료시키면서 스트림을 먼저 닫고 그 다음에 소켓을 닫아주는 것이 좋습니다.
// 프로그램이 중지될 때 호출되는 메소드
protected void onStop()
{
// 소켓이 제거되지 않은 경우
if(m_client_socket != null){
try {
// 입출력 스트림의 무효화 작업을 수행한다. 추가된 쓰레드에서 입력 스트림이
// read 메소드에 대기 상태가 걸려있기때문에 이 함수를 호출하여 스트림을 무효화한다.
if(m_in_stream != null) m_client_socket.shutdownInput();
if(m_out_stream != null) m_client_socket.shutdownOutput();

// 쓰레드가 수행중인 경우
if(m_client_thread.isAlive()){
// 추가된 쓰레드에 인터럽트를 건다.
m_client_thread.interrupt();
// 쓰레드가 인터럽트를 감지하여 종료할때까지 기다린다.
// 쓰레드 내부에 있는 try - catch - finally 중에서 finally 부분에
// 스트림과 소켓을 닫는 코드가 있으므로 onStop 에서는 처리하지 않아도 된다.
m_client_thread.join();
}
} catch (Exception e) {
}
}
// 상위 클래스의 onStop 메소드를 수행시킨다.
super.onStop();
}
// 생성된 쓰레드가 실행하는 메소드
public void run()
{
try {
// 서버 연결 및 서버에서 보내는 데이터를 받을 수 있도록 read 상태에 들어간다.
// 3.1 참고...
}
} catch (Exception e) {
// ... 예외 처리
} finally {
// onStop 에서 인터럽트를 걸어서 쓰레드가 종료되거나,
// SO_TIMEOUT 옵션에 의해 연결이 끊긴 경우 등 여러가지 예외가 발생한 경우
// 쓰레드를 빠져나가기 전에 스트림과 소켓을 정리한다.
try {
if(m_client_socket != null) {
// 입출력스트림을 닫는다.
if(m_out_stream != null) m_out_stream.close();
if(m_in_stream != null) m_in_stream.close();

// 소켓을 닫는다.
m_client_socket.close();
m_client_socket = null;
}
} catch (IOException e) {
}
}
}
4. 서버 프로그램 사용하기
PC 에서 동작하는 서버 프로그램은 MFC로 개발되었으며 이번 강좌의 클라이언트 프로그램을
테스트할 수 있도록 실행 파일의 형태로 제공합니다. 그러므로 직접 코드를 구성하고 싶으신 분들은
아래의 자료실 예제를 참고하여 구성해보시기 바랍니다.
API를 이용한 간단한 Socket 프로그램 예제 - http://www.tipssoft.com/bulletin/tb.php/update/217
이 프로그램에는 간단하게 서버에 바로 접속하여 채팅을 할 수 있는 클라이언트가 붙어있어서
안드로이드용 클라이언트 프로그램과 함께 다자간 채팅 테스트를 할 수 있습니다.
해당 서버 프로그램의 사용 방법은 다음과 같습니다.
4.1 서버 서비스 개시하기
프로그램을 실행한 후 좌측의 "서버 기능 시작" 버튼을 누르면 다이얼로그 창이 뜹니다. 서버를
실행한 PC 에서 사용 가능한 IP 주소 목록이 리스트박스에 출력되면 서버 소켓이 bind 할
IP 주소를 선택한 후 확인을 누릅니다.
"서버 서비스를 정상적으로 시작했습니다." 라는 메세지가 상단의 리스트박스에 출력되면 서버가
정상적으로 서비스를 시작한 것입니다.
4.2 서버 프로그램에 붙어있는 클라이언트 사용하기
프로그램 하단의 클라이언트 영역은 원하는 경우 바로 상단의 서버에 접속하여 채팅 테스트를
할 수 있도록 구성되어 있습니다.
클라이언트 영역의 "서버에 접속" 버튼을 누르면 IP 주소를 입력할 수 있는 다이얼로그 창이 뜨며
IP 주소를 정상적으로 입력하고, 확인 버튼을 누르면 상단의 서버와 연결됩니다.
성공적으로 서버에 접속되면 클라이언트 영역의 리스트박스에 "서버에 접속하였습니다." 라는
메세지가 추가됩니다.
4.3 다자간 채팅하기
성공적으로 서버프로그램에 접속한 프로그램 하단의 클라이언트는 "서버로 정보 전송" 버튼을
이용하여 채팅 메세지를 전송할 수 있습니다. 다음의 그림은 하나의 안드로이드용 클라이언트와
서버프로그램 하단의 클라이언트 프로그램이 채팅을 하는 모습입니다.
※ 이 예제의 첨부파일에는 서버 프로그램 실행 파일과 안드로이드용 클라이언트 프로그램을
구성하는 프로젝트 파일이 있습니다.


댓글