ANDROID/Android 앱 프로그래밍

[Android] 소켓(Socket) 사용하기

주 녕 2021. 6. 28. 20:10
728x90

모든 내용은 Do it! 안드로이드 앱 프로그래밍을 바탕으로 정리한 것입니다. 

 

소켓(Socket)

IP 주소로 목적지 호스트를 찾아내고 포트로 통신 접속점을 찾아내는 소켓 연결

TCP와 UDP 방식으로 나눌 수 있는데, 일반적인 프로그래밍에서는 대부분 TCP 연결을 사용함

 

HTTP 프로토콜과 소켓

HTTP 프로토콜은 소켓으로 웹서버에 연결한 후에 요청을 전송하고 응답을 받은 다음 연결을 끊음 비연결성(stateless)

실시간으로 데이터를 처리하는 앱은 응답 속도를 높이기 위해 연결성이 있는 소켓 연결을 선호했음

 

BUT 지금은?

  • 인터넷의 속도가 빨라져 HTTP 프로토콜을 사용하는 웹이 일반적
  • 속도가 그렇게 느리지 않으면서도 국제 표준을 따를 수 있다는 장점을 가진 웹서버로 많은 서버가 만들어지게 됨

 

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

이전에는 권장사항이었지만 현재 플랫폼 버전에서는 강제사항이 되었음. 따라서 스레드를 사용하지 않으면 네트워킹 기능 자체가 동작하지 않음!

스레드를 사용하면서 UI를 업데이트 하려면 핸들러를 사용해야 함

 

 

[예제]

AndroidManifest.xml

<manifest ...>

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

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    EditText editText;
    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.send_text);
        textView2 = findViewById(R.id.server_text);

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

        Button serverButton = findViewById(R.id.server_btn);
        serverButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                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");
            }
        });
    }
  • 첫번째 버튼을 눌렀을 때는 send() 메서드, 두번째 버튼을 눌렀을 때는 startServer() 메서드를 호출하도록 함
    • 이 2개의 메서드는 모두 네트워킹 기능을 사용할 것이므로 스레드로 만들어야 함
  • 결과를 출력하기 위한 textview, textview2
    • printClientLog()는 화면 상단의 클라이언트 레이아웃에 결과를 출력
    • printServerLog()는 화면 하단의 서버 레이아웃에 결과를 출력
    • 새로 만들어진 스레드에서 이 메서드들을 호출하여 UI를 업데이트 할 것이므로 핸들러 객체를 이용함
      • Runnable 객체의 run() 메서드 안에서 TextView를 접근하고 있음
      • TextView의 append() 메서드로 전달될 파라미터는 그대로 전달되어야 하므로 final로 정의함
...
    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();
        }
    }
}
  • 클라이언트에서 데이터를 전송하는 send() 메서드
    • 예제에서는 서버와 클라이언트가 5001번 포트를 사용하도록 함
    • 접속할 IP 주소는 "localhost", 포트는 5001번을 사용하고 있음
    • new 연산자로 만든 소켓은 이 IP 주소와 포트 번호를 파라미터로 전달받으며, 새로 만들어진 소켓을 통해 데이터를 보내거나 받고 싶을 때는 getOutputStream()과 getInputStream() 메서드로 입출력 스트림 객체를 참조함
    • 예제에서는 문자열 객체를 그대로 보내기 위해 ObjectOutputStream과 ObjectInputStream 클래스를 사용함
  • 클라이언트가 접속할 서버는 startServer() 메서드로 구현
    • 소켓 서버는 ServerSocket 클래스로 만든 후, 클라이언트로부터 요청을 처리할 수 있는데 포트 번호는 클라이언트에서 접속할 5001번을 그대로 사용함
    • while 구문을 사용해서 클라이언트의 접속을 기다리다가 클라이언트의 접속 요청이 왔을 때 accept() 메서드를 통해 소켓 객체가 반환되므로 클라이언트 소켓의 연결 정보를 확인할 수 있음

 

*실제로 앱을 만들 때는 ObjectInputStream과 ObjectOutputStream을 잘 사용하지 않음!

이 두 클래스는 자바의 객체(Object) 정보를 편리하게 주고 받을 수 있도록 만들어진 것이므로 자바가 아닌 다른 언어로 만들어진 서버와 통신할 경우에는 데이터 송수신이 정상적으로 작동하지 않을 수 있음. 따라서 일반적으로는 DataInputStream과 DataOutputStream을 주로 사용함!

 

 

 

 

 

 

*TCP와 UDP, 스레드와 핸들러에 대한 자세한 내용은 아래 포스팅 참고!

 

HTTP | 인터넷 네트워크

모든 내용의 출처는 inflearn 김영한 강사님의 모든 개발자를 위한 HTTP 웹 기본 지식입니다. 인터넷 통신 인터넷 망을 통해서 메세지를 보내야 함 이 메세지는 어떻게 수많은 중간 노드(서버)를 거

junyoung-developer.tistory.com

 

[Android] 핸들러(Handler)

모든 내용은 Do it! 안드로이드 앱 프로그래밍을 바탕으로 정리한 것입니다. 핸들러(Handler) 새로운 프로젝트를 만들면 자동으로 생성되는 메인 액티비티는 앱이 실행될 때 하나의 프로세스에서

junyoung-developer.tistory.com

728x90