Wi-Fi Direct(P2P) 개요

Wi-Fi Direct(P2P)를 사용하면 적절한 하드웨어가 있는 기기가 중간 액세스 포인트 없이 Wi-Fi를 통해 서로 직접 연결할 수 있습니다. 이 API를 사용하면 각 기기가 Wi-Fi P2P를 지원할 때 다른 기기를 검색해서 연결하고, 블루투스 연결보다 훨씬 긴 거리를 고속으로 연결해 통신할 수 있습니다. 사용자 간에 데이터를 공유하는 애플리케이션(예: 멀티플레이어 게임 또는 사진 공유 애플리케이션)에 유용합니다.

Wi-Fi P2P API는 다음과 같은 주요 부분으로 구성됩니다.

  • 피어를 검색, 요청, 연결할 수 있는 메서드로, WifiP2pManager 클래스에 정의되어 있습니다.
  • WifiP2pManager 메서드 호출의 성공 또는 실패를 알림으로 받을 수 있는 리스너 WifiP2pManager 메서드를 호출할 때 각 메서드는 매개변수로 전달된 특정 리스너를 수신할 수 있습니다.
  • Wi-Fi P2P 프레임워크에서 탐지된 특정 이벤트(예: 연결 실패, 새로 검색된 피어)에 대해 알림을 보내는 인텐트

API의 이 세 가지 주요 구성요소를 함께 사용하는 경우가 많습니다. 예를 들어 ActionListener.onSuccess()ActionListener.onFailure() 메서드가 알림을 보낼 수 있도록 discoverPeers() 호출에 WifiP2pManager.ActionListener를 제공할 수 있습니다. discoverPeers() 메서드가 피어 목록이 변경된 것을 발견하면 WIFI_P2P_PEERS_CHANGED_ACTION 인텐트도 브로드캐스트됩니다.

API 개요

WifiP2pManager 클래스는 기기의 Wi-Fi 하드웨어와 상호작용하여 검색, 피어 연결 등의 작업을 수행할 수 있게 하는 메서드를 제공합니다. 다음과 같은 작업이 가능합니다.

표 1. Wi-Fi P2P 메서드

방법 설명
initialize() Wi-Fi 프레임워크로 애플리케이션을 등록합니다. 다른 Wi-Fi P2P 메서드를 호출하기 전에 호출합니다.
connect() 지정된 구성이 있는 기기와 P2P 연결을 시작합니다.
cancelConnect() 모든 진행 중인 P2P 그룹 협상을 취소합니다.
requestConnectInfo() 기기의 연결 정보를 요청합니다.
createGroup() 현재 기기를 그룹 소유주로 하는 P2P 그룹을 생성합니다.
removeGroup() 현재 P2P 그룹을 삭제합니다.
requestGroupInfo() P2P 그룹 정보를 요청합니다.
discoverPeers() 피어 검색을 시작합니다.
requestPeers() 검색된 피어의 최신 목록을 요청합니다.

WifiP2pManager 메서드는 리스너를 전달해 Wi-Fi P2P 프레임워크에서 호출 상태에 관한 Activity를 알림으로 받을 수 있도록 합니다. 사용 가능한 리스너 인터페이스와 이 리스너를 사용하는 해당 WifiP2pManager 메서드 호출은 표 2에 설명되어 있습니다.

표 2. Wi-Fi P2P 리스너

리스너 인터페이스 연결된 작업
WifiP2pManager.ActionListener connect(), cancelConnect(), createGroup(), removeGroup(), discoverPeers()
WifiP2pManager.ChannelListener initialize()
WifiP2pManager.ConnectionInfoListener requestConnectInfo()
WifiP2pManager.GroupInfoListener requestGroupInfo()
WifiP2pManager.PeerListListener requestPeers()

Wi-Fi P2P API는 특정 Wi-Fi P2P 이벤트가 발생했을 때 브로드캐스트되는 인텐트를 정의합니다. 예를 들어 새로운 피어가 검색되었거나 기기의 Wi-Fi 상태가 변경되었을 때가 이에 해당합니다. 이러한 인텐트를 처리하는 Broadcast Receiver를 생성하여 애플리케이션에서 이 인텐트를 수신하도록 등록할 수 있습니다.

