티스토리 뷰

# 네트워킹?

: 인터넷에 연결되어 있는 원격지의 서버 또는 원격지의 단말과 통신해서 데이터를 주고받는 동작

💡 네트워킹을 통해 인터넷에 연결되어 있는 여러 단말을 동시에 사용할 수 있어 다양한 데이터 자원을 효율적으로 사용가능!

 

# 네트워크 연결 방식

✔ 가장 단순한 네트워크 연결 방식은 클라이언트와 서버가 일대일로 연결하는 '2-tier Client/Server' 방식 (ex. HTTP 프로토콜, FTP 프로토콜, POP3 프로토콜...)

✔ 서버를 더 유연하게 구성하고 싶을 때는 '3-tier' 연결방식 사용(서버를 응용서버와 데이터 서버로 구성)

✔ 단말 간 통신은 서버를 두지 않고 단말끼리 서버와 클라이언트 역할을 하는 'Peer-to-Peer' 연결 방식 사용

 

# 소켓

: 컴퓨터 네트워크를 경유하는 프로세스 간 통신의 종착점

: 프로그램이 네트워크에서 데이터를 통신할 수 있도록 연결해주는 연결부

 

✔ 소켓은 TCP/IP 수준의 통신 방식을 제공

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

✔ TCP와 UDP 방식으로 나누어짐(일반적으로 TCP사용)

 안드로이드에서 소켓 연결 등을 시도하거나 응답을 받아 처리할 때 반드시 스레드를 사용!

 

# 단말끼리 통신하는 소켓을 구현해보자!

설명) 클라이언트 단말에서 메시지와 함께 요청을 보내면, 서버 단말에서 메시지를 받아 뒤에 'from Server'를 붙여 다시 클라이언트로 응답  여기선, 하나의 단말로 통신(localhost)

 

1) 클라이언트 : 네트워킹이 필요한 부분에 스레드 생성(소켓 전송) 및 start()

//네트워킹은 무조건 스레드로!
new Thread(new Runnable() {
  @Override
  public void run() {
    send(data); //네트워킹 작업은 send()함수로 따로 구현
  }
}).start();

//send() 메서드
public void send(String data) {
        try {
            int portNumber = 5001; //서버 포트번호
            Socket sock = new Socket("localhost", portNumber); //IP주소와 포트번호로 소켓 연결

            ObjectOutputStream outstream = new ObjectOutputStream(sock.getOutputStream()); //소켓으로 데이터 보내기
            outstream.writeObject(data);
            outstream.flush();
            printClientLog("데이터 전송함.");

            ObjectInputStream instream = new ObjectInputStream(sock.getInputStream()); //소켓으로 데이터 받기
            printClientLog("서버로부터 받음 : " + instream.readObject());
            sock.close(); //소켓 연결 끊기
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    
  //printClientLog() 메서드
  public void printClientLog(final String data) { //클라이언트 쪽 로그를 출력, 스레드에서 호출 할 것임으로 핸들러 사용
        Log.d("MainActivity", data);

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

    }

2) 서버 : 네트워킹이 필요한 부분에 스레드 생성(서버 시작) 및 start()

//네트워킹은 무조건 스레드로!
new Thread(new Runnable() {
  @Override
  public void run() {
    startServer();
  }
}).start();

//startServer() 메서드
public void startServer() {
        try {
            int portNumber = 5001;

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

            while(true) { //클라이언트 접속을 기다림
                Socket sock = server.accept(); //클라이언트가 접속 했을 때 만들어지는 소켓 객체 참조
                InetAddress clientHost = sock.getLocalAddress();
                int clientPort = sock.getPort();
                printServerLog("클라이언트 연결됨 : " + clientHost + " : " + clientPort);

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

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

                sock.close();
            }
        } catch(Exception ex) {
            ex.printStackTrace();
        }
    }
    
    //printServerLog() 메서드
    public void printServerLog(final String data) {
        Log.d("MainActivity", data);

        handler.post(new Runnable() { //스레드에서 호출 할 것임으로 핸들러 사용
            @Override
            public void run() {
                textView2.append(data + "\n");
            }
        });
    }

💡 ObjectInputStream과 ObjectOutputStream은 자바의 객체 정보를 편리하게 주고받을 수 있도록 만들어졌기 때문에 자바가 아닌 다른 언어로 만들어진 서버와 통신할 경우 데이터 송수신이 정상적으로 이루어지지 않을 수 있어서 잘 사용X (대신, DataInputStream과 DataOutputStream을 많이 사용

 

# HTTP 프로토콜

: HTTP(Hypertext Transfer Protocol)는 인터넷상에서 데이터를 주고 받기 위한 서버/클라이언트 모델을 따르는 프로토콜

 

비연결성이기 때문에 웹 서버에 연결한 후에 요청을 전송하고 응답을 받은 다음 연결을 끊음

✔ 자바에서 HTTP 클라이언트를 만드는 가장 간단한 방법은 URL 객체를 만들고 openConnection 메서드를 호출하여 HttpURLConnection 객체를 만드는 것 → 단점) 요청과 응답에 필요한 코드가 많음 → 해결책) 웹 요청과 응답을 단순화해주는 Volley 라이브러리 사용!

