1. 기본 구조체 포인터 (* 선언에 없음)

struct로 구조체를 만드는데 java로 따지면 hashmap정도가 되겠다.

마침표 .을 이용해서 값을 저장하고 꺼내올 수 있다.

#include <stdio.h>
#include <string.h>

struct tagAddress
{
	char name[30];		// 이름
	char phone[20];		// 전화
	char address[100];	// 주소
};

void main( void ) 
{
	struct tagAddress ad;

	strcpy( ad.name, "홍길동" );
	strcpy( ad.phone, "02-1234-5678" );
	strcpy( ad.address, "서울시 양천구 목동아파트 13단지" );

	printf( "이름 : %s \n", ad.name );
	printf( "전화 : %s \n", ad.phone );
	printf( "주소 : %s \n", ad.address );
}

결과값

이름 : 홍길동
전화 : 02-1234-5678
주소 : 서울시 양천구 목동아파트 13단지

 

2.  구조체를 포인터 구조체로 선언함. 

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

struct tagAddress
{
	char name[30];		// 이름
	char phone[20];		// 전화
	char address[100];	// 주소
};

void main(void)
{
	struct tagAddress ad; //일반 구조체변수 선언
	struct tagAddress *pad; //포인터 구조체변수 선언
	//포인터 구조체를 선언하면 일반 구조체변수를 포인터 구조체에 저장 해야한다.
	pad = &ad;

	strcpy(ad.name, "홍길동");
	strcpy(ad.phone, "02-1234-5678");
	strcpy(ad.address, "서울시 양천구 목동아파트 13단지");

	printf("이름 : %s \n", ad.name);
	printf("전화 : %s \n", ad.phone);
	printf("주소 : %s \n", ad.address);
	printf("\n======포인터 구조체 적용====\n\n");

	//값 입력
	strcpy((*pad).name, "홍길2");
	strcpy((*pad).phone, "02-1234-5672");
	strcpy((*pad).address, "서울시 양천구 목동아파트 2단지");

	printf("이름 : %s \n", pad->name);
	printf("전화 : %s \n", pad->phone);
	printf("주소 : %s \n", pad->address);
}

 

결과값

이름 : 홍길동
전화 : 02-1234-5678
주소 : 서울시 양천구 목동아파트 13단지

======포인터 구조체 적용====

이름 : 홍길2
전화 : 02-1234-5672
주소 : 서울시 양천구 목동아파트 2단지

 

3. 구조체 안에 포인터 변수가 있는 형태

구조체 안에 포인터 변수를 알아보자

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

struct tagAddress
{
	char name[30];		// 이름
	int phone;		// 전화
	char address[100];	// 주소
	char *email[100];  // 이메일  //구조체안에 변수는 포인터변수던 그냥이던 똑같이 변수를 넣고 반환할 수 있다.
	int *byear; //생년월일  //구조체안에 변수는 포인터변수던 그냥이던 똑같이 변수를 넣고 반환할 수 있다.
};

void main(void)
{
	struct tagAddress ad; //일반 구조체변수 선언
	struct tagAddress *pad; //포인터 구조체변수 선언
	//포인터 구조체를 선언하면 일반 구조체변수를 포인터 구조체에 저장 해야한다.
	pad = &ad;

	//값 입력
	strcpy(ad.name, "홍길동");
	//strcpy(ad.phone, "0212345678"); XX 사용안됨 int 형이므로 =이용

	ad.phone = 101234; //값 처음에 0이 들어가면 안된다.
	
	strcpy(ad.address, "서울시 양천구 목동아파트 13단지");
	strcpy(ad.email, "dream1@nate.com");
	ad.byear = 97090911; //값 처음에 0이 들어가면 안된다.

	//출력

	printf("이름 : %s \n", ad.name);
	printf("전화 : %d \n", ad.phone);
	printf("주소 : %s \n", ad.address);
	printf("이메일 : %s \n", ad.email);
	printf("생년월일 : %d \n", ad.byear);

	printf("\n======포인터 구조체 적용====\n\n");

	//값 입력
	strcpy((*pad).name, "홍길2");
	ad.phone = 201234; //값 처음에 0이 들어가면 안된다.
	strcpy((*pad).address, "서울시 양천구 목동아파트 2단지");
	strcpy((*pad).email, "dream1@nate.com2");
	ad.byear = 97090912; //값 처음에 0이 들어가면 안된다.

	//출력
	printf("이름 : %s \n", pad->name);
	printf("전화 : %d \n", pad->phone);
	printf("주소 : %s \n", pad->address);
	printf("이메일 : %s \n", pad->email);
	printf("생년월일 : %d \n", pad->byear);
}

결과값

이름 : 홍길동
전화 : 101234
주소 : 서울시 양천구 목동아파트 13단지
이메일 : dream1@nate.com
생년월일 : 97090911

======포인터 구조체 적용====

이름 : 홍길2
전화 : 201234
주소 : 서울시 양천구 목동아파트 2단지
이메일 : dream1@nate.com2
생년월일 : 97090912

 

4. 구조체안에 포인터멤버가 있고 메모리를 할당하는 형태

코드를 보기전에 설명을 하면은

 

* 일반 선언된 구조체안에 멤버에 값을 넣어줄 때는

d1.numPtr = &num, d1.c = 'x'형태로 쓴다. (포인터 멤버(*)든 일반 멤버든 상관 없음)

 

* 포인터로 선언된 구조체안의 멤버에 값을 넣어줄 때는

d2->numPtr = &num, d2->c = 'y' 형태로 쓴다. (포인터 멤버(*)든 일반 멤버든 상관 없음)

또는 (*d2).numPtr, (*d2).c 형태로 써도 된다.

 

화살표 꺽쇠모양은 -> 이 포인터의 값(*d2)을 불러와서 그 값안의 멤버(numPtr)를 호출하는 기능이다.

따라서 꺽쇠는 포인터로 선언된 구조체에만 써야한다.

 

 

