Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

기록장

[안드로이드] 네트워킹, Socket (소켓) 본문

안드로이드

[안드로이드] 네트워킹, Socket (소켓)

edit0 2021. 1. 7. 22:54

네트워킹

 

인터넷에 연결되어 있는 원격지의 서버 또는 원격지의 단말과 통신해서 데이터를 주고받는 동작들을 포함한다.

 

원격지의 서버를 연결하는 가장 단순한 방식은 Client와 Server의 1:1로 연결하는 2-tier-C/S 방식이다.

가장 많이 사용하는 네트워킹 방식이며 대부분 Client가 Server에 연결되어 데이터를 요청하고 응답받는 단순한 개념으로 이해할 수 있다.

 

 그 다음 서버를 좀 더 유연하게 구성할 수 있는 3-tier 연결 방식이 있다.

응용 서버와 데이터 서버로 서버를 구성하면 DB를 분리할 수 있어 중간에 비지니스 로직을 처리하는 응용 서버가 좀 더 다양한 역할을 할 수 있다.

 

Socket 연결은 데이터를 주고 받을 때 Socket에 데이터를 담아 보내는 방식으로 IP주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아낸다. 통신 방식에는 TCP와 UDP 방식이 있다. 보통 TCP를 사용한다.

* 포트는 통신을 위한 통로 개념으로 공유가 안되고 한정되어 있기 때문에 사용하지 않을 때 쓰지 않을 때는 중단시켜야 다른 프로그램에서 사용 가능

 

* 안드로이드에서 통신을 하기 위해선 무조건 스레드를 사용해야 한다.

* 그러므로 핸들러 개념도 알고 있어야 한다.

Thread, Handler 개념을 잘 모르겠다면 참고

edit0.tistory.com/15

 

[안드로이드] Thread, Handler 이해 및 간단한 예제

들어가기 전에.. 내 생각 처음 접하시는 분들은 이걸 왜 써야하는지 잘 모르고 접근하면 이해가 더 안될 것이라 생각되어 사용 목적에 대해 간략이 적어보자면,, (나도 배우는 입장이라 잘 모른

edit0.tistory.com

 

코드

 

코드 실행 과정은 client에서 데이터를 보내고 server에서 받아서 다시 돌려주면 client에서 받는 형식으로 진행

(주석 잘 보길 권장함)

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edittext1"/>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30dp"
            android:text="클라이언트"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="서버로 전송"
            android:id="@+id/button1"/>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/textview1"
                    android:textSize="20dp"/>

            </LinearLayout>
        </ScrollView>


    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="30dp"
            android:text="서버"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="서버 시작"
            android:id="@+id/button2"/>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:id="@+id/textview2"
                    android:textSize="20dp"/>

            </LinearLayout>
        </ScrollView>

    </LinearLayout>

</LinearLayout>

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    EditText et1;
    Button b1, b2;
    TextView tv1, tv2;

    Handler handler = new Handler();

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

        et1 = findViewById(R.id.edittext1);
        b1 = findViewById(R.id.button1);
        b2 = findViewById(R.id.button2);
        tv1 = findViewById(R.id.textview1);
        tv2 = findViewById(R.id.textview2);

        b1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String data = et1.getText().toString();

                new Thread(new Runnable() { //네트워킹 기능을 사용하므로 thread를 사용해야함
                    @Override
                    public void run() {
                        send(data);
                    }
                }).start();
            }
        });

        b2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() { //네트워킹 기능을 사용하므로 thread를 사용해야함
                    @Override
                    public void run() {
                        startServer();
                    }
                }).start();
            }
        });

    }

    public void send(String data){
        try{
            int portNumber = 1223; //포트 번호 설정 (통신을 위한 통로? Path라 보면 된다)

            Socket socket = new Socket("localhost", portNumber); //소켓 생성 / ip주소는 로컬 호스트, 포트 1223

            printClientLog("소켓 연결함");

            //소켓에 담아 서버에 데이터를 쓰기 위한 객체 생성
            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(data); //데이터 쓰기
            outputStream.flush(); //스트림에 남아 있는 데이터를 비우는 역할

            printClientLog("데이터 전송함");

            //응답(데이터)받을 스트림 객체 생성
            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());

            printClientLog("서버로부터 받음: "+ inputStream.readObject()); //데이터 읽어서 보여주기

            socket.close(); //연결 끊기

        } catch (Exception e){
            e.printStackTrace();
        }
    }

    public void startServer(){
        try {
            int portNumber = 1223; //포트 넘버는 client와 맞춰준다

            ServerSocket server = new ServerSocket(portNumber); //서버 소켓 객체 생성
            printServerLog("서버 시작함: "+ portNumber);

            //client의 접속 요청이 오면 실행된다.
            while(true){
                Socket socket = server.accept(); //accept() 메소드를 통해 소켓 객체가 반환
                //이 과정으로 client의 소켓 연결 정보를 알 수 있다.

                InetAddress clientHost = socket.getLocalAddress(); //client ip정보
                int clientPort = socket.getPort(); //client 포트 정보

                printServerLog("클라이언트 연결됨: "+ clientHost + " : " + clientPort);

                //소켓에 있는 데이터 읽어오기 위한 스트림 객체 생성
                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                Object obj = inputStream.readObject(); //데이터 읽기

                printServerLog("데이터 받음: " + obj); //받은 데이터 보여주기

                //받은 데이터 다시 client에게 돌려주기 위한 스트림 객체 생성
                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(obj + " from Server(돌려주기)"); //데이터 쓰기
                outputStream.flush();

                printServerLog("데이터 보냄");

                socket.close(); //연결 끊기
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void printClientLog(final String data){
        android.util.Log.i("태그", data);

        handler.post(new Runnable() {
            @Override
            public void run() {
                tv1.append(data + "\n");
            }
        });
    }

    public void printServerLog(final String data){
        android.util.Log.i("태그", data);

        handler.post(new Runnable() {
            @Override
            public void run() {
                tv2.append(data + "\n");
            }
        });
    }
}

 

결과

 

 

실제 앱에서는 ObjectOutputStream과 ObjectInputStream은 잘 사용되지 않는다고 한다. 이유는 자바가 아닌 다른 언어로 만들어진 서버와 통신할 경우에는 데이터 송수신이 정상적으로 이루어지지 않을 수 있기 때문이다. 일반적으로는 DataOutputStream과 DataInputStream을 많이 사용한다고 한다.

그리고 보통 서버를 만들 때는 백그라운드에서 실행되도록 Service를 만들어 구현하는 것이 좋다. (액티비티에서 만들면 자원이 부족해지면 강제 종료가 될 수 있기 때문)

 

+

 

서버가 Service를 통해 실행되도록 구현해보도록 하겠다.

그리고 client와 server도 새로 프로젝트를 만들고 따로따로 분리해서 진행하도록 하겠다.

 

각 프로젝트 AndroidManifest.xml에 권한 추가

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

 

Service는 기본 개념으로 onCreate(), onStartCommand(), onDestroy()가 있다. 보통 onStartCommand()에서 메인 구현을 진행한다. Intent 객체를 통해 Service 호출 가능하다.

 

Server 프로젝트에서는 Service를 사용하기 전 AndroidManifest.xml의 application 내에 아래 코드를 사용해 꼭 등록해야한다.

<service android:name="service"> //각 Service를 구현한 클래스명
	<intent-filter>
		<action android:name="패키지명" />
	</intent-filter>
</service>

 

 

 

코드

 

server 프로젝트

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="서버 시작"
        android:id="@+id/button1"/>

</LinearLayout>

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    Button b1;

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

        b1 = findViewById(R.id.button1);

        b1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), "서버 start", Toast.LENGTH_SHORT).show();

                Intent intent = new Intent(MainActivity.this, service.class);
                startService(intent);
            }
        });
    }
}

 