표 3. Wi-Fi P2P 인텐트

인텐트 설명
WIFI_P2P_CONNECTION_CHANGED_ACTION 기기의 Wi-Fi 연결 상태가 변경되면 브로드캐스트합니다.
WIFI_P2P_PEERS_CHANGED_ACTION discoverPeers()를 호출할 때 브로드캐스트합니다. 일반적으로는 이 인텐트를 애플리케이션에서 처리할 경우 requestPeers()를 호출하여 업데이트된 피어 목록을 가져옵니다.
WIFI_P2P_STATE_CHANGED_ACTION 기기에서 Wi-Fi P2P가 활성화되었거나 비활성화되었는지 브로드캐스트합니다.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 기기의 세부정보(예: 기기 이름)가 변경되었는지 브로드캐스트합니다.

Wi-Fi P2P 인텐트의 Broadcast Receiver 만들기

Broadcast Receiver는 Android 시스템에서 브로드캐스트하는 인텐트를 수신하여 애플리케이션이 관심 대상인 이벤트에 응답할 수 있게 합니다. Wi-Fi P2P 인텐트를 처리하기 위한 Broadcast Receiver를 생성하는 기본적인 단계는 다음과 같습니다.

  1. BroadcastReceiver 클래스를 확장하는 클래스를 만듭니다. 클래스 생성자의 경우 WifiP2pManager, WifiP2pManager.Channel, 이 Broadcast Receiver가 등록될 Activity에 대한 매개변수를 사용합니다. 이렇게 하면 Broadcast Receiver가 Activity에 업데이트를 전송하고, 필요한 경우 Wi-Fi 하드웨어와 통신 채널에 액세스할 수 있습니다.

  2. Broadcast Receiver에서 onReceive() 메서드에서 관심이 있는 인텐트를 확인합니다. 수신한 인텐트에 따라 필요한 작업을 실행합니다. 예를 들어 Broadcast Receiver가 WIFI_P2P_PEERS_CHANGED_ACTION 인텐트를 수신할 경우 requestPeers() 메서드를 호출해 현재 발견된 피어의 목록을 가져올 수 있습니다.

다음 코드는 일반적인 Broadcast Receiver를 생성하는 방법을 보여줍니다. Broadcast Receiver는 WifiP2pManager 객체와 Activity를 인수로 취하고, 이 두 가지 클래스를 사용하여 Broadcast Receiver가 인텐트를 수신했을 때 필요한 작업을 적절히 수행합니다.

Kotlin

/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
class WiFiDirectBroadcastReceiver(
       private val manager: WifiP2pManager,
       private val channel: WifiP2pManager.Channel,
       private val activity: MyWifiActivity
) : BroadcastReceiver() {

   override fun onReceive(context: Context, intent: Intent) {
       val action: String = intent.action
       when (action) {
           WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
               // Check to see if Wi-Fi is enabled and notify appropriate activity
           }
           WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
               // Call WifiP2pManager.requestPeers() to get a list of current peers
           }
           WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
               // Respond to new connection or disconnections
           }
           WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
               // Respond to this device's wifi state changing
           }
       }
   }
}

자바

/**
* A BroadcastReceiver that notifies of important Wi-Fi p2p events.
*/
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

   private WifiP2pManager manager;
   private Channel channel;
   private MyWiFiActivity activity;

   public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel,
           MyWifiActivity activity) {
       super();
       this.manager = manager;
       this.channel = channel;
       this.activity = activity;
   }

   @Override
   public void onReceive(Context context, Intent intent) {
       String action = intent.getAction();

       if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
           // Check to see if Wi-Fi is enabled and notify appropriate activity
       } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
           // Call WifiP2pManager.requestPeers() to get a list of current peers
       } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
           // Respond to new connection or disconnections
       } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
           // Respond to this device's wifi state changing
       }
   }
}

Android 10 이상을 실행하는 기기에서 다음 브로드캐스트 인텐트는 스티키가 아닙니다.