(*d2).numPtr을 쉽게 설명하면  dr이라는 구조체를 호출하여 자물쇠*를 푼다. 그뒤 푼값의 numPtr을 가르킨다.

참고로 자물쇠를 풀지않은 d2의 멤버가 정수형일 경우 주소를 가르킨다.

 

그래서 정수형일 경우는 &num처럼 &를 붙혀줘야한다.

 

꺽쇠는 *를 붙혀 자물쇠를 풀고 값을 가르키는 2가지 기능이 있다는 것을 알아두자.

 

printf로 값을 볼 때는 주소값이 아닌 값을 봐야한다.

 

일반 선언된 구조체는

printf("*d1.numPtr : %d\n", *d1.numPtr);

printf("d1.c : %c\n", d1.c);

로 볼수 있다.

 

d1.numptr자체는 주소이므로 *를 붙혀서 자물쇠를 풀어줘야 한다.

 

포인터로 선언된 구조체는 아래와 같이 프린트해야 한다.

printf("*d1->numPtr : %d\n", *d2->numPtr);

printf("d1->c : %c\n", d2->c);

 

 

풀소스 코드

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

typedef struct Data_s {
    char c;
    int* numPtr;    // 포인터
}Data;

int main()
{
    int num = 10;
    Data d1;    // 구조체 변수 선언
    Data* d2 = malloc(sizeof(Data));  // 구조체 포인터 변수 선언 및 메모리 할당

    //값 저장
    d1.numPtr = &num; //구조체 d1의 멤버인 numPtr에 num의 메모리 주소를 전달
    d2->numPtr = &num; //d2에 있는 Data의 메모리 주소를 역참조하여 멤버인 numPtr에 num의 메모리 주소 전달
    
    d1.c = 'x';
              //
    printf("d1.c : %c\n", d1.c);
    printf("*d1.numPtr : %d\n", *d1.numPtr);   // 구조체의 멤버를 역참조
    
    printf("*d2->numPtr : %d\n", *d2->numPtr);  // 구조체 포인터의 멤버를 역참조
    
    //아래처럼도 쓸수 있음
    //printf("*d2->numPtr : %d\n", *((*d2).numPtr));
    
    printf("sizeof(Data) : %d\n", sizeof(Data));    // 구조체의 크기

    //  *d2->numPtr = *((*d2).numPtr)= *(numPtr)


    //값 저장
    d2->c = 'y';                 //구조체 포인터 역참조하여 멤버 c에 접근해 b 값 넣기

   printf("(*d2).c : %c\n", (*d2).c);      //  b: 구조체 포인터를 역참조하여 c에 접근
                                   // d2->c과 같음

   printf("%d\n", *(*d2).numPtr); // 10: 구조체 포인터를 역참조하여 numPtr에 접근한 뒤 다시 역참조
                                   // *d2->numPtr과 같음

    free(d2);

    return 0;
}

 

이 단원에서 가장 중요한 것은

-> 는 포인터의 자물쇠를 *를 붙혀서 풀어, 정수인 경우 주소값을, 문자열인경우 값을 반환 한다는 것이다.

d2->numPtr = &num    이것은 (*d2).numPtr = &num와 같다.

 

포인터*는 나중에 참조가 되는 값으로 먼저 참조하려면 반드시 소괄호를 붙혀줘야 한다.

 

문자열과 정수가 다른 이유는 문자열은 값자체가 1byte로 이루어져 있지만 정수는 int형인 경우 4바이트에 21억4700만까지이기 때문에 반드시 주소값으로 처리를 해줘야한다.

 

자바나 파이썬은 이런 것을 고려할 필요가 없지만 c언어는 고려해야한다.

 

같은 원리로 정수형의 경우 =라는 shallow copy가 아닌 deep copy로 해야한다.

 

memcpy와 strcpy는 malloc()으로 메모리를 할당하고 =으로 얕은 복사를 하는 두가지 기능을 다 담고 있다.

 

 

 

 

 

 

 

 

 

 

1. 문자 포인터

c언어에서 문자포인터는 선언은 *을 붙히지만 포인터변수에 값을 저장 할때 &를 안붙혀도 된다.

문자배열 자체가 포인터 이기 때문이다. 포인터변수=문자배열대표값

 

아래 코드를 참조

#define _CRT_SECURE_NO_WARNINGS //비쥬얼 스튜디오에서 strcpy를 쓰려면 붙혀줘야함
#include <stdio.h>
#include <string.h>

void main(void)
{
	//문자 포인터 배열
	char ochar[] = "Korea";
	char ochar2[5+1] = "China"; //5글자를 입력하려면 글자 총 길이에 +1을 해줘야함
	char* pchar;

	pchar = ochar; //&를 안붙혀줘도 된다.


	puts(ochar);		// "Korea"
	puts(pchar);		// "Korea"

	strcpy(pchar, "Japan");


	puts(ochar);		// "Japan"
	puts(pchar);		// "Japan"

	memcpy(ochar, ochar2, sizeof(ochar2));
	// 
	//아래와 같이 써도 된다.
	//memcpy(ochar, ochar2, sizeof(char)*5); //china가 5바이트 이므로 길이에 5를 넣어줬다.
	//memcpy(ochar, ochar2, (strlen(ochar2))); //strlen으로 스트링의 길이를 넣어줬다.
	//strcpy(ochar, ochar2);  //단순 스트링 카피
	//strncpy(ochar, ochar2, strlen(ochar2));  //단순 스트링 카피

	printf("ochar 배열마지막 %p \n", ochar[5]); //마지막 배열의 주소는 0인 것을 볼수 있다.

	puts(ochar);		// "Japan"
	puts(ochar2);		// "Japan"
	puts(pchar);		// "Japan"
}

결과값

Korea
Korea
Japan
Japan
China
China

 

2. 숫자 포인터

