와이파이 다이렉트를 이용한 통신 프로그램 개발: 안드로이드 어플리케이션

2017년 7월 21일 코코넛냠냠


1. 개요

와이파이 다이렉트는 와이파이(무선랜)가 AP를 거치지 않고 기기간 직접 접속하여 데이터를 주고 받을 수 있도록 하는 기술입니다.

서로 다른 두 단말기가 와이파이 다이렉트를 통해 연결하여 네트워크를 형성하고 데이터를 공유할 수 있도록 하는 안드로이드 어플리케이션 프로그램의 작성 방법에 대해 다룹니다.


2. 사용 시나리오 (작업 순서)

와이파이 다이렉트로 단말기를 연결하기 위해서는 몇 가지 단계가 있는 과정을 거쳐야 합니다.


① 무선랜 디바이스 초기화

: 와이파이 디바이스를 초기화하고 와이파이 다이렉트 사용을 활성화합니다. 이 작업이 성공하면 와이파이가 네트워크를 형성할 수 있게 됩니다. 이 네트워크를 채널이라고도 부릅니다. 채널은 와이파이 다이렉트의 네트워크를 식별할 수 있게 합니다.


② 주변의 디바이스 검색

: 와이파이 다이렉트로 단말기를 연결하기 전에 와이파이 다이렉트 단말기에 대한 검색을 수행하여 연결하고자 하는 단말기를 발견해야 합니다. 검색이 성공하면 주변에서 검색을 수행중인 다른 와이파이 다이렉트 단말기들에 대한 정보를 얻습니다. 검색으로 발견된 모든 단말기들을 피어(Peer)라고 부릅니다. 검색으로 피어 목록을 획득한 뒤에는 피어와 와이파이 다이렉트로 연결할 수 있습니다.


③ 디바이스에 연결하기

: 디바이스가 서로 와이파이 다이렉트로 연결하는 것은 와이파이 그룹에 소속되는 것을 의미합니다. 와이파이 그룹이란 와이파이 다이렉트 장비들이 형성하는 네트워크를 말합니다. 이 네트워크를 채널이라는 이름으로 구분합니다. 와이파이 다이렉트로 디바이스에 연결하는 것은 다시 한 번 두 가지 절차를 필요로 합니다.


I. 사용자 인증

: 와이파이 다이렉트로 두 단말기가 서로 연결할 때 한쪽은 연결을 요청하는 쪽이 되며 다른 한쪽은 연결을 요청 받는 입장이 됩니다. 연결을 요청 받는 입장에서는 그러한 연결 요청에 대해 승인 또는 거절로 응답해야 합니다. 또 연결 요청을 받는 쪽에서는 요청을 승인하기 위해서 사용자 인증을 반드시 거쳐야 합니다. 사용자 인증은 사용자에게 비밀번호를 요구하는 방식과 비밀번호 없이 간단히 사용자가 수락(승인)/끊기(거절) 버튼을 누르는 것만으로 넘어가는 방식이 있습니다. 후자의 사용자 인증 방식을 PBC(Push Button Configuration)라고 부릅니다.


II. 그룹 생성, 오너 결정, IP 주소 배정

: 사용자 인증을 거친 뒤에는 연결하는 두 단말기 의 와이파이 다이렉트 네트워크가 온전히 형성됩니다. 이렇게 생긴 네트워크 그룹의 단말기 가운데 하나가 그룹 오너로 선정됩니다. 그룹 오너는 클라이언트/서버 개념의 서버에 해당합니다. 마지막으로 서버로 동작하는 그룹 오너의 IP를 통해 클라이언트 단말기가 (소켓 통신 등으로) 접속할 수 있게 됩니다.


3. 프로그래밍 (개요)


- 안드로이드에서 와이파이 다이렉트를 이용하기 위해서는 시스템(커널)에 요청해야 하는데, 안드로이드 앱은 그 일을 직접 수행할 수 있는 권한이 없습니다. 대신 안드로이드에서는 와이파이 다이렉트와 관련한 일들을 모두 대신 처리하는 인터페이스를 시스템 서비스로 제공합니다. 그 덕분에 안드로이드 앱은 와이파이 다이렉트의 시스템 서비스를 통해서 와이파이 다이렉트의 제어를 수행할 수 있습니다. 해당 시스템 서비스의 클래스는 android.net.wifi.p2p.WifiP2pManager입니다.


- 검색, 연결 등 와이파이 다이렉트의 작업은 비동기적으로 일어납니다. 이 때 그 결과를 반환하는 데에 리스너(콜백) 함수 이외에 브로드캐스트를 이용한다는 점에 유의해야 합니다. 그 때문에 브로드캐스트 리시버를 이용하여 브로드캐스트에 맞추어 처리하는 프로그램을 작성해야 합니다. 그런 모든 이벤트들을 그림으로 요약하여 나타내면 다음과 같습니다. (여기서 와이파이 다이렉트에 관련한 브로드캐스트는 모두 WIFI_P2P_로 시작하여 _ACTION으로 끝납니다.)


