Android/App개발2011. 10. 20. 19:06

http://sdw8001.tistory.com/31
 

이번에 정리해 볼 이슈는 안드로이드 개발에 반드시 사용하게 되는 에뮬레이터의 관한 이슈이다.
에뮬레이터가 특별한 이슈가 될 것은 없지만 Network programming 을 할 때는 이슈가 발생한다.
특히, 안드로이드 애플리케이션이 서버로 동작할 때는 더욱더 그러하다.

들어가기에 앞서, 에뮬레이터를 클라이언트로 이용해서 사용하는 경우에는 별다른 이슈가 없다고 하였는데 이미 에뮬레이터에는 브라우저도 있고, WebView 클래스를 이용한 Webkit controller도 띄우는 예제가 있다. 다만, 외부로 접속하고자 할 경우(즉, 인터넷을 사용하는 경우에는) user-permission 셋팅을 해 주어야 하는 것을 잊지 말자.

AndroidManifest.xml 파일의 가장 상위 엘리먼트인 <manifes> 바로 다음 자식 엘리먼트로 다음을 넣어주면 된다.

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

이 부분에 대한 간략한 설명은 이전 포스트에도 살펴볼 수 있다.

http://www.wiseant.net/blog/7 : android.webkit.WebView 를 이용한 데모

자! 그러면 여기서 살펴보고자 하는 것은 안드로이드 애플리케이션을 SocketServer를 이용한 서버로 띄웠을 때의 이슈이다.

그렇다면, 이에 해당하는 부분을 안드로이드 문서에서는 어떻게 정의해 두었을까? 다음의 링크를 통해서 확인할 수 있다.

# emulator reference

# emulator reference networking

일종의 제약 사항이라고 할 수 있는데 에뮬레이터의 네트워크 아키텍쳐를 virtual router/firewall 을 사용하여 내부적으로 에뮬레이터내에 IP를 할당하고 있는 것을 볼 수 있다.
이렇게되어 있다보니 로컬 시스템의 IP를 사용할 것으로 생각하면 오산인 것이다. 일단은 위의 문서를 토대로 대략적인 것을 살펴볼 수 있고, Network redirerction이란 것도 볼 수 있다.

안드로이드 에뮬레이터에서 Network redirection은 에뮬레이터가 가지는 VLAN과 로컬 시스템의 포트를 연결(파이프로 연결한다고 생각하면 된다)해 주는 개념이다.
즉, 로컬 시스템의 포트로 들어오는 패킷을 에뮬레이터의 포트로 넘겨주는 식이다(Forward 개념과 비슷하다).

좀 복잡한 듯 하니 그냥 간단한 테스트 소스와 설정하는 방법을 통해서 알아보자.
먼저, 안드로이드 애플리케이션에 올라가는 간단한 서버 소켓을 여는 프로그램을 만들어보자. 참고로 안드로이드 전체 소스는 생략한다. 많은 안드로이드 기초 강좌 등에서 찾아볼 수 있다.

    private String host = "localhost";
    private String port = 8002;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);

        status = (TextView)findViewById(R.id.status);
        status.setText("do something");

        Button btnRunServer = (Button)findViewById(R.id.runserver);
        btnRunServer.setOnClickListener(new Button.OnClickListener() {
         public void onClick(View v) {
          // run server
          try {
           ServerSocket serverSocket = new ServerSocket(port);
           
           status.setText(status.getText() + " waiting");
           Log.d("Green", "Waiting...");
           
           Socket player1 = serverSocket.accept();

           status.setText(status.getText() + " Connected");
           Log.d("Green", "Connected");

           player1.close();
          }
          catch (Exception ex) {
           status.setText(status.getText() + ex.getMessage());
          }
         }
        });

        Button btnConnect = (Button)findViewById(R.id.connect);
        btnConnect.setOnClickListener(new Button.OnClickListener() {
         public void onClick(View v) {
          // connect to server
          try {
           Socket socket = new Socket(host, port);

           DataInputStream fromServer = new DataInputStream(socket.getInputStream());
           DataOutputStream toServer = new DataOutputStream(socket.getOutputStream());

           status.setText("connected");
           Log.d("Green", "Connected...");
           
           toServer.writeInt(1337);
          }
          catch (Exception ex) {
           status.setText(ex.getMessage());
          }
         }
        });
    }