정수 포인터는 포인터 변수에 값을 넣을 때 주소를 넣어줘야 하므로 &를 붙혀야 한다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void main(void)
{
	//정수 포인터 배열
	int oint=100;
	int* pint;

	pint = &oint; //포인터 변수에 주소 값 
	//문자배열과는 달리 포인터와 정수배열은 같지 않음

	printf("oint: %d \n", oint); //처음 넣었던 100이 나옴
	printf("pint: %d \n", *pint); // pint를 &oint로 연결 시켰으므로 100 나옴
	printf("pint: %p \n", pint); //pint의 주소값 출력


	//pint = 200;  //pint는 포인터 정수변수이므로 *를 붙혀줘야함
	*pint = 200; 
	//pint[0] = 200; //포인터변수는 배열이므로 이형태로 써도됨


	printf("oint 두번째: %d \n", oint);
	printf("pint 두번째: %d \n", *pint);
	printf("pint 두번째: %p \n", pint);
}

 

위에서는 숫자를 대입할 때 * pint를 썼는데 숫자형은 memcpy로 대입할 수도 있다.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void main(void)
{
	//정수 포인터 배열
	int oint = 100;
	int* pint;
	int oint2 = 300;

	pint = &oint; //포인터 변수에 주소 값 
	//문자배열과는 달리 포인터와 정수배열은 같지 않음. 따라서 &를 붙혀줘야함

	printf("oint: %d \n", oint); //처음 넣었던 100이 나옴
	printf("pint: %d \n", *pint); // pint를 &oint로 연결 시켰으므로 100 나옴
	printf("pint: %p \n", pint); //pint의 주소값 출력

	/* pint에 값을 대입할땐 아래와 같은 형식 사용 가능 */
	*pint = 200;
	//pint = 200; XX 이렇게 쓰면안됨  //pint는 포인터 정수변수이므로 *를 붙혀줘야함
	//*pint = oint2; 정수로 직접 써도 되고 값을 넣어도 됨
	
	//pint[0] = 200; //포인터변수는 배열이므로 이형태로 써도됨
	
	//memcpy로 써도 됨
	//memcpy(&oint, &oint2, sizeof(int)); //정수 변수는 값이 주소에서 꺼내온 것이므로 &를 붙혀줘야함 반면 문자 변수는 &를 안붙혀줘도됨

	/****************************************************/

	printf("oint 두번째: %d \n", oint);
	printf("pint 두번째: %d \n", *pint);
	printf("pint 두번째: %p \n", pint);
	printf("sizeof(int): %d \n", sizeof(int));
}

결과값

oint: 100
pint: 100
pint: 000000111C70F4E4
oint 두번째: 200
pint 두번째: 200
pint 두번째: 000000111C70F4E4
sizeof(int): 4

 

자바스크립트에서는 console.log()라는 명령어로 콘솔을 찍고

자바에서는 System.out.println()으로 찍는다.

 

하지만 C언어는 조금다르다.

 

콘솔을 찍으려는 문자 혹은 숫자의 형태를 앞에다 써줘야한다.

 

예를 들면 

 

틀린예

#include <stdio.h>

int main()
{
	int num1 = 10;
	int num2 = 20;
	int num3 = 30;

	printf(num1); //틀린예이다. 아무것도 출력되지 않는다.
	

	return 0;
}

여기서 printf에 num1을 넣어봐도 10이 출력되지 않는다.

 

맞는 예를보면

 

맞는예

#include <stdio.h>

int main()
{
	int num1 = 10;
	int num2 = 20;
	int num3 = 30;

	printf("%d %d %d \n", num1, num2, num3);


	return 0;
}

이렇게 %d처럼 변수의 형태를 써줘야 출력이 된다.

 

%d는 %i를 써도 된다. 또한 역슬래쉬n은 줄바꾸는 표시이므로 생략해도 된다.

 

 

redis 윈도우버전을 설치한다.

 

그뒤 cmd에서 --redis-server --port 7001 로 실행

 

그리고 아래와 같이 자바코드를 넣는다.

package com.test.redisTest;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

public class TestRedis {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		
		 String host = "127.0.0.1"; //로컬서버
	        int port = 7001;   //포트
	        int timeout = 3000;
	        int db = 0;
	        
	        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
	        
	        JedisPool pool = new JedisPool(jedisPoolConfig,host,port,timeout,null,db);
	        
	        Jedis jedis = pool.getResource();
	        
	        //Connect 체크 
	        System.out.println(jedis.isConnected());
	        
	        jedis.set("key1", "apple");
	        jedis.set("key2", "banana");
	        jedis.set("key3", "grape");
	        
	        // 데이터의 만료시간을 지정
	        jedis.expire("key5",1);
	        
	        System.out.println("key3: "+jedis.get("key3"));
	        
/*	        try {
	            Thread.sleep(3000);
	        } catch (InterruptedException e) {
	            // TODO Auto-generated catch block
	            e.printStackTrace();
	        }*/
	        System.out.println("key5: "+jedis.get("key5"));
	    
	        if( jedis != null ){
	        	System.out.println("클로즈");
	            jedis.close();
	        }
	        pool.close();
		
		
		
	        System.out.println("key6: "+jedis.get("key6"));
		
		
		
	}

}

 

그럼 결과값은

 

 

 

 

아파치의 httpcomponenet를 활용하여 http 통신 테스트를 해본다.

 

그러기 위해서는 pom.xml에 아래를 추가한다.

 

<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.4</version>
</dependency>

그뒤 패키지를 만들고 그 안에 void 자바파일을 하나 만든다.

 

패키지명: com.test.httpConnection01

 

파일명: HttpConnectionTest01.java

package com.test.httpConnection01;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;

public class HttpConnectionTest01 {
	
    private static final String USER_AGENT = "Mozila/5.0";
    private static final String GET_URL = "http://www.google.com";    
 
