안드로이드 UDP 통신 예제

Reliable·2022년 11월 7일
1

UDP란?

UDP(User Datagram Protocol)는 TCP/IP 프로토콜의 4계층인 통신계층 (transport Layer)에서 사용되는 통신 규약이다. 통신계층은 송신자와 수신자의 데이터 전달을 담당하는 계층으로 크게 TCP와 UDP로 나뉜다.

오늘은 UDP 통신의 특성을 알아보고 안드로이드 스튜디오 (Java)를 통해서 cilent와 server의 UDP통신을 구현해보자.

UDP의 특성

UDP는 client와 server 간에 datagram의 신뢰할 수 없는 전송을 제공한다.
*신뢰할 수 없는 전송이란 분실이 일어날 수 있음을 의미한다.

  • UDP는 application 관점에서 처리하는 단위가 datagram인 서비스이다.
  • UDP통신을 위해서는 datagram을 만들어야하고 datagram을 목적지로 내보내는 socket을 UDP Socket이라고 한다.
  • UDP는 data를 보내기 전에 connection을 설정할 필요가 없기 때문에 handshacking을 하지 않아도 된다.
  • 송신자는 목적지의 IP주소와 port number를 명시적으로 첨부해야 하며, 수신자는 수신된 패킷에서 송신자의 IP 주소 및 port number를 추출할 수 있다.
  • UDP는 매우 단순해서 서비스의 신뢰도가 낮다.
  • datagram이 인터넷 상에서 분실 될 수도 있고 여러 datagram을 보냈다면 순서가 뒤바뀔 수 도 있다.

UDP의 특성을 고려하여 Android 어플로 UDP통신을 하는 client와 server를 구현해보자

UDP 통신 구현 방법


먼저 client socket과 server socket을 만든다. socket에는 상대방의 IP주소나 포트넘버가 필요없다. 대신 datagram을 보내려고 할 때 목적하는 곳의 IP주소, 포트넘버를 datagram에 언급해야 한다.

datagram을 생성해서 상대방의 IP주소와 포트넘버를 담고 UDP socket을 통해서 datagram을 보낸다.

서버는 datagram을 받고 client의 IP주소와 포트넘버를 client가 보낸 datagram으로 부터 추출할 수 있다. 읽어들인 datagram의 소스 IP주소, 소스 포트넘버는 client의 IP주소, 포트넘버가 된다.

서버는 echo msg를 서버소켓을 통해서 내보낸다.: 1. 데이터그램을 만든다. 2. 추출한 client의 ip주소와 포트넘버를 datagram에 담아서 내보낸다

서버가 보낸 datagram을 클라이언트가 받아서 화면에 뿌려주고 socket을 끝낸다.

Android UDP 구현

먼저 인터넷을 사용해야 하기 때문에 client와 server 모두 manifest에 permission을 해준다.

AndroidManifest.xml

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

Client

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="10dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="UDP Client"
        android:layout_gravity="center"
        android:textSize="30dp"/>

    <EditText
        android:id="@+id/editIP"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ip주소를 입력하시오."/>
    <EditText
        android:id="@+id/editMsg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="보낼 메세지를 입력 하시오."/>
    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="메세지 전송"/>
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="서버로 부터 받은 메세지 :"/>
</LinearLayout>

MainActivity.java

