레이블이 안드로이드 네트워킹인 게시물을 표시합니다. 모든 게시물 표시
레이블이 안드로이드 네트워킹인 게시물을 표시합니다. 모든 게시물 표시

2014년 6월 4일 수요일

Bluetooth Programming in Android (2/2)

이전 포스트(Bluetooth Programming in Android (1/2))에 이어 이제부터는 실제로 블루투스 디바이스와 연결해 데이터를 보내는 방법에 대해 이야기하겠다.

Profile and UUID


블루투스 표준은 블루투스 디바이스들이 제공하는 기능들에 대한 몇가지 프로파일을 정의하고 있다.

간단하게 말하자면 블루투스 프로파일은 디바이스가 무엇을 할 수 있는가에 대응한다. 예를 들어 블루투스 헤드셋은 오디오 스트림을 어떻게 주고 받고, 페어링 된 휴대폰에 기본적인 명령(응답, 볼륨 조절 등)을 어떻게 보내는가를 정의한 HSP(Handset Service Profile)을 구현한다. 일부 고급 헤드셋은 A2DP(Advanced Audio Distribution Profile)도 구현해서 사용자가 휴대폰에서 스트리밍하는 음악도 고음질 스테레오로 들을 수 있게 해 준다.



연결을 설정할 때, 연결을 시작하는 디바이스는 SDP(Service Discovery Protocol) 프로토콜을 사용해서 상대방 디바이스가 어떤 서비스를 제공하는가, 즉 어떤 프로파일을 구현했는가를 알아낼 수도 있다.

각 서비스는 128비트 숫자 식별자(UUID)를 사용해 정의되어 있다. 일반적으로는 이 식별자의 짧은 형태가 사용된다.

  • 0x00000000-0000-1000-8000-00805f9b34fb같은 128비트 베이스 UUID가 정의되어 있다.
  • 서비스 UUID의 짧은 형태는 앞쪽의 8개 0을 대치한다.
  • 이것이 완전한 서비스 UUID이다.
예를 들어 HSP 서비스의 짧은 형태 UUID는 0x1108이므로 완전한 UUID는 0x00001108-0000-1000-8000-00805f9b34fb가 된다.

SPP


가장 간단하고 임베디드 디바이스와 통신하는데 가장 많이 사용되는 것이 Serial Port Profile(SPP)로 짧은 UUID는 0x1101이다.

이 프로파일은 두 디바이스간 시리얼 링크를 에뮬레이션 한다.

Android


이전 예제에서 스마트폰에 페어링 된 디바이스들을 어떻게 나열하는가를 배웠다. 각 디바이스는 BluetoothDevice 오브젝트의 인스턴스에 대응한다. 이 오브젝트는 통신채널을 열기 위해 두 가지 메소드를 제공한다.

  • createRfcommSocketToServiceRecord(UUID)
  • createInsecureRfcommSocketToServiceRecord(UUID)
두 메소드는 프로파일의 UUID를 요구하고, 첫번째 메소드는 암호화 된 커넥션을 만드는것만 다르다.
메소드가 성공하면 스마트폰과 페어링 된 디바이스간 통신 채널에 대응하는 BluetoothSocket 오브젝트를 리턴한다.

SPP 프로파일을 구현한 디바이스에 어떻게 데이터를 보내는 지 보도록 하자.

먼저 프로파일의 UUID를 정의한다.

UUID SPP_UUID = java.util.UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

그리고 나서 디바이스와의 통신 채널을 가져온다.

BluetoothSocket btSocket = null;
try {
  btSocket = targetDevice.createInsecureRfcommSocketToServiceRecord(SPP_UUID);
} catch (IOException e) {
  Toast.makeText(this, "Unable to open a serial socket with the device", Toast.LENGTH_SHORT).show();
}

이 지점은 아직 채널이 열리지 않았으니 connect() 메소드를 사용해 디바이스와 연결한다.

 try {
  btSocket.connect();
} catch (IOException e) {
  Toast.makeText(this, "Unable to connect to the device", Toast.LENGTH_SHORT).show();
}