    public static void sendGet() throws ClientProtocolException, IOException {
        
        //http client 생성
        CloseableHttpClient httpClient = HttpClients.createDefault();
        
        
        //get 메서드와 URL 설정
        HttpGet httpGet = new HttpGet(GET_URL);

        //agent 정보 설정
        httpGet.addHeader("User-Agent", USER_AGENT);
        
      //get 요청
        CloseableHttpResponse httpResponse = httpClient.execute(httpGet);

        System.out.println("::GET Response Status::");
        
        //response의 status 코드 출력
        System.out.println("status code: "+httpResponse.getStatusLine().getStatusCode());
        
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                httpResponse.getEntity().getContent()));
 
        String inputLine;
        StringBuffer response = new StringBuffer();
 
        while ((inputLine = reader.readLine()) != null) {
            response.append(inputLine);
        }
        
        reader.close();
 
        //Print result
        System.out.println(response.toString());
        httpClient.close();
    }

}

 

그리고 위 메소드를 실행할 파일을 하나 만든다.

 

파일명: HttpConnectionTextExec.java

package com.test.httpConnection01;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;

public class HttpConnectionTextExec {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		try {
			//예제1 connection 테스트
			HttpConnectionTest01.sendGet();
			

			//System.out.println("==============비트코인 가격  json 시작=================");
			//예제2 비트코인가격 Json
			//HttpConnectionTest02.sendGet();
			
			
		
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
		
	}

}

 

코드를 완성 한 후 실행하면 아래와 같은 결과를 얻는다.

::GET Response Status::
status code: 200
<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage"

 

그런데 json을 받아오는 경우가 많으므로 json 예제파일을 하나 더 만들어보자.

 

파일명: HttpConnectionTest02

package com.test.httpConnection01;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpConnectionTest02 {

	
	
	private static final String USER_AGENT = "Mozila/5.0";

	//비트코인 가격 json API 주소
    private static final String GET_URL = "https://api.blockchain.com/v3/exchange/tickers/"+
    "BTC-USD";


    
    public static void sendGet() throws ClientProtocolException, IOException {
        
        //http client 생성
        CloseableHttpClient httpClient = HttpClients.createDefault();
        
        //get 메서드와 URL 설정
        HttpGet httpGet = new HttpGet(GET_URL);
        
        //agent 정보 설정
        httpGet.addHeader("User-Agent", USER_AGENT);
        httpGet.addHeader("Content-type", "application/json");
        
        //get 요청
        CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
        

       System.out.println("status code: "+httpResponse.getStatusLine().getStatusCode());
        String json = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
        
        
        
        System.out.println(json);
        
        httpClient.close();


    }
}

 

그리고 실행 파일에서 예제1에 주석을 걸고, 예제2 비트코인 가격 부분 주석을 푼다.

package com.test.httpConnection01;

import java.io.IOException;

import org.apache.http.client.ClientProtocolException;

public class HttpConnectionTextExec {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		try {
			//예제1 connection 테스트
			//HttpConnectionTest01.sendGet();
			

			System.out.println("==============비트코인 가격  json 시작=================");
			//예제2 비트코인 가격
			HttpConnectionTest02.sendGet();
			
			
		
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
		
	}

}

 

그러면 결과값은 아래와 같다.

==============비트코인 가격  json 시작=================
status code: 200
{"symbol":"BTC-USD","price_24h":35775.7,"volume_24h":63.66743294,"last_trade_price":35717.4}

 

더나아가서 비트코인 가격만 나타나려고 하면 json으로 그 값을 저장하는 변수를 추가해줘야한다.

 

pom.xml에 아래와 같이 GSON 메이븐을 추가해준다. (json-simple이 아닌 gson이다.)

<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.8.1</version>
</dependency>

 

그뒤 비트코인 가격 클래스에 json parsing 코드를 넣는다.

package com.test.httpConnection01;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public class HttpConnectionTest02 {

	
	
	private static final String USER_AGENT = "Mozila/5.0";
/*    private static final String GET_URL = "https://blockchain.info/ko"
            + "/rawblock/0000000000000bae09a7a393a8acd"
            + "ed75aa67e46cb81f7acaa5ad94f9eacd103";*/
    
	//비트코인 가격 json API 주소
    private static final String GET_URL = "https://api.blockchain.com/v3/exchange/tickers/"+
    "BTC-USD";


    
    public static void sendGet() throws ClientProtocolException, IOException {
        
        //http client 생성
        CloseableHttpClient httpClient = HttpClients.createDefault();
        
        //get 메서드와 URL 설정
        HttpGet httpGet = new HttpGet(GET_URL);
        
        //agent 정보 설정
        httpGet.addHeader("User-Agent", USER_AGENT);
        httpGet.addHeader("Content-type", "application/json");
        
        //get 요청
        CloseableHttpResponse httpResponse = httpClient.execute(httpGet);
        

       System.out.println("status code: "+httpResponse.getStatusLine().getStatusCode());
        String json = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
        String sdsd="dsd";
        
        
        System.out.println(json);
        
        //Json parsing 추가
        JsonParser jsonParser= new JsonParser(); //JsonParser를 불러온다.
        JsonObject jsonObj= (JsonObject) jsonParser.parse(json); //json을 오브젝트 형식으로 파싱함
        
        JsonElement last_trade_price = jsonObj.get("last_trade_price");
        
        
        
        
        System.out.println("비트코인 가격: "+last_trade_price);
        
        
        httpClient.close();


    }
}

 

그 뒤 실행 파일에서 실행해보면 결과는 아래와 같다.

==============비트코인 가격  json 시작=================
status code: 200
{"symbol":"BTC-USD","price_24h":35666.0,"volume_24h":62.14906458,"last_trade_price":36196.52}
비트코인 가격: 36196.52

 

 

애드밴티지 SKAdNetwork 가이드

V1.0 2021.05.24 Initial publishing SH Jung

 

1. SKAdNetwork Introduction

Apple에서 IOS14 운영체제 출시와 함께 기존 Retargeting 광고의 기반이 되었던, IDFA(Identifier For Apple)에 대하여 ATT(App Tracking Transparency Framework)를 준수하도록 강제하고 있습니다.