WIFI_P2P_CONNECTION_CHANGED_ACTION
애플리케이션은 requestConnectionInfo(), requestNetworkInfo() 또는 requestGroupInfo()를 사용하여 현재 연결 정보를 가져올 수 있습니다.
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
애플리케이션은 requestDeviceInfo()를 사용하여 현재 연결 정보를 가져올 수 있습니다.

Wi-Fi P2P 애플리케이션 만들기

Wi-Fi P2P 애플리케이션을 만들려면 애플리케이션에 대한 Broadcast Receiver를 생성해 등록하고, 피어를 검색하고, 피어에 연결하고, 피어로 데이터를 전송해야 합니다. 다음 섹션에서는 그 방법을 설명합니다.

초기 설정

Wi-Fi P2P API를 사용하기 전에 애플리케이션이 하드웨어에 액세스할 수 있는지, 기기가 Wi-Fi P2P 프로토콜을 지원하는지 확인해야 합니다. Wi-Fi P2P가 지원되는 경우 WifiP2pManager의 인스턴스를 획득하고, Broadcast Receiver를 생성하여 등록하고, Wi-Fi P2P API를 사용하여 시작할 수 있습니다.

  1. 기기에서 Wi-Fi 하드웨어를 사용하기 위한 권한을 요청하고 Android 매니페스트에서 애플리케이션이 올바른 최소 SDK 버전을 가지도록 선언합니다.

    <uses-sdk android:minSdkVersion="14" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
                     <!-- If your app derives location information from
                          Wi-Fi APIs, don't include the "usesPermissionFlags"
                          attribute. -->
                     android:usesPermissionFlags="neverForLocation" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
                     <!-- If any feature in your app relies on precise location
                          information, don't include the "maxSdkVersion"
                          attribute. -->
                     android:maxSdkVersion="32" />
    

    위의 권한 외에도 다음 API도 위치 모드를 사용하도록 설정해야 합니다.

  2. Wi-Fi P2P가 켜져 있고 지원되는지 확인합니다. Broadcast Receiver가 WIFI_P2P_STATE_CHANGED_ACTION 인텐트를 수신했을 때 이를 확인하는 것이 좋습니다. Activity에 Wi-Fi P2P 상태에 대해 알리고 그에 따라 응답합니다.

    Kotlin

    override fun onReceive(context: Context, intent: Intent) {
    ...
    val action: String = intent.action
    when (action) {
       WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
           val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
           when (state) {
               WifiP2pManager.WIFI_P2P_STATE_ENABLED -> {
                   // Wifi P2P is enabled
               }
               else -> {
                   // Wi-Fi P2P is not enabled
               }
           }
       }
    }
    ...
    }

    자바

    @Override
    public void onReceive(Context context, Intent intent) {
    ...
    String action = intent.getAction();
    if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
       int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
       if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
           // Wifi P2P is enabled
       } else {
           // Wi-Fi P2P is not enabled
       }
    }
    ...
    }
  3. 활동의 onCreate() 메서드에서 WifiP2pManager의 인스턴스를 가져오고 initialize()를 호출하여 Wi-Fi P2P 프레임워크에 애플리케이션을 등록합니다. 이 메서드는 WifiP2pManager.Channel를 반환합니다. 이는 애플리케이션을 Wi-Fi P2P 프레임워크에 연결하는 데 사용됩니다. 또한 Activity에 대한 참조와 더불어 WifiP2pManagerWifiP2pManager.Channel 객체로 Broadcast Receiver의 인스턴스를 생성해야 합니다. 이렇게 하면 Broadcast Receiver가 관심이 있는 이벤트에 대해 Activity에 알리고 그에 따라 업데이트할 수 있습니다. 또한 필요한 경우 기기의 Wi-Fi 상태도 조정할 수 있습니다.

    Kotlin

    val manager: WifiP2pManager? by lazy(LazyThreadSafetyMode.NONE) {
       getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
    }
    
    var channel: WifiP2pManager.Channel? = null
    var receiver: BroadcastReceiver? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
       ...
    
       channel = manager?.initialize(this, mainLooper, null)
       channel?.also { channel ->
           receiver = WiFiDirectBroadcastReceiver(manager, channel, this)
       }
    }

    자바

    WifiP2pManager manager;
    Channel channel;
    BroadcastReceiver receiver;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState){
       ...
       manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
       channel = manager.initialize(this, getMainLooper(), null);
       receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
       ...
    }
  4. 인텐트 필터를 만들고 Broadcast Receiver가 검사하는 것과 동일한 인텐트를 추가합니다.

    Kotlin

    val intentFilter = IntentFilter().apply {
       addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
       addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
    }

    자바

    IntentFilter intentFilter;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState){
       ...
       intentFilter = new IntentFilter();
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
       intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
       ...
    }
  5. 활동의 onResume() 메서드에서 Broadcast Receiver를 등록하고 활동의 onPause() 메서드에서 등록을 취소합니다.

    Kotlin

    /* register the broadcast receiver with the intent values to be matched */
    override fun onResume() {
       super.onResume()
       receiver?.also { receiver ->
           registerReceiver(receiver, intentFilter)
       }
    }
    
    /* unregister the broadcast receiver */
    override fun onPause() {
       super.onPause()
       receiver?.also { receiver ->
           unregisterReceiver(receiver)
       }
    }

    자바

    /* register the broadcast receiver with the intent values to be matched */
    @Override
    protected void onResume() {
       super.onResume();
       registerReceiver(receiver, intentFilter);
    }
    /* unregister the broadcast receiver */
    @Override
    protected void onPause() {
       super.onPause();
       unregisterReceiver(receiver);
    }
  6. WifiP2pManager.Channel를 가져와 Broadcast Receiver를 설정하면 애플리케이션이 Wi-Fi P2P 메서드를 호출하고 Wi-Fi P2P 인텐트를 수신할 수 있습니다.

  7. WifiP2pManager의 메서드를 호출하여 Wi-Fi P2P 기능을 사용하여 애플리케이션을 구현합니다.