위의 소스에서는 ServerSocket을 열어놓고(Listening), accept 시에 Connected 메시만을 뿌려주는 간단한 서버용 애플리케이션이다.
connect 버튼을 클릭 시에는 지정된 host로 접속해 보는 소스이다.

안드로이드 에뮬레이터에서 접속 문제를 다루고 있음으로 여기선 ServerSocket 등에 대해서는 자세하게 다루지 않는다.

일단 위의 소스를 기반으로 하여 안드로이드를 구동해 보자.

일단 위와 같이 애플리케이션이 구동되면 첫 번째 버튼을 클릭해서 ServerSocket을 띄워준다. 이제 안드로이드 서버 애플리케이션과 연동할 간단한 클라이언트 소스를 살펴보자.

/**
 * 
 */
package com.mediachorus.test.android.network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

/**
 * @author Sang-Hyup Lee
 * @version 1.0
 *
 */
public class ClientTest {

 /**
  * @param args
  */
 public static void main(String[] args) {
  String host = "localhost";
  int port = 8002;
  // TODO Auto-generated method stub
  try {
   Socket socket = new Socket(host, port);

   DataInputStream fromServer = new DataInputStream(socket.getInputStream());
   DataOutputStream toServer = new DataOutputStream(socket.getOutputStream());

   System.out.println("IP:" + socket.getInetAddress().getHostAddress()
       + ", port:" + socket.getPort());
   System.out.println("Connected...");
      
   toServer.writeInt(1337);
   // toServer.write("quit".getBytes());
   toServer.flush();
   
   System.out.println("Done...");
  }
  catch (Exception ex) {
   System.err.println(ex.getMessage());
  }
 }
}

클라이언트 소스 역시 설명할 것도 별로 없다. 자바 네트워크 프로그램으로 서버에 접속해서 패킷을 보내어 본다. 로그를 통해서 접속이 되었는지 확인만 해 보는 것이다.

클라이언트 소스를 실행해 보자. 다음과 같은 Exception을 발생시킨다.

Connection refused: connect

커넥션이 이루어지지 않았다는 말인데, 우리의 의도는 안드로이드 애플리케이션 TCP 서버 소켓에 접속하는 것이다. 동일한 PC 에서 이루어지는데 localhost 라고 하면 되는 줄 알았는데 아니다.

여기서 위의 구글 문서 링크에서 나온 것처럼 Network redirection이 필요하다.
다음과 같이 command 상에서 telnet 접속부터 시작해서 실행해보자. 참고로 에뮬레이터는 5554 포트를 기본으로 할당하여 telnet으로 접속이 가능하다.

telnet localhost 5554
redir add tcp:8002:8002 

사용자 삽입 이미지

위와 같이 로컬 시스템이 8002 포트를 사용하지 않으면 OK 메시지로 응답한다. 참고로 redir 사용법을 보려면 "redir help"라고 입력하면 된다.
위의 명령어 뜻은 다음과 같다.

TCP 형태로 redirection을 하는데, 로컬 시스템의 8002 포트를 VLAN(에뮬레이터에 올라간 안드로이드 애플리케이션) 8002 포트로 연결(redirection) 해 준다.

add <protocol>:<host-port>:<guest-port>

그렇다면 redirection을 해지하는 방법은 add를 del로 바꾸어주면 된다. 다음과 같은 형태이다.

redir del tcp:8002


del 옵션을 사용시에는 system host port만 입력해 주면 된다.

