본문 바로가기

수업 정리

75일차 수업정리(Android - Socket)

**Socket

    => 네트워크 인터페이스 카드를 추상화한 클래스

    => 통신 방식

        - 저수준 통신 : Scoket을 직접 생성하여 통신(보편적으로 의미하는 Socket 통신)

        - 고수준 통신 : Socket을 직접 생성하지 않고 추상화된 클래스를 이용

    => 저수준 통신이 효율은 좋으나 사용하기 어려움

1. java.net.InetAddress

    => 인터넷 주소와 관련된 클래스

    => 생성자는 없고, static메소드를 이용하여 인스턴스를 생성

        - getLocalHost, getByName, getAllByName을 이용하여 생성

        - 예외 처리를 강제

 

2. java.net.Socket 클래스

  1) 생성자

    - Socket()

    - Socket(InetAddress addr, int port)

    - Socket(String addr, int port)

    - Socket(InetAddress addr, int port, InetAddress localAddr, int localPort)

        => 앞의 2개는 접속할 상대방의 주소정보, 뒤의 2개는 자신의 주소정보 - Network Interface card가 2개 이상

        => 상대방의 주소를 설정하여 생성시 자동으로 접속

    - addr이 잘못되면 NullPointerException이 발생

    - port번호가 잘못되면 illegalArgumentException이 발생

  2) 입출력을 위한 스트림을 제공하는 메소드

    - InputStream getInputStream()

    - OutputStream getOutputStream()

  3) UDP와 TCP

    => UDP : 비 연결형 통신 : 데이터를 일방적으로 보내기만 하는 방식

        - 효율은 좋으나 신뢰성이 떨어짐(APNS, FCM(스마트폰 알림메시지)

    => TCP : 연결형 통신 : 서로 연결하고 대화를 주고받는 방식으로 통신

        - 효율은 UDP에 비해 떨어지지만 신뢰성이 높음

    => java에서는 일반 소켓가 ServerSocket을 이용하면 TCP통신이고, DatagramSocket을 이용하면 UDP통신

 

**Android와 PC Application TCP Socket통신

1. java Project

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) {
		try {
			//포트번호 설정
			//1024번 이하, 8080, 1521, 3306은 제외하고 설정
			int portNumber = 11000;
			System.out.println("서버 실행중...");
			//서버 소켓 생성 - 접속 준비
			ServerSocket ss = new ServerSocket(portNumber);
			while(true) {
				//클라이언트 접속 대기 여기서 블럭 되어 있다가
				//클라이언트가 접속시 클라이언트와 통신할 수 있는 소켓을 리턴하고
				//아래 문장을 실행
				Socket socket = ss.accept();
				//클라이언트 정보 출력 
				System.out.println(socket.getInetAddress() + " : " + socket.getLocalPort());
				//클라이언트와 문자 통신을 위한 스트림 생성
				BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				
				//한 줄 읽어오기 
				String msg = br.readLine();
				System.out.println(msg);
				PrintWriter pw = new PrintWriter(socket.getOutputStream());
				
				//한줄 메시지 전송
				pw.write("서버가 보내는 메시지");
				pw.flush();
				
				//소켓 종
				
			}
		}catch (Exception e) {
			// TODO: handle exception
		}
	}
}

 

2. Android Application

  1) 프로젝트 생성

 

  2) 화면 디자인

    => 전송할 문자열을 입력받을 EditText 1개와 전송받은 문자열을 출력할 TextView 1개와 전송 버튼 1개를 디자인

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/send"/>

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/receive"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="전송"
        android:id="@+id/btn"/>
</LinearLayout>

 

  3) Activity에 소스 작성

package com.example.android0722;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class MainActivity extends AppCompatActivity {
    //뷰의 참조를 저장하기 위한 변수
    EditText send;
    TextView receive;
    Button btn;

    //네트워크 사용을 위한 스레드
    //스레드는 한 번 사용하면 새로 생성해야 사용이 가능합니다.
    //
    class ThreadEx extends Thread{
        @Override
      public void run(){
            String content = null;
          try{
              //서버에 접속하는 소켓 생성
              Socket socket =
                      new Socket(
                              "192.168.0.200",
                              11000);
              //스트림 생성
              ObjectOutputStream oos =
                      new ObjectOutputStream(
                              socket.getOutputStream()
                      );
               oos.writeObject(send.getText().toString());
               oos.flush();

              ObjectInputStream ois =
                      new ObjectInputStream(
                              socket.getInputStream()
                      );
              content = (String)ois.readObject();
              //스레드에서는 UI갱신을 못함
              //receive.setText(content);
              socket.close();

          }catch(Exception e){
              Log.e("전송 에러", e.getMessage());
          }
            //핸들러에게 전송할 메시지 생성
            Message msg = new Message();
            msg.obj = content;
            //핸들러 호출
            handler.sendMessage(msg);
      }
    };

    //스레드가 전송한 내용을 출력하기 위한 핸들러
    Handler handler = new Handler(
            Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg){
            String temp = (String)msg.obj;
            receive.setText(temp);
        }
    };


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

        send = (EditText)findViewById(R.id.send);
        receive = (TextView)findViewById(R.id.receive);
        btn = (Button)findViewById(R.id.btn);

        //버튼의 클릭 이벤트 처리
        btn.setOnClickListener(
                new Button.OnClickListener(){
            public void onClick(View view){
                //스레드 시작
                new ThreadEx().start();
            }
        });

    }
}

 

  4) 안드로이드에서는 네트워크를 사용하기 위한 권한 설정

    => INTERNET

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

 