다음 섹션에서는 피어 검색, 연결과 같은 일반적인 작업을 수행하는 방법을 설명합니다.

피어 검색

discoverPeers()를 호출하여 범위 내에 있고 연결할 수 있는 사용 가능한 피어를 탐지합니다. 이 함수 호출은 비동기식이며, WifiP2pManager.ActionListener를 생성한 경우 onSuccess()onFailure()과 함께 애플리케이션에 성공 또는 실패가 전달됩니다. onSuccess() 메서드는 검색 프로세스가 성공했는지 여부만 알리고 발견한 실제 피어가 있어도 그에 대한 정보는 제공하지 않습니다. 다음 코드 샘플은 이를 설정하는 방법을 보여줍니다.

Kotlin

manager?.discoverPeers(channel, object : WifiP2pManager.ActionListener {

   override fun onSuccess() {
       ...
   }

   override fun onFailure(reasonCode: Int) {
       ...
   }
})

자바

manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
   @Override
   public void onSuccess() {
       ...
   }

   @Override
   public void onFailure(int reasonCode) {
       ...
   }
});

검색 프로세스가 성공하고 피어가 탐지되면 시스템은 WIFI_P2P_PEERS_CHANGED_ACTION 인텐트를 브로드캐스트합니다. 이 인텐트는 Broadcast Receiver에서 수신하고 피어 목록을 가져올 수 있습니다. 애플리케이션이 WIFI_P2P_PEERS_CHANGED_ACTION 인텐트를 수신하면 requestPeers()로 검색된 피어의 목록을 요청할 수 있습니다. 다음 코드는 이를 설정하는 방법을 보여줍니다.

Kotlin

override fun onReceive(context: Context, intent: Intent) {
   val action: String = intent.action
   when (action) {
       ...
       WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
           manager?.requestPeers(channel) { peers: WifiP2pDeviceList? ->
               // Handle peers list
           }
       }
       ...
   }
}

자바

PeerListListener myPeerListListener;
...
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

   // request available peers from the wifi p2p manager. This is an
   // asynchronous call and the calling activity is notified with a
   // callback on PeerListListener.onPeersAvailable()
   if (manager != null) {
       manager.requestPeers(channel, myPeerListListener);
   }
}