, 기존에는 제한없이 수집이 가능했던 IDFA, IOS14부터는 사용자의 허락을 구한 뒤 수집할 수 있게 되었습니다. (Opt-out -> Opt-in)

 

ATT에서 User IDFA의 수집을 거부하면 00000000-0000-0000-0000-000000000000” 값이 수집되어 Tracking이 어려워지게 됩니다.

 

이러한 변화는 광고 미디어 관점에서는 Performance Marketing에 어려움을 초래하며, 이에 대한 해결책으로 AppleSKAdNetwork framework를 권장하고 있습니다.

 

1.1. SKAdNetwork 참여 구성원

AppleSKAdNetwork를 크게 아래 3 Part로 정의하고 있으며, SKAdNetwork framework 참여를 위한 Responsibility를 명시하고 있습니다. (2.0 Responsibility for taking part in SKAdNetwork 참조)

 

- Ad network (애드밴티지)

- Source app (매체 지면)

- Advertised app (광고하려는 앱)

 

1.2.       Process

아래 그림은 Apple에서 제공하는 SKAdNetwork의 프로세스 입니다.

1.     소스앱 A서명된광고를 제공합니다.

2.     소스A는 목표 앱의 설치 또는 전환 추적을 용이하게하기 위해 필요한 광고키값을 포함하는 loadProduct() method를 호출합니다.

3.     사용자가 화면에 표시된 광고를 탭합니다.

4.     앱이 시작되고 광고된 앱이 기여 및 전환 추적에 필요한 정보를 제공하는 두가지 방법 중 하나를 호출합니다.

5.     Apple은 앱설치 도는 전환을 알리는 포스트백을 Ad network에 제공합니다.

 

 

 

2. Responsibility for taking part in SKAdNetwork

SKAdNetwork framework를 구성하는 참여자는 Ad network, Source app, Advertised app으로 나뉘며, Apple참여를 위해 해야하는 일을 명시하였습니다.

애드밴티지의 제휴 Publisher사는 “2.2 Source app (Publisher)” 부분을 참조해주시기 바랍니다.

 

2.1.       Ad network

2.1.1.   Apple Ad network 등록

Apple SKAdNetwork에서 Ad network ID를 요구합니다.