자, 이 상태에서(redirection을 걸어둔 상태에서) 클라이언트 프로그램을 실행해 보자. Connected 메시지를 볼 수 있을 것이다.

이렇게해서 기본적인 Network redirection을 해결할 수 있는데, 클라이언트 소스 코드를 '127.0.0.1'로 host 값을 바뀌어서 해 보자.
(결론은? 잘 된다. 어쩌면 당연한 것을?^^)

하지만, 로컬 시스템이 가지고 있는 IP를 host 값으로 수정하고 실행해 보자. 다시금 "Connection refused: connect" 에러가 발생한다.
그렇다면, 안드로이드 애플리케이션이 실행하고 있는 시스템이 아닌 다른 시스템에서의 접속은? 당연히 안 된다.

문제는 여기에 또 있는 것이다. Server/Client 프로그램은 당연히 서로 다른 시스템의 연결을 지양하고 있는 것인데 이런 문제가 발생한다.
이는 안드로이드 에뮬레이터가 동작시에 localhost에 해당하는 127.0.0.1을 개발 머신의 loopback interface로 alias를 해주기 때문이다.
결국은 로컬 시스템의 IP를 할당하여 수행하지 못한다는 것이다.

이를 해결하기 위해서 다음의 링크를 통해서 stcppipe.exe를 실행해 주어야 한다. 링크에는 sudppipe.exe도 포함되어 있다. 소스와 함께.

Simple UDP proxy/pipe 0.3a  : http://aluigi.org/mytoolz/sudppipe.zip
Simple TCP proxy/pipe 0.4.4a : http://aluigi.org/mytoolz/stcppipe.zip

사용법은 다음과 같다.

stcppipe.exe -b <local IP> <remote hosts> <local port> <remote port>
remote hosts : 127.0.0.1
remote port : emulator 의 host port

이를 응용해서 실행해 보면 다음과 같이 입력할 수 있다.

stcppipe -b 192.168.0.86 l27.0.0.1 8002 8002

이렇게 하고 다시금 로컬에서든 다른 시스템에서든지 Client 프로그램을 실행해보자.
stcppipe 프로그램을 통해서 IN/OUT 되는 포트도 확인해 볼 수 있다. UDP도 마찬가지이다. 각자 테스트해 보는 것도 좋을 거 같다.


이렇게해서 안드로이드 에뮬레이터의 Network redirection 이슈를 마친다. 끝으로 이 문제는 혼자서 고민하고 해결한 것이 아니라 kandroid.org 의 운영자이신 들풀님의 큰 도움이 있었다. 이에 감사의 뜻을 전하며 해당 부분을 Q&A를 통해서 해결하였는데, 링크를 걸어둔다.


# 처음 Emulator IP 관련된 질문후, redir (option)에 대해서 알게된 링크

# 로컬 시스템 내에서의 접속이 아닌 원격 시스템에서의 접속 처리를 해결하게 된 링크

두 번째 링크에도 포함된 링크인데 구글 그룹스에서도 똑같은 고민을 했었고 동일하게 해결한 것을 찾을 수 있다.



다시금 함께 고민해 주신 들풀님에게 감사의 뜻을 전한다.


[마치며]
지금까지의 Network redirection을 통해서 다양한 것을 개발해 보았다. 하지만 실제 개발에서 사용하는 것이 TCP, UDP를 함께 사용하면서 까다로운 네트워크 프로그램을 처리하는 것이었다. 특이한 점은 윈도우에서는 정상적으로 처리되지 않을 때가 많았다.
그래서 Ubuntu 에서 실행해 보니 접속에 대한 문제는 거의 발생하지 않았다. 이러한 원인에 대한 분석은 현재로서는 어려울 것 같아서 다루지 않겠다.
그냥 경험담이라고 생각해 주면 좋겠다.

모두들... 더 좋은 정보나 자료가 있으면 공유해 주세요~^^ 
Posted by 삼스