**URL 통신

    => 웹의 주소를 이용하는 통신 방식 

    => http 나 https 프로토콜를 이용하는 통신 방식

    => 스마트 폰은 보안 문제 때문에 http는 추가 설정을 해야만 통신이 가능

 

1. URL 클래스

    => 접속할 주소를 만들어주는 클래스

  1) 생성자

    - URL(String addr) : new URL(“https://www.daum.net/”)

    - URL(String protocol, String host, int port, String file) : new URL(“https”, “www.daum.net”, 443, “/“);

  2) 메소드

    => get으로 시작하는 메소드를 이용해서 각 부분의 정보를 리턴받을 수 있음

  3) 주의할 점

    => 잘못된 URL을 대입하면 MalformedURLException이 발생

    => URL은 전부 인코딩 된 상태로 생성해야 함

        - URL에 영문이나 숫자 이외의 데이터가 있다면 인코딩을 해 주어야 함

        - URLEncoder.encode(변환할 문자열, “utf-8”)

 

2. URLConnection

    => URL에 접속하여 서버에게 데이터를 전송하고 읽어올수 있는 클래스

  1) 객체 생성

    - (실제 사용할 클래스)URL객체.openConnection();

    => 실제 사용할 클래스는 HttpURLConnection, HttpsURLConnection, JarURLConnection

  2) 옵션을 설정하는 메소드

    => setConnectTimeout(int timeout) : 접속이 안될때 최대 시도시간으로 미설정시 무제한

    => setUseCache(boolean newValue) : 변화가 거의 없는 경우, 브라우저의 캐시처럼 데이터를 로컬에 저장, 다시 불러오기 위한 옵션

    => post 방식의 파라미터나 헤더를 추가하는 메소드

        - setRequestProperty(String attr, String value)

        - addRequestProperty(String attr, String value)

    => 전송방식 선택 : setRequestMethod("GET" or "POST") : default는 GET

    => 요청 결과 리턴 : int getResponseCode()

  3) Stream을 리턴해주는 메소드

    - InputStream getInputStream()

    - OutputStream getOutputStream()

  4) http 통신의 문제

    => 보안이 취약하기 때문에 기본적으로 접속이 안되도록 되어 있음

    => 안드로이드에서는 application 설정에 android:usesClearTraffic="true"를 추가해야 함

 

**텍스트를 읽어서 출력하기

1. 실행가능한 Activity 추가

 

2. 화면 디자인

    => 버튼 1개를 배치하고, HTML을 출력할 TextView를 배치

        - html이 TextView의 크기를 넘어가면 스크롤 할 수 있도록 TextView를 ScrollView안에 배치

    => WebView, ListView(데이터 출력을 위한 뷰), MapView는 ScrollView를 상속받아 자체적으로 스크롤이 가능

        - 그 외의 View는 스크롤 기능이 없으므로 내용이 많은 경우 스크롤 뷰안에 배치해야 함

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".TextDownloadActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="다운로드"
        android:id="@+id/btndownload"/>
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="HTML 출력"
            android:id="@+id/display"/>
    </ScrollView>
</LinearLayout>

 

3. Activity.java 파일 작성

public class TextDownloadActivity extends AppCompatActivity {
    private TextView display;
    private Button download;
    
