소켓 통신에서는 클라이언트와 서버 사이의 연결이 지속되고 실시간으로 양방향으로 데이터를 주고받는다. 오늘 할 일은 아래와 같다.

  1. PC에서 이클립스와 Java를 통해 서버를 만들고 소켓을 뚫어서 돌린다.
  2. 안드로이드 단말에서 위에서 만든 서버로 요청을 보내고 응답을 받는다.


여기서 주의해야할 것이 있다. PC로 서버를 돌릴 때, 공용 wifi를 사용했다면 IP주소는 192로 시작한다. 이 때는 안드로이드 역시 같은 공용 와이파이를 사용하는 상태라야 서버로 접근할 수 있다. 왜냐하면 공유기에 물려서 내부에서만 유효한 네트워크에 연결된 상태이기 때문에 외부에서 접근할 방법이 없기 때문이다. 이 부분을 모르고 포트포워딩을 하려다가 괜히 삽질로 시간만 날릴 수 있다.

먼저 이클립스에서 프로젝트를 생성하여 서버측 코드를 작성한다.


public class SocketServer {

    public static void main(String[] args) {
        int portNumber = 5555;

        try {
            System.out.println("서버를 시작합니다...");
            ServerSocket serverSocket = new ServerSocket(portNumber); //포트번호를 매개변수로 전달하면서 서버 소켓 열기
            System.out.println("포트 " + portNumber + "에서 요청 대기중...");

            while(true) {
                Socket socket = serverSocket.accept(); //클라이언트가 접근했을 때 accept() 메소드를 통해 클라이언트 소켓 객체 참조
                InetAddress clientHost = socket.getLocalAddress();
                int clientPort = socket.getPort();
                System.out.println("클라이언트 연결됨. 호스트 : " + clientHost + ", 포트 : " + clientPort);

                ObjectInputStream instream = new ObjectInputStream(socket.getInputStream()); //소켓의 입력 스트림 객체 참조
                Object obj = instream.readObject(); // 입력 스트림으로부터 Object 객체 가져오기
                System.out.println("클라이언트로부터 받은 데이터 : " + obj); // 가져온 객체 출력

                ObjectOutputStream outstream = new ObjectOutputStream(socket.getOutputStream()); //소켓의 출력 스트림 객체 참조
                outstream.writeObject(obj + " from server"); //출력 스트림에 응답 넣기
                outstream.flush(); // 출력
                socket.close(); //소켓 해제
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
    }
}

서버 소켓의 accept() 메소드를 사용하면 클라이언트의 요청이 왔을 때 클라이언트 소켓의 연결정보를 확인할 수 있다. 여기서 참조한 소켓의 입력 스트림을 통해 입력된 데이터를 읽고 출력 스트림을 생성하여 소켓을통해 내보낸다. 마지막으로 소켓을 해제한다.


이제 클라이언트 측 코드를 안드로이드 스튜디오에서 작성할 차례다. 먼저 화면은 연결할 서버의 IP주소와 전송할 데이터를 위한 입력상자 2개와 클릭했을 때 전송을 하기위한 버튼을 하나 만든다.

<?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">

    <EditText
        android:id="@+id/addressInput"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="서버 IP"
        android:textSize="20dp"
        android:layout_marginTop="200dp"
        android:layout_gravity="center_horizontal"/>

    <EditText
        android:id="@+id/dataInput"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:hint="보낼 데이터"
        android:textSize="20dp"
        android:layout_marginTop="10dp"
        android:layout_gravity="center_horizontal"/>

    <Button
        android:id="@+id/socketConnectBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="서버 전송"
        android:textSize="24dp"
        android:layout_marginTop="20dp"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>


클라이언트 자바 코드는 아래와 같이 작성한다.


public class MainActivity extends AppCompatActivity {

    EditText addressInput; //호스트 IP 입력상자
    EditText dataInput; //서버로 전송할 데이터 입력상자

    String str;
    String addr;

    String response; //서버 응답

    Handler handler = new Handler(); // 토스트를 띄우기 위한 메인스레드 핸들러 객체 생성

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

        addressInput = findViewById(R.id.addressInput);
        dataInput = findViewById(R.id.dataInput);
        Button socketConnectBtn = findViewById(R.id.socketConnectBtn);

        /*
        버튼을 클릭했을 때
        1. 입력상자의 서버 IP 주소와 전송할 데이터 가져오기
        2. 소켓통신을 위한 스레드의 매개변수로 넣어주어 스레드 객체 생성
        3. 스레드 시작
        */
        socketConnectBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                addr = addressInput.getText().toString().trim();
                str = dataInput.getText().toString();
                SocketThread thread = new SocketThread(addr, str);
                thread.start();
            }
        });
    }

    class SocketThread extends Thread{

        String host; // 서버 IP
        String data; // 전송 데이터

        public SocketThread(String host, String data){
            this.host = host;
            this.data = data;
        }

        @Override
        public void run() {

            try{
                int port = 5555; //포트 번호는 서버측과 똑같이
                Socket socket = new Socket(host, port); // 소켓 열어주기
                ObjectOutputStream outstream = new ObjectOutputStream(socket.getOutputStream()); //소켓의 출력 스트림 참조
                outstream.writeObject(data); // 출력 스트림에 데이터 넣기
                outstream.flush(); // 출력

                ObjectInputStream instream = new ObjectInputStream(socket.getInputStream()); // 소켓의 입력 스트림 참조
                response = (String) instream.readObject(); // 응답 가져오기

                /* 토스트로 서버측 응답 결과 띄워줄 러너블 객체 생성하여 메인스레드 핸들러로 전달 */
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(MainActivity.this, "서버 응답 : " + response, Toast.LENGTH_LONG).show();
                    }
                });

                socket.close(); // 소켓 해제

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