package com.example.udpclienttest;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    EditText editIP, editMsg;
    String inputMsg;

    private int CLIENT_PORT = 17777;
    private int SERVER_PORT = 18888;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.textView);
        editIP = (EditText) findViewById(R.id.editIP);
        editMsg = (EditText) findViewById(R.id.editMsg);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("VR", "Click OK");
                startClient();
            }
        });
    }
    // 10.0.2.16

    public void startClient() {
        // 안드로이드에서 네트워킹은 thread 이용이 필수 이다.
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 1. Client Socket 생성
                    DatagramSocket clientSocket = new DatagramSocket(CLIENT_PORT);
                    Log.d("VR", "Client Socket Created");

                    inputMsg = editMsg.getText().toString(); // 서버에 보낼 메세지
                    InetAddress inetAddress = InetAddress.getByName(editIP.getText().toString()); // InetAddress 객체에 입력받은 ip 담기

                    // 2. datagram 생성 - server IP 와 server 포트넘버 포함
                    DatagramPacket clientPacket = new DatagramPacket(inputMsg.getBytes(), inputMsg.length(), inetAddress, SERVER_PORT);
                    // UDPSocket 을 통해서 datagram 을 내보낸다.
                    clientSocket.send(clientPacket);
                    Log.d("VR", "Packet Send");

                    byte[] receiveData =new byte[256]; // server 가 보내는 data 를 받을 객체 생성
                    DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
                    clientSocket.receive(receivePacket);
                    final String receive_data = new String(receivePacket.getData(),0 , receivePacket.getLength());
                    MainActivity.this.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            textView.setText("서버로 부터 받은 메세지 : " + receive_data);
                        }
                    });
                    // 10.0.2.15
                } catch (SocketException e) {
                    Log.e("VR", "Sender SocketException");
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

Server

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="5dp">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="UDP Server"
        android:textSize="30dp"
        android:gravity="center" />

    <TextView
        android:id="@+id/serverIp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="IP Adrress : "
        android:textSize="20dp"/>

    <TextView
        android:id="@+id/clientMsg"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        android:text="클라이언트로 부터 받은 메세지 : "
        android:textSize="15dp"/>
</LinearLayout>

MainActivity.java

// server
public class MainActivity extends AppCompatActivity {
    TextView serverIp, clientMsg;
    private int SERVER_PORT = 18888;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        serverIp = (TextView) findViewById(R.id.serverIp);
        clientMsg = (TextView) findViewById(R.id.clientMsg);
        serverIp.setText(getIpAddress()); // ip 가져오기

        Thread serverThread = new Thread(new serverThread());
        serverThread.start();
    }


    class serverThread extends Thread {
        @Override
        public void run() {
            try {
                // server socket 생성
                DatagramSocket serverSocket = new DatagramSocket(SERVER_PORT);
                Log.d("VR", "Sever Socket Created");

                // client메세지를 받을 배열 선언
                byte[] receiveMsg = new byte[256];
                // server datagram 생성
                DatagramPacket serverPacket = new DatagramPacket(receiveMsg, receiveMsg.length);

                // 2.
                serverSocket.receive(serverPacket);
                final String receive_data = new String(serverPacket.getData(),0 , serverPacket.getLength());
                MainActivity.this.runOnUiThread(new Runnable() {
                                                    @SuppressLint("SetTextI18n")
                                                    @Override
                                                    public void run() {
                                                        clientMsg.setText("클라이언트로 부터 받은 메세지 : " + receive_data);
                                                    }
                                                }
                );

                final String EchoMsg = getIpAddress() + " sends UDP echo message " + "'" + receive_data +"'";
                int port = serverPacket.getPort();
                DatagramPacket send_packet = new DatagramPacket(EchoMsg.getBytes(), EchoMsg.length(),serverPacket.getAddress(), port);
                serverSocket.send(send_packet);
                Log.d("VR", "Packet Send");


            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    private String getIpAddress() {
        String ip = "";
        try {
            Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface.getNetworkInterfaces();
            while (enumNetworkInterfaces.hasMoreElements()) {
                NetworkInterface networkInterface = enumNetworkInterfaces.nextElement();
                Enumeration<InetAddress> enumInetAddress = networkInterface.getInetAddresses();
                while (enumInetAddress.hasMoreElements()) {
                    InetAddress inetAddress = enumInetAddress.nextElement();
                    if (inetAddress.isSiteLocalAddress()) {
                        ip += "IP address: " + inetAddress.getHostAddress() + "\n";
                    }
                }
            }
        } catch (SocketException e) {
        // TODO Auto-generated catch block
            e.printStackTrace();
            ip += "Something Wrong! " + e.toString() + "\n";
        }
        return ip;
    }
}

출력 화면

0개의 댓글