연결되면 BluetoothSocket 오브젝트는 두 개의 Stream을 제공한다. 하나는 데이터를 보내기 위한 것(OutputStream)이고 나머지는 데이터를 받기 위한 스트림(InputStream)이다. 편하게 사용하기 위해 스트림을 통해 글자들을 쉽게 보낼 수 있게 해 주는 OutputStreamWriter 오브젝트를 사용할 수 있다.

try {
  OutputStreamWriter writer = new OutputStreamWriter(btSocket.getOutputStream());
  writer.write("Hello World!\r\n");
  writer.flush();
} catch (IOException e) {
  Toast.makeText(this, "Unable to send message to the device.", Toast.LENGTH_SHORT).show();
}

StreamWriter는 로컬 버퍼를 가지고 있다. 모든 데이터가 보내졌는지 확실하게 하기 위해 마지막에 flush()를 호출하는걸 잊지 말자.

마지막에 소켓을 닫는걸 잊으면 안된다.

try {
  btSocket.close();
} catch (IOException e) {
  Toast.makeText(this, "Unable to close the connection to the device", Toast.LENGTH_SHORT).show();
}

Say hello to...


이 앱은 첫번째 코드를 확장했다. 소스코드는 역시 저자의 github에서 다운받을 수 있다.
  • 폰에 페어링 된 디바이스를 가져온다.
  • 한 디바이스를 클릭하면 SPP를 사용해 연결한 다음 "Hello World!" 문자열을 보낸다.

앱을 테스트 해 보려면 블루투스가 장착된 PC가 필요하고 incoming connection을 허용하도록 설정 해 줘야 한다.

윈도우 트레이에 있는 블루투스 아이콘 위에서 마우스 오른쪽 버튼을 클릭한 다음 Open Settings를 선택한다.


COM Ports 탭을 선택한 다음 Add...을 클릭한다.


Incoming을 선택한다.


새 시리얼 포트의 이름을 기록해 둔다. 여기서는 COM56 이다.


터미널 에뮬레이터(여기서는 PuTTY를 사용)를 사용해서 시리얼 포트에 연결한다.


모든게 문제 없이 잘 되었으면 스마트폰에서 앱을 실행하고 PC를 클릭하면 앱이 블루투스를 통해 메시지를 보내고 전송 확인 메시지가 표시된다.

그리고 PC를 확인해 보면 전송된 메시지가 화면에 표시된다.





위의 앱을 테스트 해 보면 한가지 작은 문제점을 발견할 수 있었을 것이다. 커넥션이 연결되고 “Hello World” 메시지가 보내질 때 까지 앱의 GUI가 응답하지 않는다. 이유는 간단하다. 대부분의 메소드는 “blocking”을 사용한다. 즉 결과를 얻을 때 까지 (또는 타임아웃이 될 때 까지) 프로세스의 실행을 중단한다.

데이터를 예를 들어 만일 소켓에서 데이터를 가져오기 위해 read() 메소드를 호출하면 읽어 올 데이터가 들어올 때 까지 메소드는 실행을 중단한다.

이 문제를 해결하기 위해서 멀티태스킹 앱, 즉 앱이 다른 프로세스를 가지고 있어 각각이 서로 독립적으로 실행되는 앱을 작성하는 법을 배워야만 한다. 


스레드와 GUI


간단하게 하기 위해 앱은 한개 또는 그 이상의 프로세스(스레드)로 구성될 수 있고, 이 프로세스들은 안드로이드 OS에 의해 병렬로 실행된다. 이전 예제와 같이 간단한 앱은 메인 스레드라 불리는 하나의 스레드만 가지고 있다. 이 스레드는 앱의 GUI를 구성하는 컴포넌트들(텍스트 박스, 이미지, 버튼 등)을 관리한다.

