Android - 소켓 통신 사용하기

유의선·2023년 7월 26일
0

TCP/IP 수준의 통신 방식을 제공하는 소켓 통신 방식은 IP 주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아낸다.

안드로이드는 표준 자바에서 사용하던 java.net 패키지의 클래스들을 그대로 새용할 수 있어 소켓 연결을 쉽게 구현할 수 있다.

안드로이드는 소켓 연결 등을 시도하거나 응답을 받아 처리할 때 스레드를 사용해야 한다.


activity_main.xml 레이아웃을 만들었다.
두 개의 레이아웃으로 나눈 후
위쪽에는 서버로 데이터를 전송하고 받는 Client 영역으로
아래쪽에는 클라이언트로부터 데이터를 받고 처리 후 다시 클라이언트로 전송하는 Server 영역으로
만들었다.


MainActivity.java 에 코드를 작성하였다

public class MainActivity extends AppCompatActivity {

    EditText editText;

    TextView textView;
    TextView textView2;

    Handler handler = new Handler();

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

        editText = findViewById(R.id.editText);
        textView = findViewById(R.id.textView);
        textView2 = findViewById(R.id.textView2);

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String data = editText.getText().toString();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        send(data);
                    }
                }).start();
            }
        });

        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        startServer();
                    }
                }).start();
            }
        });
    }

    public void printClientLog(final String data) {
        Log.d("MainActivity", data);

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

    public void printServerLog(final String data) {
        Log.d("MainActivity", data);

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

    public void send(String data) {
        try {
            int portNumber = 5001;
            Socket socket = new Socket("localhost", portNumber);
            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 ex) {
            ex.printStackTrace();
        }
    }

    public void startServer() {
        try {
            int portNumber = 5001;

            ServerSocket server = new ServerSocket(portNumber);
            printServerLog("서버 시작함" + portNumber);

            while (true) {
                Socket socket = server.accept();
                InetAddress clientHost = socket.getLocalAddress();
                int clientPort = socket.getPort();
                printServerLog("클라이언트 연결됨 : " + clientHost + " : " + clientPort);

                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                Object obj = inputStream.readObject();
                printServerLog("데이터 받음 : " + obj);

                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(obj + " from Server");
                outputStream.flush();
                printServerLog("데이터 보냄");

                socket.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

Client 영역과 Server 영역의 텍스트뷰에 글자를 출력할 수 있도록 하는 메소드 printClientLog와 printServerLog를 정의하였다.
스레드에서 이 메소드들을 호출할 것이므로 핸들러 객체를 이용하였다.

    public void printClientLog(final String data) {
        Log.d("MainActivity", data);

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

    public void printServerLog(final String data) {
        Log.d("MainActivity", data);

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

클라이언트에 데이터를 전송하는 send 메소드를 정의하였다.
여기서는 서버와 클라이언트가 5001번 포트를 사용하도록 하였다.

    public void send(String data) {
        try {
            int portNumber = 5001;
            Socket socket = new Socket("localhost", portNumber);
            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 ex) {
            ex.printStackTrace();
        }
    }

접속할 IP 주소는 "localhost"로 포트는 5001번을 사용해 socket 객체를 만들었다.

            int portNumber = 5001;
            Socket socket = new Socket("localhost", portNumber);
            printClientLog("소켓 연결함");

새로 만든 소켓을 통해 데이터를 보내거나 받을 때는 getOutputStream과 getInputStream 메소드로 입출력 스트림 객체를 참조하였다.

            ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
            outputStream.writeObject(data);
            outputStream.flush();
            printClientLog("데이터 전송함");

            ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
            printClientLog("서버로부터 받음 : " + inputStream.readObject());

마지막으로 통신을 끝내기 위해 소켓을 close 한다.

            socket.close();

클라이언트가 접속할 서버를 만들 startSerer 메소드를 정의하였다.

    public void startServer() {
        try {
            int portNumber = 5001;

            ServerSocket server = new ServerSocket(portNumber);
            printServerLog("서버 시작함" + portNumber);

            while (true) {
                Socket socket = server.accept();
                InetAddress clientHost = socket.getLocalAddress();
                int clientPort = socket.getPort();
                printServerLog("클라이언트 연결됨 : " + clientHost + " : " + clientPort);

                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                Object obj = inputStream.readObject();
                printServerLog("데이터 받음 : " + obj);

                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(obj + " from Server");
                outputStream.flush();
                printServerLog("데이터 보냄");

                socket.close();
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

먼저 ServerSocket 클래스로 소켓 서버 객체를 만들었다.

            int portNumber = 5001;

            ServerSocket server = new ServerSocket(portNumber);
            printServerLog("서버 시작함" + portNumber);

while 구문을 이용해 클라이언트의 접속을 기다리다가 클라이언트의 접속 요청이 왔을 때 accept 메소드로 소켓 객체를 반환받아 클라이언트 소켓의 연결 정보와 데이터를 확인한다.

            while (true) {
                Socket socket = server.accept();
                InetAddress clientHost = socket.getLocalAddress();
                int clientPort = socket.getPort();
                printServerLog("클라이언트 연결됨 : " + clientHost + " : " + clientPort);

                ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
                Object obj = inputStream.readObject();
                printServerLog("데이터 받음 : " + obj);

클라이언트에서 보내온 문자열에 " from Server"를 붙여 클라이언트로 다시 보낸다.

                ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
                outputStream.writeObject(obj + " from Server");
                outputStream.flush();
                printServerLog("데이터 보냄");

마지막으로 통신을 끝내기 위해 소켓을 close 한다.

            socket.close();

버튼을 누르면 위에서 정의한 메소드들을 사용해서 통신을 하도록 하였다.

네트워킹 기능을 사용하므로 스레드를 사용해 동작하도록 하였다.

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                final String data = editText.getText().toString();
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        send(data);
                    }
                }).start();
            }
        });

        Button button2 = findViewById(R.id.button2);
        button2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        startServer();
                    }
                }).start();
            }
        });

AndroidManifest.xml 파일에 INTERNET 권한을 추가한다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.techtown.socket">

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

0개의 댓글