(그림1)


4. 프로그래밍 (자세히)

여러 작업들이 비동기적으로 발생하기 때문에 프로그래밍할 때에는 이해하기 쉽도록 프로그램의 작업 순서를 위의 사용 시나리오에 맞추어서 보아야 합니다.


① 무선랜 디바이스 초기화

와이파이 디바이스를 초기화시키기 위해서는 WifiP2pManager의 initialize() 메소드를 사용합니다. 이를 위해서 다음과 같이 WifiP2pManager의 객체를 획득하고 initialize()를 호출합니다.

mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
mChannel = mManager.initialize(this, getMainLooper(), null);

initialize() 메소드를 호출하면 와이파이 디바이스가 활성화되고 채널 객체를 반환 받습니다. 이 채널은 이후에 와이파이 네트워크 그룹을 구분하는 용도로 사용합니다.

(그림1)에 나온 것과 같이 와이파이 디바이스가 활성화된 뒤에는 와이파이의 연결상태가 변했음을 알리는 WIFI_P2P_STATE_CHANGED_ACTION과 어플리케이션이 수행되는 단말기의 상태가 변했을 때 발생하는 WIFI_THIS_DEVICE_CHANGED_ACTION의 두 가지 브로드캐스트가 발생합니다. 브로드캐스트 리시버에서 해당 이벤트가 있을 때의 처리를 정의해야 합니다.

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction(); // 어떤 종류의 액션(브로드캐스트) 
    if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { // 초기화시 
        switch (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)) {
            case WifiP2pManager.WIFI_P2P_STATE_ENABLED:
                // …
                break;
            case WifiP2pManager.WIFI_P2P_STATE_DISABLED:
                // …
                break;
            default:
                // …
                break;
        }
    } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) { // 이 단말기의 상태가 변했을 때
        // …
    } 
// …

(그림1)의 ChannelListener.onChannelDisconnected() 리스너 함수는 initialize() 메소드의 마지막 인자로 전달되는 콜백 메소드입니다. 여기에는 연결된 채널이 끊어졌을 때의 처리를 기술할 수 있지만 브로드캐스트를 통해서 처리할 수 있기 때문에 잘 사용되지 않습니다.


② 주변의 디바이스 검색

단말기 검색을 위해서는 WifiP2pManager의 discoverPeers() 메소드를 호출합니다. 이 메소드로 피어의 목록을 받아와서 피어 리스트에 갱신이 일어나면 WIFI_P2P_PEERS_CHANGED_CONNECTION 브로드캐스트가 발생합니다. discoverPeers()로 받아온 피어의 리스트를 얻기 위해서는 requestPeers() 메소드를 이 브로드캐스트가 발생했을 때 호출해야 합니다. 이렇게 결과를 받아오는 과정이 (그림1)에 그려져 있습니다. 코드는 다음과 같습니다.

mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {});
@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction(); // 어떤 종류의 액션(브로드캐스트)
    if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 
// …
    } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) {
// …
    } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { // 스캔시
        mManager.requestPeers(mChannel, new WifiP2pManager.PeerListListener() {
            @Override
            public void onPeersAvailable(WifiP2pDeviceList wifiP2pDeviceList) {
                // …
            }
        });
        Log.d("onReceive", "P2P peers list changed");
    } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
// …
    } 
} 


③ 디바이스에 연결하기

WifiP2pManager.connect() 메소드로 피어에게 와이파이 다이렉트 연결을 요청할 수 있습니다. connect() 메소드의 인자로 사용자 인증과 오너 선정에 대한 파라미터를 설정할 수 있습니다. (사용자 인증 방식을 연결을 요청하는 쪽에서 파라미터로 정합니다.) 연결이 성공하면 (그림1)과 같이 WIFI_P2P_CONNECTION_CHANGED_ACTION 브로드캐스트가 발생하며 이 때 WifiP2pManager.requestConnectInfo() 메소드를 호출하여 네트워크 그룹 형성이 성공적으로 되었는지, 자신 단말기가 그룹 오너인지 여부와 오너의 IP 주소를 얻을 수 있습니다. 이후에는 일반적인 소켓 통신 기법을 이용하여 데이터를 주고 받는 것이 가능하게 됩니다.

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction(); // 어떤 종류의 액션(브로드캐스트)
    if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) { 
// …
    } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) { 
// …
    } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) { 
// …
    } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) { // 연결시
        if (((NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO)).isConnected()) {
            mManager.requestConnectionInfo(mChannel, new WifiP2pManager.ConnectionInfoListener() {
                @Override
                public void onConnectionInfoAvailable(WifiP2pInfo wifiP2pInfo) { // IP와 호스트 여부
                    //…
                }
            });
        } else { // disconnected
// …
        }
    } // elif
} // func


(본문의 끝입니다.)


Top