// build.gradle
dependencies {
    ...
    implementation 'com.android.volley:volley:1.2.0' //Volley 라이브러리
}

✔ HTTP 프로토콜을 통한 웹 응답으로 받는 데이터는 JSON(JavaScript Object Notation)포맷으로 된 데이터 → 문제) JSON 포맷 데이터를 객체로 변환해야 JAVA에서 데이터 처리 가능! → 해결책) JSON 응답을 자바 객체로 바꾸기 위해 Gson 라이브러리 사용!

// build.gradle
...
dependencies {
    ...
    implementation 'com.google.code.gson:gson:2.8.6' //Gson 라이브러리
}

 

# 웹 서버 네트워킹을 구현해보자!

설명) 에딧텍스트에 영화 API 서버 주소를 입력하고 '요청하기' 버튼을 누르면, 리싸이클러뷰에 영화 리스트가 나타남.

 

1) build.gradle에 외부 라이브러리 추가

dependencies {
    ...
    implementation 'com.android.volley:volley:1.2.0' //Volley 라이브러리
    implementation 'com.google.code.gson:gson:2.8.6' //Gson 라이브러리
}

 

2) 매니페스트 파일에 인터넷 권한 추가

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.highfive.samplemovie">

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

    <application
        android:usesCleartextTraffic="true" //추가
...

 

3) 액티비티 XML 레이아웃 구성

<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/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textPersonName"
        android:text="http://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=072b596b4a06cbb74e5f13a6bcdba521&#38;targetDt=20120101" />

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="요청하기" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

 

4) 영화 정보 JSON 응답의 포맷에 맞추어 자바 클래스 정의

//JSON 응답
{
"boxOfficeResult":
    {
      "boxofficeType":"일별 박스오피스",
      "showRange":"20120101~20120101",
      "dailyBoxOfficeList":[
     	{"rnum":"1",
         "rank":"1",
         "rankInten":"0",
         "rankOldAndNew":"OLD",
         "movieCd":"20112207",
         "movieNm":"미션임파서블:고스트프로토콜",
         "openDt":"2011-12-15",
         "salesAmt":"2776060500",
         "salesShare":"36.3",
         "salesInten":"-415699000",
         "salesChange":"-13",
         "salesAcc":"40541108500",
         "audiCnt":"353274",
         "audiInten":"-60106",
         "audiChange":"-14.5",
         "audiAcc":"5328435",
         "scrnCnt":"697",
         "showCnt":"3223"},
         {...},
         ...
      ]
    }
}
//MovieList.java
public class MovieList {
    MovieListResult boxOfficeResult;
}

* boxOfficeResult는 응답 json파일에 있는 속성값이며, MovieListResult 객체 또한 응답 json파일에 있는 boxOfficeResult의 자료형과 같음

//MovieListResult.java
public class MovieListResult {
    String boxofficeType;
    String showRange;

    ArrayList<com.highfive.samplemovie.Movie> dailyBoxOfficeList = new ArrayList<Movie>();
}
//Movie.java
public class Movie {
    String rnum;
    String rank;
    String rankInten;
    String rankOldAndNew;
    String movieCd;
    String movieNm;
    String openDt;
    String salesAmt;
    String salesShare;
    String salesInten;
    String salesChange;
    String salesAcc;
    String audiCnt;
    String audiInten;
    String audiChange;
    String audiAcc;
    String scrnCnt;
    String showCnt;
}

 

5) 어댑터 클래스 정의 MovieAdqpter.java

public class MovieAdapter extends RecyclerView.Adapter<MovieAdapter.ViewHolder> {
    ArrayList<Movie> items = new ArrayList<Movie>();

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { //뷰홀더가 만들어질 때 자동 호출
        LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
        View itemView = inflater.inflate(R.layout.movie_item, viewGroup, false); //movie_item.xml 인플레이션

        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
        Movie item = items.get(position); //현재 인덱스에 맞는 Movie객체 참조
        viewHolder.setItem(item); //뷰홀더에 설정
    }