멀티스레드 앱을 작성할 때 고려해야만 하는 첫번째 규칙은 “메인 스레드만이 GUI를 업데이트 할 수 있다”는 것이다.


이 규칙은 종종 프로그래머를 골치아프게 만든다. 별도 스레드가 블루투스 소켓에서 데이터를 받는 때를 생각해보자. 일반적으로 명령을 받으면 그에 따라 GUI를 업데이트 해야 한다…

보통 권장되는 해결책은 메인스레드에게 GUI를 업데이트 해 주도록 요구하는 것이다. 여기서는 AsyncTask 오브젝트를 사용하는 다른 방법을 소개하겠다.

AsyncTask


AsyncTask 오브젝트는 안드로이드에 포함되어 백그라운드에서 실행되면서 앱의 GUI와 상호작용을 해야만 하는 태스크들을 쉽게 관리할 수 있게 해 준다.

장점으로는 일부 메소드는 GUI(메인) 스레드에서 실행되고 나머지는 독립된 전용 스레드에서 실행된다는 것이다.

그러므로 개발자는 인터페이스를 업데이트 하기 위해서는 GUI 스레드에서 실행되는 메소드를 사용하고, (예를 들어, 소켓을 통해 데이터를 전송/수신하는것 같이) 메인 스레드를 블럭하면 안되는 백그라운드 동작은 두번째 스레드에서 실행되게 할 수 있다.


메소드들을 좀 더 자세히 보도록 하겠다.

  • onPreExecution() - GUI - 백그라운드 액티비티를 시작하기 바로 전에 실행. 사용자에게 애니메이션이나 메시지 등으로 요구한 동작이 시작된다는 것을 알려주는데 사용할 수 있음
  • doInBackground() - background - 백그라운드 태스크를 수행하는 메인 메소드
  • publishProgress() - background - 보통 doInBackground()에서 호출되어 테스크가 실행되는 중에 “progress”를 알려주는데 사용
  • onProgressUpdate() - GUI -  pubilshProgress()에 의해 호출되 GUI가 실행의 “progress”를 업데이트 할 수 있게 함
  • onPostExecute() & onCancelled() - GUI -  태스크의 끝에 (테스크가 캔슬될 때) 실행되는 메소드 
  • Android application

이 튜토리얼을 위해 개발한 안드로이드 앱은 블루투스를 통해 데이터를 송수신하는 것이다.

소스코드는 저자의github에서 다운받을 수 있다.

  • 사용자가 툴바에 있는 버튼으로 디바이스와 연결/연결해제를 할 수 있음
  • 사용자가 두 버튼중에 하나를 클릭하면 각각 “BUTTON1”, “BUTTON2” 명령을 보냄
  • 수신한것을 TextView에 표시

블루투스를 통한 통신은 BTAsyncTask라는 AsyncTask를 사용해 수행된다. 이것이 어떻게 동작하는지 확인해보자.

Connection


사용자가 페어링 된 디바이스를 선택하면 앱은 그 디바이스로 소켓을 오픈하고 BTAsyncTask 오브젝트의 새 인스턴스를 만들어 BluetoothAdapter에서 얻은 소켓을 넘겨준다.

그리고 나면 앱은 BTAsyncTask의 doInBackground 메소드를 시작시키는 execute() 메소드를 호출해서, 페어링 된 디바이스에서 새 데이터가 들어오기를 기다린다.



Data In


BTAsyncTask가 새 데이터를 받으면 publishProgress()를 호출하면서 데이터를 넘겨준다. 위에서 본 것 처럼 publishProgress() 메소드는 내부적으로 GUI 스레드레서 실행되고 있는 onProgressUpdate() 메소드를 호출해 수신한 데이터를 가지고 GUI를 업데이트 할 수 있다. 



Data Out


사용자가 버튼을 클릭하면 메인 스레드는 BTAsyncTask의 sendCommand() 메소드를 호출해 해당되는 명령을 보낸다.



데모