requestPeers() 메서드도 비동기식이며, WifiP2pManager.PeerListListener 인터페이스에 정의된 onPeersAvailable()를 사용하여 피어 목록을 사용할 수 있게 되면 Activity에 이를 알릴 수 있습니다. onPeersAvailable() 메서드는 WifiP2pDeviceList를 제공하며, 반복 작업을 통해 연결할 피어를 찾을 수 있습니다.

피어 연결

가능한 피어 목록을 가져와 연결할 기기를 선택한 후 connect() 메서드를 호출하여 기기에 연결합니다. 이 메서드를 호출할 때는 연결 대상 기기에 관한 정보가 포함된 WifiP2pConfig 객체가 필요합니다. WifiP2pManager.ActionListener는 연결 성공 또는 실패를 알릴 수 있습니다. 다음 코드는 기기에 대한 연결을 만드는 방법을 보여줍니다.

Kotlin

val device: WifiP2pDevice = ...
val config = WifiP2pConfig()
config.deviceAddress = device.deviceAddress
channel?.also { channel ->
   manager?.connect(channel, config, object : WifiP2pManager.ActionListener {

       override fun onSuccess() {
           //success logic
       }

       override fun onFailure(reason: Int) {
           //failure logic
       }
   }
)}

자바

//obtain a peer from the WifiP2pDeviceList
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
manager.connect(channel, config, new ActionListener() {

   @Override
   public void onSuccess() {
       //success logic
   }

   @Override
   public void onFailure(int reason) {
       //failure logic
   }
});

데이터 전송

연결이 설정되면 소켓을 사용하여 기기 간에 데이터를 전송할 수 있습니다. 데이터를 전송하기 위한 기본 단계는 다음과 같습니다.

  1. ServerSocket를 만듭니다. 이 소켓은 지정된 포트에서 클라이언트와 연결될 때까지 대기하고 연결이 되기 전에는 차단 상태이므로 이 작업은 백그라운드 스레드에서 수행하세요.
  2. 클라이언트 Socket를 만듭니다. 클라이언트는 서버 소켓의 IP 주소와 포트를 사용하여 서버 기기에 연결합니다.
  3. 클라이언트에서 서버로 데이터를 전송합니다. 클라이언트 소켓이 서버 소켓에 성공적으로 연결되면 바이트 스트림으로 클라이언트에서 서버로 데이터를 전송할 수 있습니다.
  4. 서버 소켓이 클라이언트에 연결될 때까지 대기합니다 (accept() 메서드 사용). 이 호출은 클라이언트가 연결될 때까지 차단 상태이므로 다른 스레드에서 호출합니다. 연결이 설정되면 서버 기기가 클라이언트로부터 데이터를 수신할 수 있습니다.

Wi-Fi P2P 데모에서 수정한 다음 예는 클라이언트-서버 소켓 통신을 생성하고 서비스를 사용하여 JPEG 이미지를 클라이언트에서 서버로 전송하는 방법을 보여줍니다. 전체적인 동작 예시를 확인하려면 데모를 컴파일하고 실행하세요.

Kotlin

class FileServerAsyncTask(
       private val context: Context,
       private var statusText: TextView
) : AsyncTask<Void, Void, String?>() {

   override fun doInBackground(vararg params: Void): String? {
       /**
        * Create a server socket.
        */
       val serverSocket = ServerSocket(8888)
       return serverSocket.use {
           /**
            * Wait for client connections. This call blocks until a
            * connection is accepted from a client.
            */
           val client = serverSocket.accept()
           /**
            * If this code is reached, a client has connected and transferred data
            * Save the input stream from the client as a JPEG file
            */
           val f = File(Environment.getExternalStorageDirectory().absolutePath +
                   "/${context.packageName}/wifip2pshared-${System.currentTimeMillis()}.jpg")
           val dirs = File(f.parent)

           dirs.takeIf { it.doesNotExist() }?.apply {
               mkdirs()
           }
           f.createNewFile()
           val inputstream = client.getInputStream()
           copyFile(inputstream, FileOutputStream(f))
           serverSocket.close()
           f.absolutePath
       }
   }

   private fun File.doesNotExist(): Boolean = !exists()

   /**
    * Start activity that can handle the JPEG image
    */
   override fun onPostExecute(result: String?) {
       result?.run {
           statusText.text = "File copied - $result"
           val intent = Intent(android.content.Intent.ACTION_VIEW).apply {
               setDataAndType(Uri.parse("file://$result"), "image/*")
           }
           context.startActivity(intent)
       }
   }
}