    @Override
    public int getItemCount() {
        return items.size();
    }

    public void addItem(Movie item) {
        items.add(item);
    }

    public void setItems(ArrayList<Movie> items) {
        this.items = items;
    }

    public Movie getItem(int position) {
        return items.get(position);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        TextView textView2;

        public ViewHolder(View itemView) {
            super(itemView);

            textView = itemView.findViewById(R.id.textView);
            textView2 = itemView.findViewById(R.id.textView2);
        }

        public void setItem(Movie item) {
            textView.setText(item.movieNm);
            textView2.setText(item.audiCnt + " 명");
        }
    }
}

 

6) 각 아이템을 위한 XML 레이아웃 정의

<!-- movie_item.xml -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="8dp"
        android:layout_marginTop="4dp"
        android:layout_marginBottom="4dp"
        app:cardBackgroundColor="#FFFFFFFF"
        app:cardCornerRadius="10dp"
        app:cardElevation="5dp" >

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <ImageView
                android:id="@+id/imageView"
                android:layout_width="80dp"
                android:layout_height="80dp"
                android:padding="5dp"
                app:srcCompat="@drawable/movie" />

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

                <TextView
                    android:id="@+id/textView"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="제목"
                    android:textSize="22sp"
                    android:maxLines="1"/>

                <TextView
                    android:id="@+id/textView2"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="10dp"
                    android:paddingRight="10dp"
                    android:gravity="right"
                    android:text="관객수"
                    android:textColor="#FF0000FF"
                    android:textSize="16sp" />
            </LinearLayout>

        </LinearLayout>
        
    </androidx.cardview.widget.CardView>
    
</LinearLayout>

 

7) 액티비티 자바 소스파일 수정

//MainActivity.java
public class MainActivity extends AppCompatActivity {
    EditText editText;
    TextView textView;

    static RequestQueue requestQueue; //한 번만 만들어 계속 사용 가능

    RecyclerView recyclerView;
    MovieAdapter adapter;

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

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

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                makeRequest();
            }
        });

        if (requestQueue == null) {
            requestQueue = Volley.newRequestQueue(getApplicationContext()); //요청 큐 생성
        }

        recyclerView = findViewById(R.id.recyclerView); //xml레이아웃에 정의한 리사이클러뷰 객체 참조

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); //리싸이클러뷰를 세로방향 리스트 모양으로 설정하기 위해
        recyclerView.setLayoutManager(layoutManager);

        adapter = new MovieAdapter();
        recyclerView.setAdapter(adapter); //리싸이클러뷰에 어댑터 설정

    }


    public void makeRequest() {
        String url = editText.getText().toString();

        //요청 객체 생성
        StringRequest request = new StringRequest(
                Request.Method.GET, //파라미터1, 요청 방식
                url, //파라미터2, 웹 사이트 주소
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        println("응답 -> " + response);

                        processResponse(response); //응답을 받았을 때 processResponse 메서드 호출
                    }
                }, //파라미터3, 응답받을 리스너
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        println("에러 -> " + error.getMessage());
                    }
                } //파라미터4, 오류 리스너
        ) {
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String,String> params = new HashMap<String,String>();
                 //POST방식을 사용한다면, params객체에 파라미터 값을 넣어주면 됨
                 
                return params;
            }
        };

        request.setShouldCache(false); //캐쉬를 사용하지 않도록 설정
        requestQueue.add(request); //요청 객체를 요청 큐에 넣기
        println("요청 보냄.");
    }

    public void println(String data) {
        Log.d("MainActivity", data);
    }

    public void processResponse(String response) {
        Gson gson = new Gson();
        MovieList movieList = gson.fromJson(response, MovieList.class); //응답받은 JSON 문자열을 MovieList로 변환하기

        println("영화정보의 수 : " + movieList.boxOfficeResult.dailyBoxOfficeList.size());

        for (int i = 0; i < movieList.boxOfficeResult.dailyBoxOfficeList.size(); i++) {
            Movie movie = movieList.boxOfficeResult.dailyBoxOfficeList.get(i);

            adapter.addItem(movie);
        }

        adapter.notifyDataSetChanged(); //변경사항이 반영
    }

}

 


출처

Do it! 안드로이드 앱 프로그래밍(개정8판)

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/02   »
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
글 보관함