    //데이터를 다운로드 받을 클래스 
    //Thread는 재사용이 안되므로 클래스로 만들어서
    //필요할 때 마다 객체를 생성해서 사용 
    class ThreadEx extends Thread{
        public void run(){
            //다운로드 받은 문자열을 저장할 변수 
            String html = null;
            try{
                //다운로드 받을 주소 생성
                URL url = new URL("https://www.google.com");
                //연결 객체를 생성하고 옵션을 설정
                HttpURLConnection con = 
                        (HttpURLConnection)url.openConnection();
                con.setUseCaches(false);
                con.setConnectTimeout(30000);
                con.setRequestMethod("GET");
                //필요한 스트림을 생성해서 읽기
                //문자열을 읽을 스트림 생성
                BufferedReader br = 
                        new BufferedReader(
                            new InputStreamReader(
                                con.getInputStream()));
                StringBuilder sb = new StringBuilder();
                while(true){
                    //한 줄 읽기
                    String line = br.readLine();
                    //읽은 내용이 없으면 종료
                    if(line == null){
                        break;
                    }
                    //읽은 내용이 있으면 sb에 추가
                    sb.append(line + "\n");
                }
                html = sb.toString();
                //생성한 객체 닫기
                br.close();
                con.disconnect();
                
                //UI 갱신 할 거라면 Handler를 호출해서
                //데이터를 넘겨주기 
                Message message = new Message();
                message.obj = html;
                handler.sendMessage(message);
                
            }catch(Exception e){
                Log.e("다운로드 에러", e.getMessage());
            }
        }
    }
    
    //데이터를 출력하기 위한 객체
    //재사용이 가능하므로 하나의 객체를 바로 생성해서 사용 
    Handler handler = 
            new Handler(Looper.getMainLooper()){
        public void handleMessage(Message message){
            //데이터 가져오기
            String html = (String)message.obj;
            display.setText(html);
        }
    };
    
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_text_download);
        
        display = (TextView)findViewById(R.id.display);
        download = (Button)findViewById(R.id.download);
        
        download.setOnClickListener(
                new Button.OnClickListener() {
            @Override
            public void onClick(View view) {
                new ThreadEx().start();
            }
        });
    }
}

 

4. AndroidManifest.xml 파일에서 권한 확인

    => INTERNET 권한이 있어야 하고, 접속하는 곳이 http로 시작하는 경우 application에 android:usesCleartextTraffic="true" 추가

 

**데이터 다운로드 받아서 출력할 때 주의사항

    => 권한 설정하지 않으면 permit관련 에러

    => 다운로드 받는 코드를 Thread에 작성하지 않으면 앱은 Crash

    => UI 갱신을 스레드에서 직접 하면 아무일도 발생하지 않음

 

**이미지 파일 다운로드

    => 파일을 다운로드 받는 것은 BufferedReader 대신 BufferedInputStream을 사용하여 읽어온 후 파일에 기록

    => 파일은 계속 다운로드 받지 않고 대부분의 경우 처음 접속시만 다운로드 받아 저장 후 저장데이터를 확인하여 없을 경우만 다운로드

    => 파일이 업데이트 될 가능성이 있다면 서버를 만들때 업데이트 날짜를 기록해야하고, 앱은 접속시 업데이트 날짜 확인

        - 서버의 업데이트 날짜가 더 클경우 다운로드, 같을 경우 로컬 파일 사용

    => 안드로이드 앱은 자신의 데이터 디렉토리에만 파일을 저장할 수 있음

        - /data/data/패키지명/files 디렉토리가 자신의 데이터를 저장할 수 있는 디렉토리

        - 맨 앞의 /data대신 Environment.getDataDirectory().getAbsolutePath()를 이용해서도 생성 가능

 

**이미지 파일을 다운받아서 바로 ImageView에 출력하는 것과 파일로 저장하고 출력하기

1. 실행 가능한 Activity 생성

 

2. 레이아웃 수정

    => 버튼 2개, 이미지뷰 1개

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".ImageActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="이미지 바로 출력"
        android:id="@+id/btndrawimage"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="이미지 저장 후 출력"
        android:id="@+id/btnsaveimage"/>

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/imageview"/>

</LinearLayout>

 

3. 

 

 

 

 

TIP!

1. GET, POST

  1) GET : 파라미터를 URL에 포함하여 전송

    - 보안이 취약하고, 전송하는 크기에 제한이 있음

  2) POST :  파라미터를 header에 숨겨서 전송

    - 보안이 우수, 전송하는 크기에 제한이 없음

    - 인코딩을 잘 설정해야하고, 속도는 GET에 비해 느림, 

    - 최근에는 FORM의 데이터는 되도록 POST로 전송하는 것을 권장

    - Password, textarea, file이 존재하는 경우 반드시 POST

 

2. 응답코드

    - 200번대 : 정상 응답

    - 300번대 : 리다이렉트 중

    - 400번대 : 클라이언트 오류(URL 에러나 권한이 없거나 하는 등의 에러)

    - 500번대 : 서버 오류(서버의 로직이 잘못 수행되는 경우)