Ad network ID가 없다면 Apple 홈페이지(https://developer.apple.com/contact/request/ad-network-id/)에서 발급 요청을 할 수 있습니다.

(Apple 개발자 프로그램에 등록하여야 요청이 가능합니다. 법인 등록은 D-U-N-S Number가 필요합니다.)

 

 

발급 신청을 하면 등록에 성공했다는 메일이 오고 보통 일주일 이내 발급이 이루어집니다.

 

-       그 뒤 elliptic curve cryptographic key pair를 만들고 서명확인을 위해 공개키를 Apple과 공유합니다. 공개키/개인키 생성 방법은 아래에 나와 있습니다.

-       더불어 Apple SKAdnetwork 설치 검증 포스트백을 받기 위한 URL을 전달해야합니다.

 

2.1.2.   Private Key 생성

SKAdnetwork에 연동하고자 하는 애드네트워크는 개인키(Private Key)와 공개키(Public Key)를 생성하고, 공개키를 Apple에 보내야 합니다. 개인키를 생성하려면 다음 명령어를 터미널(cmd)에 입력합니다.

openssl ecparam -name prime256v1 -genkey -noout -out companyname_skadnetwork_private_key.pem

명령에서 companyname회사 이름으로 바꿉니다. 예를 들어 advantage라는 회사의 개인 키 파일 이름은 advantage_skadnetwork_private_key.pem입니다.

 

명령어 입력 및 수행 성공을 위해서는 오픈소스 OpenSSL을 이용하여 SSL 인증서를 발급합니다. OpenSSL 다운로드 방법은 OppenSSL 사이트의 github 게시글을 참고

(https://github.com/openssl/openssl)

 

개인 키는 공유하거나 클라이언트 측 코드에 포함시켜서는 안됩니다. 공개 키만 공유하세요.

 

2.1.3.   Public Key 생성

-       생성한 개인키를 이용하여 공개키를 생성합니다. 공개키를 생성하려면 다음 명령어를 터미널(cmd)에 입력합니다.

openssl ec -in companyname_skadnetwork_private_key.pem -pubout -out companyname_skadnetwork_public_key.pem

 

-       명령어에서 companyname_skadnetwork_private_key.pem 부분은 개인키 코드로 바꿉니다. 명령어에서 companyname 부분을 회사 이름으로 바꿉니다. 예로, advantage 회사의 개인 키 파일 이름은 다음과 같습니다. : advantage_skadnetwork_public_key.pem

-       SKAdnetwork 등록 시에는 공개키를 보냅니다. 참고로, Apple의 공개키는 아래와 같습니다.

MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEMyHD625uvsmGq4C43cQ9BnfN2xslVT5V1nOmAMP6qaRRUll3PB1JYmgSm+62sosG

 

2.1.4.   필수키 전달

(또는 광고 네트워크 SDK)에 의해 광고가 로드되면 loadProduct() 메소드를 호출하고 이 메소드와 함께 서명을 제공해야합니다.

따라서Ad networkPublisher에게 아래 필수키와 서명을 전달합니다.

 

1 version SKAdNetwork 버전
2 ad-network-id Apple로부터 부여 받은 Ad network 식별자
3 campaign-id Ad network 플랫폼 내의 캠페인을 나타내는 1-100 사이의 정수
4 itunes-item-id Advertised AppApp store ID
5 fidelity-type 2.2 이상 버전에 쓰이는 매개변수. view-through 광고에서는 1, 클릭 광고는 0을 입력
6 private key 소스앱에서 서명에 필요한 개인키 (확실하지 않으니 확인 바람)

 

 

 

 

2.1.5.   포스트백 검증

소스앱에서 인스톨, 이벤트가 발생하면 특정 시간이 흐른 뒤 Ad network Apple에 제공한 포스트백URL JSON data를 수신합니다.

{ 
    "version" : "2.0",
    "ad-network-id" : "advantage.skadnetwork",
    "campaign-id" : 42,
    "transaction-id" : "6aafb7a5-0170-41b5-bbe4-fe71dedf1e28",
    "app-id" : 525463029,
    "attribution-signature" : "MDYCGQCsQ4y8d4BlYU9b8Qb9BPWPi+ixk\/OiRysCGQDZZ8fpJnuqs9my8iSQVbJO\/oU1AXUROYU=",
    "redownload": 1,
    "source-app-id": 1234567891,
    "conversion-value": 20
}

 

각 값의 정의는 아래와 같습니다.

1 version SKAdNetwork 버전
2 ad-network-id Apple로부터 부여 받은 Ad network 식별자
3 campaign-id Ad network 플랫폼 내의 캠페인을 나타내는 1-100 사이의 정수
4 transaction-id 중복제거를 위한 고유 문자
5 app-id 광고된 앱의 App store ID
6 attribution-signature Apple의 서명
7 redownload 최초 다운로드는 1, 재다운로드는 0
8 app-id 소스앱의 App store ID
9 conversion-value 최대 64 개의 서로 다른 전환 액션을 나타낼 수 있는 6 비트 값

 

광고가 송출되는 앱(소스앱) iOS 11.3 ~ 14 이전 SDK를 사용하거나 해당 버전의 기기에서 실행되는 경우 SKAdnetwork 로부터 버전 1.0의 포스트백을 받습니다.

광고가 송출되는 앱(소스앱) iOS 14 이후 SDK를 사용하거나 해당 버전의 기기에서 실행되는 경우 SKAdnetwork 로부터 버전 2.0의 포스트백을 받습니다.

그리고 iOS 14.6 이상에서는 버전 3.0의 포스트백을 받습니다.

 

Ad networkApple로부터 포스트백을 받으면 HTTP 상태 코드 200 OK로 응답해야 합니다. 기기가 200상태 코드를 받지 못하면 최대 9일 동안 최대 9번의 포스트백 전송을 시도합니다.

 

포스트백을 받은 후 아래의 순서대로 UTF-8문자로 결합하고 그 사이에 구분기호 “\u2063”을 사용하여 Apple에서 보낸 것인지 확인 해야합니다.

아래의 정확한 순서대로 결합하여야 합니다. conversion-value는 포함하지 않으며, source-app-id는 포스트백에 있는 경우에만 포함합니다.

 

Version 1.0인 경우

ad-network-id + '\u2063' + campaign-id + '\u2063' + app-id + '\u2063' + transaction-id

 

Version 2.0 또는 2.1인 경우

version + '\u2063' + ad-network-id + '\u2063' + campaign-id + '\u2063' + app-id + '\u2063' + transaction-id + '\u2063' + redownload + '\u2063' + source-app-id

 

Version 2.2인 경우

version + '\u2063' + ad-network-id + '\u2063' + campaign-id + '\u2063' + app-id + '\u2063' + transaction-id + '\u2063' + redownload + '\u2063' + source-app-id + '\u2063' + fidelity-type

 

Version 3.0 이상인 경우

version + '\u2063' + ad-network-id + '\u2063' + campaign-id + '\u2063' + app-id + '\u2063' + transaction-id + '\u2063' + redownload + '\u2063' + source-app-id + '\u2063' + fidelity-type + '\u2063' + did-win

 

위 문자열을 Apple의 공개키를 사용하여 변환이 유효한지 확인합니다.

Apple의 공개키는 아래와 같습니다.

 

Version 1.0인 경우 P-192공개키

MEkwEwYHKoZIzj0CAQYIKoZIzj0DAQEDMgAEMyHD625uvsmGq4C43cQ9BnfN2xslVT5V1nOmAMP6qaRRUll3PB1JYmgSm+62sosG

 

Version 2.1 이상인 경우 NIST P-256공개키

MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWdp8GPcGqmhgzEFj9Z2nSpQVddayaPe4FMzqM9wib1+aHaaIzoHoLN9zW4K8y4SPykE3YVK3sVqW6Af0lfx3gg==

 

그리고 다음 단계를 수행합니다.

-       Base64로 인코딩된 Apple의 공개키를 디코딩합니다. 결과는 byte array입니다.

-       바이트 배열에서 X.509 표준 공개키를 만듭니다.

-       Base64 형식의 Apple signature를 디코딩합니다.

-       ECDSA (Elliptic Curve Digital Signature Algorithm), SHA256 hash를 사용하여 attribution-signature 값과 Apple의 공개키를 파라미터로 하여 서명을 검증합니다.

 

이해하기 쉽게 파이썬 코드로 설명하면 아래와 같습니다.

포스트백 데이터 저장

data = {
"version": "2.1",
"ad-network-id": "advantage.skadnetwork",
"campaign-id": "42",
"transaction-id": "6aafb7a5-0170-41b5-bbe4-fe71dedf1e28",
"app-id": "525463029",
"attribution-signature": "MEUCID6rbq3qt4GvFaAaynh5/LAcvn1d8CQTRhrZhLIxLKntAiEAo7IrvoMw6u2qDg6Tr5vIsEHXjlLkPlCOL0ojJcEh3Qw=",
"redownload": "true",
"source-app-id": "1234567891",
"conversion-value": "20",
}

 

파라미터 결합

post_back_parameter = "\u2063".join(
[
data["version"],
data["ad-network-id"],
data["campaign-id"],
data["app-id"],
data["transaction-id"],
data["redownload"],
data["source-app-id"],
]
).encode("utf-8")

 

▶ Apple 공개키 Base64 decode DER serialization

apple_pbkey = b64decode(
"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEWdp8GPcGqmhgzEFj9Z2nSpQVddayaPe4FMzqM9wib1+aHaaIzoHoLN9zW4K8y4SPykE3YVK3sVqW6Af0lfx3gg=="
)
pbkey = serialization.load_der_public_key(apple_pbkey)

 

▶ attribution-signature Base64 decode

sig = b64decode(data["attribution-signature"])

 

검증

pbkey.verify(
sig, post_back_parameter, ec.ECDSA(hashes.SHA256())
) //verify Boolean형식

 

 

 

2.2.  Source app (Publisher)

Source app에서는 가이드에 따라 작업을 진행하되, 자세한 개발 사항은 Apple의 가이드에 따라주시기 바랍니다.

(https://developer.apple.com/documentation/storekit/skadnetwork)

 

기존의 애드밴티지 click url(http://dr2.kr/~)과는 별도로 Apple로 데이터를 전송해주는 작업이 필요합니다.

 

-       Ad network(애드밴티지)가 전달한 Ad network idInfo.plist 파일에 추가 설정합니다.

-       Ad network가 송출한 광고를 노출시키고, loadProduct(withParameters:completionBlock:) 함수를 호출하여 Apple에 광고 노출 정보를 전송합니다.  또한 이 method와 함께 서명을 제공해야 합니다.

 

-       서명에는 아래 값들이 필요합니다. 2,3,4번은 ad network로부터 전달 받습니다.

 

1 version SKAdNetwork 버전
2 ad-network-id Apple로부터 부여 받은 Ad network 식별자
3 campaign-id Ad network 플랫폼 내의 캠페인을 나타내는 1-100 사이의 정수
4 itunes-item-id 광고되는 앱의 App store ID
5 nonce 각 광고 노출에 대해 광고 네트워크에서 생성 한 고유 UUID 값입니다. nonce는 해시가 고유한지 확인하기 위해 일반적으로 암호화에서 사용되는 값입니다.
6 source-app-id 소스앱의 App store ID
7 timestamp 최대 64 개의 서로 다른 전환 액션을 나타낼 수 있는 6 비트 값

 

위 값을 아래 예에 표시된 정확한 순서대로 보이지 않는 구분기호(‘\u2063’)로 결합하여 UTF-8문자열로 만들어야 합니다.

 

-     Version 1.0 일 시

ad-network-id + '\u2063' + campaign-id + '\u2063' + itunes-item-id + '\u2063' + nonce + '\u2063' + timestamp

 

-       Version 2.0 또는 2.1일 시

version + '\u2063' + ad-network-id + '\u2063' + campaign-id + '\u2063' + itunes-item-id + '\u2063' + nonce + '\u2063' + source-app-id + '\u2063' + timestamp

-       Version 2.2이상 일 시

version + '\u2063' + ad-network-id + '\u2063' + campaign-id + '\u2063' + itunes-item-id + '\u2063' + nonce + '\u2063' + source-app-id + '\u2063' + fidelity-type + '\u2063' + timestamp

 

-       UTF-8로 결합된 값을 아래 알고리즘을 사용하여 서명합니다.

▶ PKCS # 8 Private key

▶ SHA-256 해시를 사용하는 ECDSA (Elliptic Curve Digital Signature Algorithm)

 

 

-       서명한 뒤에는 결과 이진값을 Base64 문자열로 인코딩합니다. 그러면 아래와 비슷한 결과가 나타납니다.

MEQCIEQlmZRNfYzKBSE8QnhLTIHZZZWCFgZpRqRxHss65KoFAiAJgJKjdrWdkLUOCCjuEx2RmFS7daRzSVZRVZ8RyMyUXg==

 

이것은 loadProduct() 메소드를 호출할 때 다른 모든 필수 키 (아래의 "소스 앱"섹션 참조)와 함께 사용되는 키 값입니다.

 

, loadProduct() 메소드를 호출하는 데에는 위에 적은 7개의 키값과 서명의 결과값이 필요합니다.

 

이로써 사용자가 광고 된 앱을 설치하고 실행하면 Ad network는 등록 중에 제공 한 URLApple로부터 포스트백을 받습니다.

 

 

2.3.     Advertised app (Advertiser)

Advertised app은 광고 네트워크가 설치를 어트리뷰션하거나 선택적으로 SKAdNetwork를 사용하여 전환을 추적하기 위해 실행해야하는 책임이 있습니다.

설정 부분은 제휴 MMP에 따라 상이할 수 있습니다.

 

Advertised app은 기기에서 registerAppForAdNetworkAttribution () 또는 updateConversionValue () 메소드를 호출하여 설치 알림을 생성하고 광고 네트워크의 포스트 백 URL을 호출해야합니다.

 

▶ registerAppForAdNetworkAttribution () 메소드

registerAppForAdNetworkAttribution() 메소드는 사용자가 광고 된 앱을 시작할 때 호출되어야 합니다.

이 메소드를 호출하면 광고 네트워크에서 로드한 어트리뷰션 데이터와 광고 네트워크에서 제공하는 포스트 백 URL로 전송되는 소스 앱을 사용하여 설치 알림이 생성됩니다.

 

이 메서드가 호출 되자마자 24시간 타이머가 시작되고 초기 24 시간 타이머가 만료 된 후 0-24 시간 후에 포스트 백이 전송되므로 사용자가 광고 된 앱을 설치하고 실행 한 후 24-48 시간이 걸립니다.

 

▶ updateConversionValue (_:) 메소드

이 메소드는 포스트 백에서 전송된 conversion value를 업데이트하기 위해 광고 앱에서 호출됩니다. 6 비트 값이므로 64 개의 가능한 변환 값이 있습니다. 이러한 64개 값 각각은 하나 또는 여러 의미로 해석 될 수 있습니다.

 

예를 들어, 전환 가치는 계정에 가입 한 사용자에게 할당 될 수 있고, 다른 가치는 그들이 앱에서 항목을 구매했음을 나타내기 위해 사용될 수 있거나 다른 가치는 여러 행동의 조합을 나타낼 수 있습니다.

 

Apple은 포스트 백에서 단일 전환 가치 만 허용하므로 광고 네트워크가 단일 사용자의 여러 전환 액션을 추적하려면 단일 가치를 여러 전환 액션으로 해석해야 할 것입니다.

 

registerAppForAdNetworkAttribution () 메소드와 달리 Apple은 새 전환 값이 이전 값보다 크면 24 시간 동안 updateConversionValue () 메소드를 호출 할 수 있습니다. 더 큰 전환 가치로 메서드를 호출하면 24 시간 타이머가 다시 시작됩니다. 타이머가 만료 된 후 0 ~ 24 시간 후에 전환 가치와 함께 설치 알림이 광고 네트워크로 전송됩니다.

 

 

 

-       버전 1 ~ 2.2로 서명 된 광고의 경우 기기는 단일 winning postback을 보냅니다.

-       버전 3.0 이상으로 서명 된 광고의 경우 iOS 14.6부터 기기는 winning postback하나와 non-winning postback을 최대 5 개까지 보냅니다.

non-winning postback은 어트리뷰션을 획득하지 못했다는 뜻입니다.

 

 

위 사항에서 잘못된 부분 또는 문의사항이 있으면 댓글로 남겨주시기 바랍니다.

 

감사합니다.

 

- Enliple Advantage -

 

'개발 용어 정리' 카테고리의 다른 글

2. 광고 플랫폼 용어 정리  (0) 2021.01.05
01. 웹 크롤링 / HTTP  (0) 2019.04.19

1. ROAS (Return of Ad Spend)

광고대비 매출액

광고에 지출하는 1달러당 얼마나 많은 수익을 얻는지 보여주는 비율

 

수익/캠페인비용 = ROAS

예를들어 $5000/$1000 = $5

 

보통 400% 이상이면 좋은 ROAS라고 봄.

ROAS를 개선하기 위해서는 구매가능성이 높은 고객들에게 광고를 타겟팅하는 것이 가장 중요함.

 

2. CPA (Cost Per Action)

광고주가 원하는 어떠한 행동을 취했을 때 비용이 발생함.

예를 들어 회원가입을 하거나 상담신청을 누르거나 어플을 설치하는 등의 횟수가 Action에 해당됨.

CPC(Cost Per Click) 광고가 진화된 상태

 

3. CTR (Click Through Rate)

노출수 대비 클릭수 비율

광고가 클릭한 횟수를 광고가 게재된 횟수로 나눈 값

 

4. CVR (Click Conversion Rate)

유입 후 전환이 이루어진 비율

전환은 상품구매, 문의, 회원가입 등이 행동을 말함.

 

5. Retention (잔존율)

어플을 설치한 뒤 남아 있는 비율

NCPI가 CPI보다 잔존율이 높다.

 

6. NCPI (Non-incentive Cost Per Install)

설치시 보상을 제공하지 않음.

어플의 순위 상승이나 즉각적인 회원의 유입이 필요할 때는 CPI가 낫지만

조금 장기적인 관점에서 보면 NCPI가 낫다.

 

'개발 용어 정리' 카테고리의 다른 글

iOS14로 인한 SKAdnetwork 가이드 매뉴얼  (0) 2021.05.20
01. 웹 크롤링 / HTTP  (0) 2019.04.19

docker는 하이브리드 클라우드의 모든 애플리케이션을 처리할 수 있는 유일한 컨테이너 플랫폼 제공 프로그램이다.

 

즉, 컨테이너 기반의 오픈소스 가상화 플랫폼이다.

 

docker에서 말하는 컨테이너란 택배나 화물선 위에 수출용품을 싣는 컨테이너를 대신하여 프로그램(소프트웨어)을 담는 격리된 공간을 의미한다.

각각의 격리된 여러개의 소프트웨어 컨테이너에는 ubuntu, centos 등의 운영 체제 그리고 java, python, mysql, 아파치 같은 프로그램까지도 담기게된다.

 

그리고 각 컨테이너는 격리된 공간이기 때문에 한 컨테이너가 해킹을 당하던 한 컨테이너에 문제가 생기더라도 컨테이너간에 영향을 끼치지 않는다.

 

 

1) 우선 작업 관리자에서 가상화를 사용함으로 설정했는지 확인 한다. (안되어 있는 경우 BIOS 에서 사용함으로 설정해야 한다.)

 

2) 1. 도커를 사용하기 위해서 가상화 기술인 Hyper-V(하이퍼 바이저)를 활성화시켜야한다.

 

 

3) 다음 사이트 접속 > Get Docker 클릭 하여 다운로드
 - https://hub.docker.com/editions/community/docker-ce-desktop-windows/

 

 

4) ok를 계속 누르면 아래처럼 설치가 진행된다.