자바

public static class FileServerAsyncTask extends AsyncTask {

   private Context context;
   private TextView statusText;

   public FileServerAsyncTask(Context context, View statusText) {
       this.context = context;
       this.statusText = (TextView) statusText;
   }

   @Override
   protected String doInBackground(Void... params) {
       try {

           /**
            * Create a server socket and wait for client connections. This
            * call blocks until a connection is accepted from a client
            */
           ServerSocket serverSocket = new ServerSocket(8888);
           Socket client = serverSocket.accept();

           /**
            * If this code is reached, a client has connected and transferred data
            * Save the input stream from the client as a JPEG file
            */
           final File f = new File(Environment.getExternalStorageDirectory() + "/"
                   + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis()
                   + ".jpg");

           File dirs = new File(f.getParent());
           if (!dirs.exists())
               dirs.mkdirs();
           f.createNewFile();
           InputStream inputstream = client.getInputStream();
           copyFile(inputstream, new FileOutputStream(f));
           serverSocket.close();
           return f.getAbsolutePath();
       } catch (IOException e) {
           Log.e(WiFiDirectActivity.TAG, e.getMessage());
           return null;
       }
   }

   /**
    * Start activity that can handle the JPEG image
    */
   @Override
   protected void onPostExecute(String result) {
       if (result != null) {
           statusText.setText("File copied - " + result);
           Intent intent = new Intent();
           intent.setAction(android.content.Intent.ACTION_VIEW);
           intent.setDataAndType(Uri.parse("file://" + result), "image/*");
           context.startActivity(intent);
       }
   }
}

클라이언트 측에서는 클라이언트 소켓과 서버 소켓을 연결하고 데이터를 전송합니다. 이 예시에서는 클라이언트 기기의 파일 시스템에서 JPEG 파일을 전송합니다.

Kotlin

val context = applicationContext
val host: String
val port: Int
val len: Int
val socket = Socket()
val buf = ByteArray(1024)
...
try {
   /**
    * Create a client socket with the host,
    * port, and timeout information.
    */
   socket.bind(null)
   socket.connect((InetSocketAddress(host, port)), 500)

   /**
    * Create a byte stream from a JPEG file and pipe it to the output stream
    * of the socket. This data is retrieved by the server device.
    */
   val outputStream = socket.getOutputStream()
   val cr = context.contentResolver
   val inputStream: InputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"))
   while (inputStream.read(buf).also { len = it } != -1) {
       outputStream.write(buf, 0, len)
   }
   outputStream.close()
   inputStream.close()
} catch (e: FileNotFoundException) {
   //catch logic
} catch (e: IOException) {
   //catch logic
} finally {
   /**
    * Clean up any open sockets when done
    * transferring or if an exception occurred.
    */
   socket.takeIf { it.isConnected }?.apply {
       close()
   }
}

Java

Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];
...
try {
   /**
    * Create a client socket with the host,
    * port, and timeout information.
    */
   socket.bind(null);
   socket.connect((new InetSocketAddress(host, port)), 500);

   /**
    * Create a byte stream from a JPEG file and pipe it to the output stream
    * of the socket. This data is retrieved by the server device.
    */
   OutputStream outputStream = socket.getOutputStream();
   ContentResolver cr = context.getContentResolver();
   InputStream inputStream = null;
   inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"));
   while ((len = inputStream.read(buf)) != -1) {
       outputStream.write(buf, 0, len);
   }
   outputStream.close();
   inputStream.close();
} catch (FileNotFoundException e) {
   //catch logic
} catch (IOException e) {
   //catch logic
}

/**
* Clean up any open sockets when done
* transferring or if an exception occurred.
*/
finally {
   if (socket != null) {
       if (socket.isConnected()) {
           try {
               socket.close();
           } catch (IOException e) {
               //catch logic
           }
       }
   }
}