안드로이드 앱에 응답하기 위한 간단한 .Net 어플리케이션을 만들었다. 다음은 앱이 어떻게 동작하는가를 보여주는 짧은 비디오이다.





* 이 글은 Luca Dentella의 튜토리얼 시리즈 Android e Bluetooth 를 저자의 승락을 받고 번역한 글입니다. 흔쾌히 허락해 준 저자 Luca에게 감사드리며...



Un enorme grazie a Luca~

Bluetooth Programming in Android (1/2)

안드로이드 앱에서 블루투스를 사용하려면 각각 기능에 해당하는 permission이 필요하다. Permission은 안드로이드에게 앱이 어떤 기능을 필요로 하는지 알려주는 역할을 한다. 앱을 설치할 때 어떤 permission을 필요로 하는지 알려주고 허가할 것인지 물어보는 창이 열리는걸 본 적이 있을 것이다.

앱이 어떤  permission을 사용하는지 알려주기 위해 필요한 작업은 다음과 같다.

먼저 프로젝트의 AndroidManifest.xml 파일을 연다.


이 파일을 열면 아래와 같은 화면이 나오는데 'Permissions' 탭을 선택한 다음 오른쪽의 Add... 버튼을 누른다.



버튼을 누르면 나오는 화면에서 Uses Permission을 선택한다.



그 다음 android.permissions.BLUETOOTH를 선택하고 파일을 저장한다.



BluetoothAdapter


BluetoothAdapter는 폰의 블루투스 모듈을 사용하기 위한 오브젝트이다.

스태틱 메소드인 getBluetoothAdapter()를 호출해서 오브젝트의 디폴트 인스턴스를 가져온다.

mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

만일 폰에 블루투스 모듈이 없으면 null을 리턴한다.  이 경우 Toast를 사용해 에러메시지를 표시하고 앱을 종료한다.

Toast.makeText(this, "This app requires a bluetooth capable phone", Toast.LENGTH_SHORT).show();
finish();

블루투스 모듈에 대한 인스턴스를 가져온 다음 모듈이 활성화 되어 있는지 확인해야만 한다. 모듈 활성화 여부를 확인하는데 isEnabled() 메소드를 사용한다.

mBluetoothAdapter.isEnabled();

모듈이 활성화 되어 있지 않으면 Intent를 사용해 사용자에게 활성화 시키도록 요청할 수 있다. 먼저 request를 식별하기 위한 상수를 정의한 다음 새 Intent를 만들어 OS에 보낸다.

private final int REQUEST_ENABLE_BT = 1;
...
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);

앱은 백그라운드로 들어가고 아래와 같이 사용자에게 앱이 블루투스를 활성화 시키도록 허가할 것인지 묻는 메시지 팝업이 열린다.



사용자가 request를 허가(또는 거부)하면 안드로이드는 앱의 onActivityResult 메소드를 호출해서 request의 허가/거부를 확인할 수 있다.

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  if (requestCode == REQUEST_ENABLE_BT)
    if (resultCode == RESULT_OK) listPairedDevices();

페어링 된 디바이스


첫번째 예제에서 앱은 페어링 된 디바이스를 화면에 표시한다. 블루투스 디바이스는 연결해서 사용하기 전에 먼저 페어링 되어야만 한다.

getBondedDevices() 메소드는 BluetoothDevice 오브젝트 집합을 리턴한다.

Set pairedDevices = mBluetoothAdapter.getBondedDevices();

각 디바이스는 이름과 (서로 다른) 주소를 가진다. 루프를 사용해 TextArea에 페어링 된 디바이스들을 표시한다.

for (BluetoothDevice pairedDevice : pairedDevices) {
  textView2.append(Html.fromHtml("<strong>" + pairedDevice.getName() + "</strong>"));
  textView2.append(" (" + pairedDevice.getAddress() + ")\n");
}

아래가 앱의 스크린 캡춰이다.



여기서 사용한 소스코드는 전부 저자의 github에서 다운받을 수 있다.

 Discovery 와 Permissions