여기서 중요한 것이 있다.

안드로이드에서는 네트워킹을 반드시 새로운 스레드를 통해서 해야한다. 최신 버전부터 이렇게 변경되었다. 따라서 네트워킹을 통해 UI를 업데이트 할 때는 메인 스레드의 핸들러를 통해서 해야한다.

아무튼 버튼을 클릭했을 때 새로운 스레드를 만들고 소켓을 뚫은다음 소켓의 출력 스트림을 통해 데이터를 내보내주는 것이 전부이다. 이 때 소켓 생성 시 서버IP와 연결할 포트번호를 매개변수로 전달해주어야한다.

또한 네트워크 통신을 하기위해 매니페스트 파일을 열고 다음과 같이 인터넷 권한을 부여한다.

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


먼저 이클립스에서 클래스를 실행하면 다음과 같은 화면을 볼 수 있다.

1


이제 안드로이드 앱을 실행한 아래 사진과 같이 다음 IP주소와 보낼 데이터를 입력하고 보내면 된다.

2

서버 IP주소는 PC에서 네트워크 및 공유센터에서 확인하면 된다. 아무튼 코드를 작성한대로 서버에서 받은 데이터에 " from server" 라는 문자열을 덧붙여서 응답으로 보내는 것을 볼 수 있다.


이 때, 서버측으로 다시 돌아가서 출력 결과를 마저 확인해보자.

3

코드에서 System.out.println()을 통해 클라이언트의 IP주소와 포트 번호를 확인할 수 있도록 해놓았었다. 또한 받은 데이터 역시 출력하도록 했었다. 그 결과를 위 출력화면에서 확인할 수 있다.


아주 간단한 기능만 하는 서버와 클라이언트를 작성했지만, 이 내용은 안드로이드에서 네트워크를 사용하기 위한 기본이 된다. 결국 모바일 게임등의 네트워크가 필요한 애플리케이션들은 모두 위의 내용을 기초하여 구현하는 것이기 때문이다.

+ Recent posts