재시작 후 구동시킨다. 아이디가 없으면 회원가입을 해야한다.

 

5) 설치가 정상적으로 되었으면 파워쉘에서 docker -v를 치면 아래처럼 나온다.

 

 

윈도우 환경에서 자바를 설치하는 방법을 체계적으로 알아본다.

 

혹시 컴퓨터에 자바가 설치되었는지 아닌지 확실하지 않다면 윈도우에서 cmd라고 쳐보자.

 

cmd 창에서 java -version이라고 치면 자바 버전이 나온다.


* 자바 설치 확인

위 메시지는 자바 JDK 1.8버전이 설치되어있다는 뜻입니다.

 

JAVA 8버전이 JDK1.8이고 JAVA 11버전이 JDK 11입니다.

 

JDK의 버전은 오래된 순으로 1.6, 1.7, 1.8, 9, 11, 12, 13, 14, 15 이런식으로 이어집니다.

 

* 자바C 설치 확인 (자바 컴파일러)

 

* 자바설치

아래 링크로 들어가던가, 아니면 구글에 jdk download를 검색한다.

www.oracle.com/kr/java/technologies/javase-downloads.html

 

위는 jdk SE 15버전인데 1.6~1.8버전도 거의 상관이 없다.

window64 installer를 클릭하여 설치해준다. 별도 로그인은 필요없다.

 

잘 설치가 되었다면 프로그램 폴더에 자바가 있을 것이다.

 

 

 

 

 

 

 

 

+ Recent posts