service.java

public class service extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        server_class serverClass = new server_class();
        serverClass.start();

    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

    }

    class server_class extends Thread{
        @Override
        public void run() {
            super.run();

            try {
                int portNumber = 1223; //포트 넘버는 client와 맞춰준다

                ServerSocket server = new ServerSocket(portNumber); //서버 소켓 객체 생성

                //client의 접속 요청이 오면 실행된다.
                while(true){
                    Socket socket = server.accept(); //accept() 메소드를 통해 소켓 객체가 반환
                    //이 과정으로 client의 소켓 연결 정보를 알 수 있다.

                    InetAddress clientHost = socket.getLocalAddress(); //client ip정보
                    int clientPort = socket.getPort(); //client 포트 정보

                    //소켓에 있는 데이터 읽어오기 위한 스트림 객체 생성
                    ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                    Object obj = inputStream.readObject(); //데이터 읽기

                    //받은 데이터 다시 client에게 돌려주기 위한 스트림 객체 생성
                    ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                    outputStream.writeObject(obj + " from Server"); //데이터 쓰기
                    outputStream.flush();

                    socket.close();
                }

            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

 

client 프로젝트

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/edittext1"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/button1"
        android:text="Client 시작"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/textview1"
        android:textSize="30dp"/>

</LinearLayout>

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    EditText et1;
    Button b1;
    TextView tv1;

    Handler handler = new Handler();

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

        et1 = findViewById(R.id.edittext1);
        b1 = findViewById(R.id.button1);
        tv1 = findViewById(R.id.textview1);

        b1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                client_class clientClass = new client_class(et1.getText().toString());
                clientClass.start();
            }
        });

    }

    class client_class extends Thread{
        String string = "";

        client_class(String str){
            this.string = str;
        }

        @Override
        public void run() {
            super.run();

            try{
                int portNumber = 1223; //포트 번호 설정 (통신을 위한 통로? Path라 보면 된다)

                Socket socket = new Socket("localhost", portNumber); //소켓 생성 / ip주소는 로컬 호스트, 포트 1223

                //소켓에 담아 서버에 데이터를 쓰기 위한 객체 생성
                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(string); //데이터 쓰기
                outputStream.flush(); //스트림에 남아 있는 데이터를 비우는 역할

                //응답(데이터)받을 스트림 객체 생성
                final ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                final Object temp = inputStream.readObject();

                //핸들러 사용하여 UI 객체에 접근
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            tv1.setText("" + temp);
                        } catch (Exception e){
                            e.printStackTrace();
                        }

                    }
                });

                socket.close();

            } catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

 

결과

 

 

 

부족한 점, 피드백 환영합니다.

 

감사합니다.

 

 

참고

정재곤, 『Do it! 안드로이드 앱 프로그래밍 - 개정 6판』, 이지스퍼블리싱(주)