새 블루투스 디바이스를 찾기 위한 discovery 기능을 사용하려면 새로운 permission(BLUETOOTH_ADMIN)이 필요하다.



새 검색을 시작하려면 BluetoothAdapter 오브젝트의 startDiscovery 메소드를 호출한다.

mBluetoothAdapter.startDiscovery();

이 메소드는 비동기 메소드이다. 즉 이 메소드를 호출하면 안드로이드에게 새 검색을 시작하라는 요청을 넘겨주고 바로 리턴한다. 새 디바이스가 발견되거나 discovery가 끝난걸 앱에게 알려주기 위해서는 BroadcastReceiver를 사용해야만 한다.

Receiver 와 IntentFilter


앱은 안드로이드에게 어떤 이벤트가 발생한 걸 통보해 달라고 요청할 수도 있다. 예를 들어 문자메시지가 오거나 배터리가 거의 없을 때 같은 경우가 될 수 있다. 이런 이벤트들은 broadcast 메시지를 사용해 통보된다. 그러므로 앱은 BroadcastReceiver 오브젝트를 통해 메시지를 받을 수 있다.

BroadcastReceiver 오브젝트가 인스턴스화 되고 나면 OS에게 어떤 notification을 받을건지 알려줘야만 한다. 받기를 원하는 각 이벤트마다 필터(IntentFilter)를 만든다. 그리고 registerReceiver() 메소드를 사용해 안드로이드가 IntentFilter에 지정되어 있는 이벤트를 주어진 BroadcastReceiver에게 통보하도록 요구한다.



이 예제에서 우리는 2개의 이벤트를 모니터링 할 필요가 있다.
  • Bluetooth.ACTION_FOUND - 새 디바이스가 발견될 때
  • Bluetooth.ACTION_DISCOVERY_FINISHED - discovery가 끝날 때
해당하는 IntentFilter를 만들어 receiver를 등록한다.

IntentFilter deviceFoundFilter = new IntentFilter(Bluetooth.ACTION_FOUND);
IntentFilter discoveryFinishedFilter = new IntentFilter(Bluetooth.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, deviceFoundFilter);
registerReceiver(mReceiver, discoveryFinishedFilter);

BraodcastReceiver는 onReceive() 메소드를 꼭 만들어줘야만 한다. 이 메소드는 새 이벤트가 발생한 걸 알려주기 위해 안드로이드에 의해 호출된다.

mReceiver = new BroadcastReceiver() {
  public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();

action 스트링을 보면 어떤 이벤트가 발생했는지 알 수 있다.

if (BluetoothDevice.ACTION_FOUND.equals(action)) {
  // show the new device
}
if (BluetoothDevice.ACTION_DISCOVERY_FINISHED).equals(action)) {
  // enable the SCAN button
}


이 예제를 위한 앱은 매우 간단하다. 버튼을 누르면 검색을 시작하고 장치가 발견되면 리스트에 장치의 이름과 주소를 추가한다. 역시 소스코드는 저자의 github에서 다운받으면 된다.


Detect a state change


안드로이드는 멀티태스킹 OS이다. 당신의 앱이 실행되는 동안 다른 앱이나 사용자가 블루투스 모듈의 상태를 바꿔 놓을수도 있다. 예를 들어 모듈을 비활성화 시켜 버릴 수 있다. 그렇기 때문에 안드로이드에게 모듈의 상태에 어떤 변화가 생기면 그걸 통보하도록 요청할 수 있다.

먼저 OS에서의 notification을 받을 BroadcastReceiver를 만든다.

