본문 바로가기
안드로이드 자바

[Java][Android] 소켓(Socket)을 이용해 에코서버(Echo Server) 구현

by teamnova 2023. 2. 13.
728x90

에코서버란 클라이언트가 전송해 주는 데이터를 그대로 반환해 주는 서버를 이야기 합니다.

오늘은 소켓을 이용하여 에코서버를 구현해 보겠습니다.

 

 

안드로이드 에뮬레이터를 클라이언트, 서버의 경우 인텔리제이를 통해 구현하였습니다.

 

결과는 다음과 같습니다.

 

먼저 인텔리제이에서 서버는 다음과 같이 구현하였습니다.

 

서버 코드

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;

public class SocketServer {

    public static void main(String[]args) {


        int portNumber = 5001;

        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() 메서드는 스레드 정지 상태로 대기하다가 서버 소켓이 생성당시 전달받은 포트번호로 접근해 연결된 소켓을 

반환해 줍니다.

 

 

 

다음은 안드로이드 소스 코드 입니다. (클라이언트 부분)

 

먼저 네트워크 통신을 위해 안드로이드 매니페스트에 인터넷 권한을 부여합니다.

 

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

레이아웃 코드를 작성합니다.

<?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 = 5001; //포트 번호는 서버측과 똑같이

                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();
            }
        }
    }

}

 

클라이언트에서 소켓이 생성됨과 동시에 서버에 연결을 시도합니다.

때문에 클라이언트에서 소켓이 만들어지기 전에  서버가 실행되어 있어야합니다.

 

결과화면에서 서버 ip의 경우 현재 연결된 컴퓨터의 ip 를 입력해 주면 됩니다. 

윈도우의 경우 cmd 실행 후 ipconfig 를 통해 확인할 수 있습다.

ipv4 주소를 입력해 주면 됩니다.

 

또한 서버에 입력된 포트와 클라이언트에서 입력된 포트는 동일해야합니다.

 

예제 실행 순서는 다음과 같습니다.

1. 서버를 실행합니다.

2. 클라이언트 프로그램을 실행합니다.

3. 서버 ip 를 입력해 줍니다.(현재 pc 환경이므로 작업중인 pc의 ip를 찾아서 입력해 주면 됩니다.)

4. 전송할 내용을 입력후 전송을 누릅니다.

 

서버에선 클라이언트에서 전달한 데이터가 전달되고 

클라이언트에선 보냈던 데이터가 그대로 서버에서 다시 전달되어 토스트로 결과를 확인할 수 있습니다.