mReceiver = new BroadcastReceiver() {

이 receiver의 onReceive() 메소드에서 들어 온 메시지가 블루투스 모듈의 상태변화에 관한 것인지 확인한다.

public void onReceive(Context context, Intent intent) {
  String action = intent.getAction();
  if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {

상태변화에 관한 것이 맞다면, Intent의 extra parameter에서 변화된 현 상태를 가져올 수 있다. state는 int값이므로 getIntExtra 메소드를 사용해야만 하고 메소드를 호출할 때 파라메타의 이름과 디폴트값을 지정해주면 된다. 디폴트값은 지정한 이름의 파라메타가 없을 때 리턴할 값이다.

int actualState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.STATE_OFF);

그리고 receiver를 등록하는걸 잊으면 안된다.

IntentFilter stateChangedFilter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(mReceiver, stateChangedFilter);

블루투스 모듈을 위한 4개의 상태가 정의되어 있다.

  • BluetoothAdapter.STATE_ON
  • BluetoothAdapter.STATE_OFF
  • BluetoothAdapter.STATE_TURNING_ON
  • BluetoothAdapter.STATE_TURNING_OFF
위에 설명한 걸 정리한 앱은 다음과 같다. 앱은 화면에 텍스트와 아이콘으로 현재 상태를 나타내고 상태가 변경될 때 마다 내용을 자동으로 업데이트 한다. 아래 데모 앱의 소스는 저자의 github에서 다운받으면 된다.







* 이 글은 Luca Dentella의 튜토리얼 시리즈 Android e Bluetooth 를 저자의 승락을 받고 번역한 글입니다. 흔쾌히 허락해 준 저자 Luca에게 감사드리며...



Un enorme grazie a Luca~





2010년 10월 15일 금요일

안드로이드 에뮬레이터 네트워킹 설정 (Android Emulator Networking)

Network address space

실행되는 각 에뮬레이터 인스턴스는 개발머신의 네트웍 인터페이스나 설정과 분리된 가상 라우터/방화벽 뒤에서 실행된다. 에뮬레이트 되는 디바이스는 개발머신이나 다른 에뮬레이터 인스턴스를 볼 수가 없다. 이더넷을 통해 라우터/방화벽에 연결된걸로만 보이게 된다.

각 인스턴스를 위한 가상 라우터는 10.0.2/24 네트웍 주소 공간을 관리한다. - 라우터가 관리하는 모든 주소는 10.0.2.<xx>의 형태를 가지고 여기서 <xx>는 숫자이다. 이 공간내의 주소는 에뮬레이터/라우터에 의해 다음과 같이 미리 할당되어 있다.

Network Address      Description
-------------------  ------------
10.0.2.1             Router/gateway address
10.0.2.2             Special alias to your host loopback interface (i.e., 127.0.0.1 on your development machine)
10.0.2.3             First DNS server
10.0.2.4 / 10.0.2.5 / 10.0.2.6 Optional second, third and fourth DNS server (if any)
10.0.2.15            The emulated device's own network/ethernet interface
127.0.0.1            The emulated device's own loopback interface 

실행되는 모든 에뮬레이터 인스턴스는 동일한 주소 할당을 사용하고 있음을 주의해라. 즉 한 개발머신에 두개의 에뮬레이터 인스턴스가 동시에 실행되고 있을 때 각각의 에뮬레이터는 자신의 라우터를 가지고 그 라우터 뒤에서 동일한 IP 주소인 10.0.2.15를 가지게 된다. 에뮬레이터 인스턴스는 라우터로 분리되고 서로를 볼 수 없다.

또한 개발머신의 127.0.0.1은 에뮬레이터의 자체 루프백 인터페이스에 대응한다. 개발머신의 루프백 인터페이스(127.0.0.1)에서 실행되고 있는 서비스를 억세스 하고 싶으면 127.0.0.1 대신 특별 주소인 10.0.2.2를 사용해야 한다.

마지막으로 각 에뮬레이터 디바이스의 미리 할당된 주소들은 안드로이드 에뮬레이터에 한정된 것이고 실제 디바이스에서는 완전히 다를 수도 있음을 주의해야만 한다.


Local Networking Limitations

각 에뮬레이터 인스턴스는 가상 라우터 뒤에서 실행되지만 실제 라우터를 통해 인터넷에 연결되는 실제 디바이스와는 다르게 에뮬레이터 디바이스는 물리 네트웍을 억세스 할 수 없다. 그 대신 개발머신상의 일반적 어플리케이션의 일부분으로 실행된다. 즉 개발머신에서 실행되는 다른 어플리케이션같이 동일 네트웍 제한을 받게 된다.

   * 에뮬레이터 디바이스와의 통신은 개발머신에서 실행되는 방화벽 프로그램에 의해 막힐 수도 있다.
   * 에뮬레이터 디바이스와의 통신은 개발머신이 연결되어 있는 다른 (물리적) 방화벽/라우터에 의해 막힐 수도 있다.

에뮬레이터의 가상 라우터는 개발머신의 네트웍 환경이 허용해주면 모든 outbound TCP/UDP 커넥션/메시지를 에뮬레이터 디바이스의 대리로 처리할 수 있어야 한다. 호스트 OS와 네트웍에 의해 제한되지 않으면 포트번호나 범위에 대한 별다른 제한은 없다.

환경에 따라 에뮬레이터는 다른 프로토콜을 지원하지 않을 수 있다. 현재 에뮬레이터는 IGMP나 멀티캐스트를 지원하지 않는다


Using Network Redirections

가상 라우터 뒤에 있는 에뮬레이터 인스턴스와 통신하기 위해서는 가상 라우터의 네트웍 리다이렉션을 설정해 줘야 한다. 그러면 클라이언트는 라우터의 지정된 게스트 포트로 연결하고 라우터가 그 포트로 오가는 트래픽을 에뮬레이터 디바이스의 호스트 포트로 포워딩하게 된다.

네트웍 리다이렉션을 설정하려면 에뮬레이터 인스턴스에서 호스트/게스트 포트/주소를 매핑해야 한다. 네트웍 리다이렉션을 설정하는데 두가지 방법이 있다. 에뮬레이터 콘솔 명령어를 사용하거나 ADB 툴을 사용하면 된다.


Setting up Redirections through the Emulator Console

각 에뮬레이터 인스턴스는 그 인스턴스에 명령어를 실행할 수 있도록 연결할 수 있는 제어 콘솔을 제공한다. 리다이렉션을 설정하려면 redir 콘솔 명령어를 사용할 수 있다.

먼저 타겟 에뮬레이터 인스턴스의 콘솔 포트 번호를 알아야 한다. 예를 들어 첫번째 실행된 에뮬레이터 인스턴스를 위한 콘솔포트는 5554이다. 다음 명령어를 사용해서 타겟 에뮬레이터 인스턴스를 위한 콘솔에 연결한다.

% telnet localhost 5554

연결되면 redir 명령어를 사용해서 리다이렉션을 설정할 수 있다. 리다이렉션을 추가하려면 다음과 같다.

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

여기서 <protocol>은 tcp 또는 udp, <host-port>와 <guest-port>는 각각 개발머신과 에뮬레이터 시스템간의 매핑을 설정한다.

예를 들어 호스트(개발머신)의 127.0.0.1:5000으로 들어오는 모든 TCP 커넥션을 에뮬레이터 디바이스의 10.0.2.15:6000으로 리다이렉션 하도록 설정하려면 다음의 명령어를 사용하면 된다.

redir add tcp:5000:6000

리다이렉션을 삭제하려면 redir del 명령어를 사용하면 된다. 특정 에뮬레이터 인스턴스의 모든 리다이렉션 목록을 보고 싶으면 redir list명령어를 사용한다.

포트번호는 로컬 환경에 의해 제한된다는걸 주의해라. 즉 일반적으로 관리자 권한이 없으면 1024번 이하의 포트 번호는 사용할수가 없다. 또한 호스트의 다른 프로세스가 이미 사용하고 있는 호스트 포트를 리다이렉션 하려고 하면 redir은 에러메시지를 발생한다.


Setting Up Redirections through ADB

ADB 툴로 포트 포워딩을 설정할수도 있다.  또한 현재 ADB는 ADB서버를 kill하는것 이외에는 리다이렉션을 없앨 수 있는 방법을 제공하지 않는다. 상세한 내용은 ADB 문서의 Forwarding Ports를 참고하면 된다.


Configuring the Emulator's DNS Settings

처음 시작될 때 에뮬레이터는 호스트가 현재 사용하고 있는 DNS 서버 목록을 읽는다. 그래서 그 목록중에 최대 4개까지의 서버 주소를 기록하고 각각의 주소를 에뮬레이터의 10.0.2.3, 10.0.2.4, 10.0.2.5, 10.0.2.6으로 alias한다.

리눅스와 OS X에서 에뮬레이터는 /etc/resolv.conf 파일을 파싱해서 DNS 서버 주소를 가져온다. 윈도우에서는 GetNetworkParams() API를 호출해서 주소를 얻어온다. 즉 에뮬레이터는 hosts 파일(리눅스나 OS X에서는 /etc/hosts, 윈도우는 %WINDOWS%\system32\HOSTS)의 내용을 무시한다는 것이다.

명령어 라인에서 에뮬레이터를 시작할 때 -dns-server <serverList> 옵션을 사용해서 수동으로 사용할 DNS 서버 주소를 지정할수도 있다. <serverList>는 콤마로 구분된 서버 이름 또는 IP 주소 목록이다.


Using the Emulator with a Proxy

에뮬레이터가 프락시 서버를 통해 인터넷을 억세스 해야 하는 경우 에뮬레이터를 시작할 때 -http-proxy <proxy> 옵션을 사용해 적절하게 리다이렉션 하도록 설정할 수 있다. 이 경우 <proxy>의 프락시 정보는 다음중의 한가지 포맷을 사용할 수 있다.

http://<machineName>:<port> 또는 http://<username>:<password>@<machineName>:<port>

-http-proxy 옵션은 에뮬레이터가 모든 outgoing TCP 커넥션을 지정된 HTTP/HTTPS 프락시를 사용하도록 해 준다. UDP 리다이렉션은 현재 지원하지 않는다.

다른 방법으로 http_proxy 환경변수에 <proxy>의 값을 넣어 정의할수도 있다. 이 경우 -http-proxy 명령어에 <proxy> 값을 지정할 필요가 없다. 에뮬레이터가 시작될 때 http_proxy 환경변수의 값을 확인해서 변수값이 정의되어 있으면 자동으로 그 값을 사용한다.

프락시 커넥션 문제를 검사하려면 -debug-proxy 옵션을 사용할 수도 있다.


Interconnecting Emulator Instances

하나의 에뮬레이터 인스턴스가 다른 인스턴스와 통신을 할 수 있도록 하려면 다음과 같이 필요한 네트웍 리다이렉션을 설정해 줘야만 한다.

환경이 다음과 같다고 가정하겠다.

   * A는 개발머신(호스트)
   * B는 A에서 실행되는 첫번째 에뮬레이터 인스턴스
   * C는 A에서 실행되는 두번째 에뮬레이터 인스턴스

그리고 C가 연결할 서버는 B에서 실행한다고 가정한다.

  1. B에서 서버를 설정해서 10.0.2.15:<serverPort>를 listen하도록 한다.
  2. B의 콘솔에서 A:localhost:<localPort>에서 B:10.0.2.15:<serverPort>로 리다이렉션을 설정한다.
  3. C에서 클라이언트가 10.0.2.2:<localPort>로 접속한다.
예를 들면 HTTP 서버를 실행하기를 원하는데 <serverPort>는 80번으로 하고 <localPort>는 8080으로 하는 경우의 설정은 다음과 같다.

   * B는 10.0.2.15:80을 listen
   * B의 콘솔에서 redir add tcp:8080:80 을 실행
   * C가 10.0.2.2:8080으로 접속