728x90

AES 구조



AES(Advanced Ecryption Standard)는 1997년 DES를 대체할 목적으로 NIST 암호 표준 공모를 하여 2001년에 채택된 암호 표준이다.


feistel 구조인 DES와 달리 SPN구조로 되어있어서 복호화시 역연산의 알고리즘을 따로 구현해야 하지만 병렬 처리를 하기 용이하다..


확장성을 고려하여 설계되어서 키 길이는 128/192/256 bit를 가지고 키 길이에 상관없이 블록 길이는 128 bit이며 라운드 수는 10/12/14를 가진다.


대략 적인 순서는


1. 라운드키와 xor한다. (AddRoundKey)


2. 바이트를 치환한다. (SubBytes)


3. 행별로 바이트를 옮긴다. (ShiftRows)


4. 열 별로 바이트를 섞는다. (MixColumns)


5. 라운키와 xor한다. (AddRoundKey)


6. (라운드 수 - 1) 만큼 2~5를 반복한다.


7. 마지막 라운드는 MixColumns를 제외하고 수행한다.


복호화는 각 단계의 역연산을 거꾸로 수행하면 된다.


이제 각 단계를 살펴보자.



aes는 입력 블럭을 위 그림과 같은 행렬로 구성하여 처린한다. 최 상위 한바이트씩 총 16바이트 가 들어가는데 주의할 점은


S00 , S01, S02, ......., S33 순이 아닌 S00, S10, S20, ......, S33순으로 즉 열 우선으로 넣어야 한다.



void AddRoundKey(BYTE state[][4], WORD* rKey) {
	int i, j;
	WORD mask, shift;

	for (i = 0; i < 4; i++) {
		shift = 24;
		mask = 0xff000000;

		for (j = 0; j < 4; j++) {
			state[j][i] = ((rKey[i] & mask) >> shift) ^ state[j][i];
			mask >>= 8;
			shift -= 8;
		}
	}
}


단순하게 state행렬의 각 바이트와 라운드 키 각 바이트와 xor한다.



Subbytes



SubBytes는 위 행렬식을 곱하여 바이트를 치환한다. X0 ~ X7순으로 바이트의 최상위 비트를 위치하여 계산한다.


메모리가 부족한 시스템이라면 직접 계산을 하여 구하지만 보통은 미리 모든 바이트가 계산된 치환표를 사용한다.


SubBytes 치환표



가령 0x37을 치환하면 0xb2로 바뀐다. 복호화시에는 동일하게 역 치환표도 있다.


치환표를 사용하지 않고 계산할 때에는 해당 바이트에 [1 0 1 0 0 0 0 0]을 빼고 위 8x8행렬의 역행렬을 곱해주면 된다.



void SubBytes(BYTE state[][4]) {
	int i, j;

	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++)
			state[i][j] = S_box[HIHEX(state[i][j])][LOWHEX(state[i][j])];
}

* HIHEX(), LOWHEX()는 각각 상위4비트 하위4비트를 반환하는 함수.


각각의 바이트에 대하여 구해준다.




ShiftRows



첫행은 움직이지 않고 둘째행은 1번, 셋째행은 2번, 넷째행은 3번 바이트단위로 왼쪽순환쉬프트를 한다.


void ShiftRow(BYTE state[][4]) {
	int i, j;

	for (i = 1; i < 4; i++)
		for (j = 0; j < i; j++)
			CirshiftRows(state[i]);
}

void CirshiftRows(BYTE state[]) {
	BYTE temp = state[0];

	state[0] = state[1];
	state[1] = state[2];
	state[2] = state[3];
	state[3] = temp;
}


코드도 간단하다.



MixColumns 행렬식



MixColumns는 배열의 각 열에 대하여 위 그림과 같은 연산을 수행하는데 기약다항식 x^8 + x^4 + x^3 + x + 1 = 0 상에서 하게 되며 따라서


캐리 발생시 0001 1011을 xor한다.


void MixColumns(BYTE state[][4]) {
	int i, j, k;
	BYTE a[4][4] = { 0x2, 0x3 , 0x1, 0x1,
		0x1, 0x2, 0x3, 0x1,
		0x1, 0x1, 0x2, 0x3,
		0x3, 0x1, 0x1, 0x2 };

	for (i = 0; i < 4; i++) {
		BYTE temp[4] = { 0, };

		for (j = 0; j < 4; j++)
			for (k = 0; k < 4; k++)
				temp[j] ^= x_time(state[k][i], a[j][k]);

		state[0][i] = temp[0];
		state[1][i] = temp[1];
		state[2][i] = temp[2];
		state[3][i] = temp[3];
	}
}

BYTE x_time(BYTE b, BYTE n) {
	int i;
	BYTE temp = 0, mask = 0x01;

	for (i = 0; i < 8; i++) {
		if (n & mask)
			temp ^= b;

		if (b & 0x80)
			b = (b << 1) ^ 0x1B;
		else
			b <<= 1;

		mask <<= 1;
	}

	return temp;
}


x_time() 함수는 곱하려는 행렬 값에 따라 기약다항식 상에서 값을 배수해주는 함수이다.


역 MixColumns함수의 경우 4x4행렬의 역행렬을 사용한다.



키 확장




라운드 키는 128비트 기준 총 11개(첫 Addroundkey포함)이고 각 키는 4개의 의 워드로 구성되기때문에 총 44개의 워드가 필요하다.


키 확장의 순서는 이렇다.


1. 키를 4바이트씩 나누어 4개의 워드로 만든다.


2. 이전 번의 워드와 4번째전 워드를 xor하여 새 워드를 생성한다. ( 4의 배수 번째의 경우 이전 워드를 g함수에 통과시킨다.)

    

    2-1. 입력의 워드를 1바이트만큼 왼쪽 순환시프트를 한다.

  

    2-2. Subbytes에 썼던 치환을 여기서도 사용한다.


    2-3. 상수값 R과 xor하여 결과를 반환한다.


3. 워드가 44개 생성될때까지 2를 반복한다.


R상수



void KeyExpansion(BYTE key[], WORD w[]) {
	int i = 0;
	WORD temp;

	while (i < Nk) {
		w[i] = BTOW(key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]);
		i = i + 1;
	}

	i = Nk;

	while (i < (Nb * (Nr + 1))) {
		temp = w[i - 1];
		if (i%Nk == 0)
			temp = SubWord(RotWord(temp)) ^ Rcon[i / Nk - 1];
		else if ((Nk > 6) && (i%Nk == 4))
			temp = SubWord(temp);

		w[i] = w[i - Nk] ^ temp;
		i += 1;
	}
}

* Nk : 키 바이트수(4), Nb : 블럭 수(4), Nr : 라운드수(10)



이상 AES의 알고리즘에 대해 알아보았다...

728x90
728x90

param=$1 echo "숫자 입력 : " read input ret=`expr $input + $param` echo "결과 : "$ret exit 0


쉘 스크립트는 띄어쓰기하나 잘못 해도 에러를 뿜으니 주의해야 한다.


변수의 값을 사용할때에는 $(변수이름)으로 사용해야 한다.


첫째줄에 $1은 프로그램 파라미터의 첫번째이며 $2, $3, ... 식으로 사용할 수 있다.


expr로 묶어주지 않으면 문자열로 처리하기 때문에 연산이 불가하다.


*, /을 사용할 때에는 \*, \/로 사용해야 한다. (키워드 이기 때문)



결과 :



사진과 같이 쉘에 넘겨 실행시켜도 되고 chmod로 실행권한을 줘 실행시켜도 된다.

728x90

'Linux' 카테고리의 다른 글

리눅스 시작 시 자동으로 실행 될 프로그램 등록하기  (0) 2016.03.28
728x90

DES 메카니즘



현대암호(하이브리드 암호)의 조상님인 DES( Data Encryption Standard )이다.


DES는 feistel 구조로 되어있으며 라운드수는 16이고 64비트 입력(블럭)에 64비트키(사실상 56비트 키)를 가진다.


대략적인 순서는


1. 64비트 입력을 초기 전치한다.


2. 32비트씩 쪼갠다.


3. 오른쪽32비트는 다음 라운드의 왼쪽32비트가 된다.


4. 오른쪽32비트를 라운드 키와함께 F함수에 통과한 후 왼쪽32비트와 xor하여 다음 라운드의 오른쪽32비트가 된다.


5. 3, 4를 16번 반복한다. 단, 마지막 16라운드에는 왼쪽 오른쪽을 교차하지 않는다.


6. 최종 전치( 초기 전치의 역전치 )를 하면 끝.


feistel구조는 원래 des이전부터 있던 구조이며 따라서 feistel구조의 블럭암호들은 보통 F함수를 설계한다.


DES F함수



DES의 F함수의 대략적 순서는


1. 32비트 입력을 48비트로 확장전치한다.


2. 48비트 라운드키와 xor한다.


3. 6비트씩 S박스에 통과시킨다. 이때 박스당 6비트의 입력은 4비트의 출력을 내며 S박스 통과 후 32비트가 된다.


4. 한번 더 전치 끝.


키는 입력 64비트키에서 확장시켜서 라운드키를 생성하는데


대략적 순서는


1. 전치한다. 이때 8의 배수 번째(8, 16, ... , 64)가 없어지며 56비트가 된다.

(이때문에 DES의 키공간은 2^56이다.)


2. 28비트씩 쪼갠다.


3. 라운드별 쉬프트 횟수만큼 왼쪽순환쉬프트를 왼쪽28비트 오른쪽28비트 각각 해준다.


4. 합쳐서 전치하여 라운드키 생성. 이때 48비트가 된다.


5. 3, 4번을 16라운드 반복한다.



코드를 보자


void DES_Encryption(BYTE *p_text, BYTE *result, BYTE *key) {
	int i;
	BYTE data[BLOCK_SIZE] = { 0, };

	BYTE round_key[16][6] = { 0, };
	UINT left = 0, right = 0;

	key_expansion(key, round_key);    // 키 확장
	IP(p_text, data);                        // 초기 전치

	BtoW(data, &left, &right);           // 32비트씩 쪼갬

	for (i = 0;i < DES_ROUND; i++) { 
		left = left ^ f(right, round_key[i]);
		if (i != DES_ROUND - 1)
			swap(&left, &right);
	}

	WtoB(left, right, data);
	In_IP(data, result);                    // 마지막 전치
}


암호화 코드이다. 


복호화 코드는 라운드 키를 역순으로 넣어주는 것 빼고 다른건 없다.( Feistel 구조의 특성 )




void IP(BYTE *in, BYTE *out)
{
	int i;
	BYTE index, bit, mask = 0x80;

	for (i = 0;i<64;i++)
	{
		index = (ip[i] - 1) / 8;
		bit = (ip[i] - 1) % 8;

		if (in[index] & (mask >> bit))
			out[i / 8] |= mask >> (i % 8);
	}
}


초기 전치 코드이다.


마스크값과 &연산을 하여 해당 비트가 1인지 확인하고 1이면 out의 비트 1로 한다.


0으로 초기화되어 있기때문에 0일때는 처리하지 않는다.




UINT f(UINT r, BYTE* rkey)
{
	int i;
	BYTE data[6] = { 0, };
	UINT out;

	EP(r, data);

	for (i = 0;i<6;i++)                                 // 라운드 키와 xor
		data[i] = data[i] ^ rkey[i];

	out = Permutation(S_box_Transfer(data));   // S박스 통과하여 전치함

	return out;
}


F함수 코드이다.



void EP(UINT r, BYTE* out)
{
	int i;
	UINT mask = 0x80000000;

	for (i = 0;i<48;i++)
	{
		if (r & (mask >> (E[i] - 1)))
		{
			out[i / 8] |= (BYTE)(0x80 >> (i % 8));
		}
	}
}


F함수 내 확장 전치 코드이다.


IP코드와 유사하다.


이 외의 전치코드들도 거의 흡사하므로 담지 않았다.



UINT S_box_Transfer(BYTE* in)
{
	int i, row, column, shift = 28;
	UINT temp = 0, result = 0, mask = 0x80;

	for (i = 0;i<48;i++)
	{
		if (in[i / 8] & (BYTE)(mask >> (i % 8)))  // 마스크를 씌워 확인 후 temp에 해당 비트 1로 함
                        temp |= 0x20 >> (i % 6);

		if ((i + 1) % 6 == 0)                        // 6비트마다
		{
			row = ((temp & 0x20) >> 4) + (temp & 0x01);           // 행 값
			column = (temp & 0x1E) >> 1;                               // 열 값

			result += ((UINT)s_box[i / 6][row][column] << shift);    // 값 더하고 쉬프트(4비트씩)

			shift -= 4;
			temp = 0;
		}
	}

	return result;
}


S-box의 코드이다.


C언어에서 최소 데이터형이 8비트이기 때문에 temp에 6비트씩 담아서 처리한다.


비트필드로도 처리해도 되겠지만 귀찮아서 패스.


6비트중 상위 1번째비트와 6번째 비트는 S-box의 행값( 따라서 총 4행 )을 나타내고


중간 4비트는 열값을 나타내며 해당 행, 열에 위치한 값으로 치환된다.


S1 box




void key_expansion(BYTE *key, BYTE round_key[16][6])
{
	int i;
	BYTE pc1_result[7] = { 0, };
	UINT c = 0, d = 0;

	PC1(key, pc1_result);                // 축소 전치(64 -> 56)

	makeBit28(&c, &d, pc1_result);  // 28비트씩 쪼갬

	for (i = 0;i<16;i++)
	{
		c = cir_shift(c, i);             // 순환왼쪽쉬프트
		d = cir_shift(d, i);

		PC2(c, d, round_key[i]);     // 축소 전치(56 -> 48)
	}
}


키 확장 코드이다.



UINT cir_shift(UINT n, int r)
{
	int n_shift[16] = { 1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1 };

	n = (n << n_shift[r]) + (n >> (28 - n_shift[r]));

	return n & 0xFFFFFFF;      // 쓰레기값 제거
}


왼쪽 순환 쉬프트 코드이다.


n_shift 배열에 각 라운드별 순환 횟수가 있다.


그 횟수 만큼 왼쪽 쉬프트를 해주고 순환쉬프트이므로 28비트에서 밀려난 상위비트를 밀려난 만큼 최하위로 밀어준다.


UINT는 32비트이므로 n을 왼쪽으로 밀때 32비트 상위 4비트에 쓰레기값이 생긴다. 따라서 하위 28비트만 취할 수 있게


0xFFFFFFF의 마스크를 씌워 반환한다.



이상 DES의 구조와 핵심 코드들을 살펴 보았다.

728x90
728x90




기본 줄거리 :


우주비행사 양성소 리베 델타 내부 리바이어스라는 전투함이 숨겨져 있다.


그 전투함을 얻기 위해 궤도보안청 요원이 잠입하여 리베 델타를 게둘트 바다로 빠뜨렸고 리베 델타와 함께 게둘트내에서 압괴될 운명이었던 학생들.


그 중 츠바이(고급 연습생)들이 리바이어스를 발견하게 되고 압괴되기 직전 학생전원은 리바이어스로 피신을 하게 되는데...



평가(극히 주관적):


스토리 ★★★★☆ / 중간중간 짜증나는 부분도 있었지만 대체로 만족하며 본 듯 하다..


작화 ★★☆☆☆ / 보다시피 옛날 작품이기도 하고 그 당시로는 괜찮은 수준이라 생각하여 2개를 주었다.


개그 ☆☆☆☆☆ / 그런 거 없다.


음악 ★★☆☆☆ / 딱히 좋지도 나쁘지도 않지만 중간에 나오는 힙합풍의 bgm이 별로다.



상업성 애니메이션이 터져나오는 이때에 이런 고전 수작들을 보는 것도 나쁘지 않다. 오히려 재미가 있다.


내용과 분위기가 전반적으로 우울하고 어두운 중에 진행이 되는 작품이지만 실낱 같은 희망을 품고 주인공들이


리아비어스의 전원이 앞으로 나아가는 것을 보는 재미가 있다.

728x90
728x90

이 코드는 이 코드를 위해 작성하였습니다.



java에서 smartcardio라이브러리를 이용하면 nfc제어를 쉽게 구현할 수 있다.


nfc리더기가 연결되어 있으면 터미널 초기화가 가능하다. 현재 사용중인 acr 1251 리더기는 PICC, SAM 두개의 터미널이 있는데


기본적으로 PICC를 사용한다. (SAM은 태그에 보호기술이 들어간 것에 사용)


IsCardPresent() 메소드는 리더기 위에 카드가 있는지 검사하며 있을 경우 터미널에 대한 채널을 GetCardAndOpenChannel() 메소드로 오픈한다.


SendCommand() 메소드를 통해 올려진 카드(혹은 핸드폰)에 데이터를 보낸다.


위 안드로이드 어플리케이션과 통신하기 위해 Select AID과정을 거치는데 그에 해당하는 바이트열이 selectCardAID()메소드에 있는 것이다.


NFCMain.java

package nfc_simple;

import javax.smartcardio.*;

import java.nio.ByteBuffer;
import java.util.*;

public class NFCMain {
    private static final String UNKNOWN_CMD_SW = "0000";
    private static final String SELECT_OK_SW = "9000";
    
    public void run() {
        CardTerminal terminal;
        CardChannel channel;
        
        while (true) {
            // 터미널 초기화
            try {
                terminal = InitializeTerminal();
                
                if(IsCardPresent(terminal)) {                                   // 리더기 위에 카드(핸드폰)가 있을 경우
                    channel = GetCardAndOpenChannel(terminal);
                    
                    String response = selectCardAID(channel);
                    
                    System.out.println(response);
                }
                
                Thread.sleep(2000);
                
            } catch (CardException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public CardTerminal InitializeTerminal() throws CardException { 
        // Get terminal 
        System.out.println("Searching for terminals..."); 
        CardTerminal terminal = null; 
        TerminalFactory factory = TerminalFactory.getDefault(); 
        List<CardTerminal> terminals = factory.terminals().list(); 
      
        //Print list of terminals 
        for(CardTerminal ter:terminals) { 
            System.out.println("Found: "  +ter.getName().toString()); 
            terminal = terminals.get(0);// We assume just one is connected 
        } 
     
        return terminal; 
    }
    
    public boolean IsCardPresent(CardTerminal terminal) throws CardException {
        System.out.println("Waiting for card...");
        
        boolean isCard = false;
        
        while (!isCard) {
            isCard = terminal.waitForCardPresent(0);
            if(isCard)
                System.out.println("Card was found! :-)");
        }
        
        return true;
    }
    
    public CardChannel GetCardAndOpenChannel(CardTerminal terminal) throws CardException {
        Card card = terminal.connect("*");
        CardChannel channel = card.getBasicChannel();
        
        byte[] baReadUID = new byte[5];
        baReadUID = new byte[]{(byte) 0xFF, (byte) 0xCA, (byte) 0x00, (byte) 0x00, (byte) 0x00};
        
        // tag의 uid (unique ID)를 얻은 후 출력
        System.out.println("UID : " + SendCommand(baReadUID, channel));
        
        return channel;
    }
    
    public String selectCardAID(CardChannel channel) {
        
        byte[] baSelectCardAID = new byte[11];
        baSelectCardAID = new byte[]{(byte) 0x00, (byte) 0xA4, (byte) 0x04, (byte) 0x00, (byte)0x05,(byte) 0xF2, (byte) 0x22, (byte) 0x22, (byte) 0x22, (byte) 0x22};
        
        return SendCommand(baSelectCardAID, channel);
    }
    
    public static String SendCommand(byte[] cmd, CardChannel channel) { 
        String response = "";
        byte[] baResp = new byte[258];
         
        ByteBuffer bufCmd = ByteBuffer.wrap(cmd);
        ByteBuffer bufResp = ByteBuffer.wrap(baResp);
         
        int output = 0;
         
        try { 
            output = channel.transmit(bufCmd, bufResp); 
        } catch(CardException ex){ 
            ex.printStackTrace(); 
        } 

        for (int i = 0; i < output; i++) {
            response += String.format("%02X", baResp[i]); 
        }
          
        return response;  
    }
}



728x90
728x90

HCE (host-based card emulation)을 이용하면 nfc 태그가 없더라도 nfc통신이 가능하다.



리더기는 acr 1251제품을 사용하였으며 리더기 제어에 관한 코드는 여기에 있다.


hce를 사용하기 위해서는 기본적으로 HostApduService를 상속받아야 하는데 이름에서 알 수 있듯이 Service이기 때문에

매니페스트 파일에 추가해 주어야 한다.


AndroidMainifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hyunseo.hce_sample">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".CardService"
            android:exported="true"
            android:permission="android.permission.BIND_NFC_SERVICE">
            <!-- Intent filter indicating that we support card emulation. -->
            <intent-filter>
                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
            <!-- Required XML configuration file, listing the AIDs that we are emulating cards
                 for. This defines what protocols our card emulation service supports. -->
            <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
                android:resource="@xml/aid_list"/>
        </service>
    </application>

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

</manifest>


AID란 것이 존재하는데 여러 어플리케이션에서 어떤 어플리케이션과 nfc통신을 해야 하는지를 구분하기 위한 값으로 임의로 설정 가능하다.

매니페스트 파일에 aid값이 있는 xml파일을 명시해 주어야 한다. 또 android.permission.BIND_NFC_SERVICE권한을 꼭 추가해 주어야 한다.


MainActivity.java

package com.example.hyunseo.hce_sample; import android.content.Intent; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import org.w3c.dom.Text; public class MainActivity extends AppCompatActivity { private TextView _tv; private Intent _cardService; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); _tv = (TextView) findViewById(R.id._textView); _cardService = new Intent(this, CardService.class); startService(_cardService); } @Override protected void onDestroy() { stopService(_cardService); } public void onClickUpdate(View v) { _tv.setText("Count : " + Counter.GetCurrentCout()); } }


메인 액티비티에서 하는 일은 간단한데 사용자가 버튼을 누르면 Count클래스에 있는 cnt값을 화면에 표시한다.



CardService.java

package com.example.hyunseo.hce_sample; import android.content.Intent; import android.nfc.cardemulation.HostApduService; import android.os.Bundle; import android.os.Message; import android.os.Messenger; import android.os.RemoteException; import android.util.Log; import java.util.Arrays; public class CardService extends HostApduService { private static final String TAG = "CardService"; // AID for our loyalty card service. private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222"; // ISO-DEP command HEADER for selecting an AID. // Format: [Class | Instruction | Parameter 1 | Parameter 2] private static final String SELECT_APDU_HEADER = "00A40400"; // "OK" status word sent in response to SELECT AID command (0x9000) private static final byte[] SELECT_OK_SW = HexStringToByteArray("9000"); // "UNKNOWN" status word sent in response to invalid APDU command (0x0000) private static final byte[] UNKNOWN_CMD_SW = HexStringToByteArray("0000"); private static final byte[] SELECT_APDU = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID); //////////////////////////////////////////////////////////////////////////////////// private Messenger _handler; @Override public int onStartCommand(Intent intent, int flags, int startId) { // UI thread 핸들 얻음 Bundle extras = intent.getExtras(); // 서비스를 앱 종료시까지 계속 실행상태로 return START_STICKY; } // 외부로부터의 APDU명령을 받았을때 호출 @Override public byte[] processCommandApdu(byte[] commandApdu, Bundle extras) { if (Arrays.equals(SELECT_APDU, commandApdu)) { Log.i(TAG, "Application selected"); Counter.AddOne(); return SELECT_OK_SW; } else { return UNKNOWN_CMD_SW; } } // 연결을 잃었을때 호출됨 @Override public void onDeactivated(int reason) { Log.i(TAG, "Deactivated: " + reason); } public static byte[] BuildSelectApdu(String aid) { // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA] return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid); } public static byte[] HexStringToByteArray(String s) throws IllegalArgumentException { int len = s.length(); if (len % 2 == 1) { throw new IllegalArgumentException("Hex string must have even number of characters"); } byte[] data = new byte[len / 2]; // Allocate 1 byte per 2 hex characters for (int i = 0; i < len; i += 2) { // Convert each character into a integer (base-16), then bit-shift into place data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } }


이 클래스가 nfc통신을 담당할 클래스이다. 외부로부터 이 어플리케이션에 apdu 명령을 할 경우 processCommandApdu() 메소드가 호출되며


카운터 클래스의 Counter.AddOne() 메소드를 호출한다.


기본적으로 Select AID란 작업이 완료되면 리더기에서 핸드폰을 떼기 전까지는 이 어플리케이션에만 apdu명령이 들어온다.


현재 aid는 f2222222로 설정하였고 Select AID의 apdu헤더는 00A40400이며 aid의 값은 5바이트(바이트로 변환하기 때문에 16진수로 끊어서)이기 때문에


여기서의 Select AID의 apdu명령은 "00A40400 05 f2222222"의 바이트열로 구성된다. 외부 카드리더기로부터 저 명령이 왔을때부터 핸드폰을 리더기에서


뗄때까지 이 어플리케이션과 통신이 이루어진다. 위 코드에는 select aid외 작업을 하지 않지만 원하면 다른 바이트를 주고 받을 수 있다.


select aid가 완료된 이후에는 따로 헤더가 필요치 않는다. processCommandApdu()의 리턴값은 리더기에 보낼 바이트열 값이다.



Counter.java

package com.example.hyunseo.hce_sample; public class Counter { private static int _cnt = 0; public static void AddOne() {_cnt++;} public static int GetCurrentCout() {return _cnt;} }


카운터 클래스는 간단하게 UI thread와 Service 두곳에서 접근 가능하도록 static으로 구성하였다. 카드서비스에서 _cnt의 값을 올리면


메인액티비티에서 증가된 값을 표시한다.



aid_list.xml

<?xml version="1.0" encoding="utf-8"?>
<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:description="@string/service_name"
    android:requireDeviceUnlock="false">

    <aid-group android:description="@string/card_title" android:category="other">
        <aid-filter android:name="F222222222"/>
    </aid-group>
</host-apdu-service>


aid가 적혀있다. 외부로부터 이 값을 이용하여 들어온다.



실행결과 :

리더기에 찍고 UPDATE버튼을 누르면 카운트값이 변경이 된다.


자세한 설명은 구글 개발자 페이지를 확인하시면 됩니다.

https://developer.android.com/samples/CardEmulation/index.html

728x90
728x90

 원문 : http://www.rastertek.com/dx11tut17.html

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ


Tutorial 17: Multitexturing and Texture Arrays


이번 강좌는 DirectX 11 에서 멀티텍스쳐링을 하는 방법과 텍스쳐 배열을 사용하는지에 대해 설명할 것입니다. 멀티텍스쳐링은 다른 두 텍스쳐를 섞어서 최종 텍스쳐를 얻는 과정입니다. 여러분이 텍스쳐를 혼합하는데 사용하는 방정식은 여러분이 성취하고자 하는바에 따라 다를 수 있습니다. 이번 강좌에서는 두 텍스쳐 색상의 평균값을 결합하여 균등하게 혼합된 최종 텍스쳐를 얻는 것을 볼 것입니다.


텍스쳐 배열은 DirectX 10부터 적용된 것으로 여러분이 gpu에서 한번에 여러 텍스쳐를 활성화를 가능하게 해줍니다. 이전 gpu에서 오직 단일 텍스쳐만 활성화되었을때 방법들은 항상 텍스쳐를 로드하고 언로드하는데 많은 추가 과정들이 초래했습니다. 대부분의 사람들은 이러한 문제를 텍스쳐 별칭을 사용하고 (하나의 큰 텍스쳐로 텍스쳐의 묶음을 로딩) UV 좌표를 다르게 사용하여 해결하였습니다. 그러나 이 새로운 특성으로 인해 텍스쳐 별칭 더이상 필요치 않습니다.


첫번째로 이 강좌에서 사용된 텍스쳐이며 우리는 기본 텍스쳐로 부를 것이고 다음과 같습니다.



두번째 텍스쳐는 첫번째것과 결합하는데 사용될 것이고 컬러 텍스쳐라 불릴 겁니다. 다음과 같습니다.



이 두 텍스쳐는 픽셀 쉐이더에서 픽셀대 픽셀로 결합될 것입니다. 우리가 사용할 블렌딩 방정식은 다음과 같습니다.


	blendColor = basePixel * colorPixel * gammaCorrection;


저 방정식과 위 두 텍스쳐를 사용하여 다음과 같은 결과를 얻을 것입니다.



여러분들은 아마도 의아해 하실 겁니다. 왜 그냥 두 텍스쳐 픽셀의 평균값을 다음처럼 더하지 않는지..


	blendColor = (basePixel * 0.5) + (colorPixel * 0.5);


그 이유는 우리에게 표현되는 픽셀 색상은 모니터의 감마값에 달려있기 때문입니다. 이는 픽셀 값을 0.0에서 1.0으로 곡선을 따르게 합니다. 따라서 비선형 컬러 값을 다루기 위해 픽셀 쉐이더에서 감마 보정이 필요합니다. 감마 보정을 하지 않고 단순히 평균을 더하면 다음과 같이 뭔가 젖은 듯한 결과를 얻게 됩니다.



또 중요한게 대부분의 장치들은 감마값이 달라 룩업테이블(미리 계산된 값이 나열된 테이블)이나 감마 슬라이더(보통의 모니터 화면 조정에 있는 것)를 통해 사용자가 장치에 대한 감마 설정을 할 수 있게 해줍니다. 이번 예제에서는 강좌를 쉽게 하기 위해 감마값을 2.0으로 하겠습니다.


먼저 텍스쳐 쉐이더 파일을 조금 변형한 멀티텍스쳐 쉐이더를 살펴봄으로 코드 섹션을 시작하겠습니다.


Multitexture.vs


버텍스 쉐이더는 이름만 바뀌었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.vs
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////
cbuffer MatrixBuffer
{
    matrix worldMatrix;
    matrix viewMatrix;
    matrix projectionMatrix;
};


//////////////
// TYPEDEFS //
//////////////
struct VertexInputType
{
    float4 position : POSITION;
    float2 tex : TEXCOORD0;
};

struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


////////////////////////////////////////////////////////////////////////////////
// Vertex Shader
////////////////////////////////////////////////////////////////////////////////
PixelInputType MultiTextureVertexShader(VertexInputType input)
{
    PixelInputType output;
    

    // Change the position vector to be 4 units for proper matrix calculations.
    input.position.w = 1.0f;

    // Calculate the position of the vertex against the world, view, and projection matrices.
    output.position = mul(input.position, worldMatrix);
    output.position = mul(output.position, viewMatrix);
    output.position = mul(output.position, projectionMatrix);
    
    // Store the texture coordinates for the pixel shader.
    output.tex = input.tex;
    
    return output;
}


Multitexture.ps


////////////////////////////////////////////////////////////////////////////////
// Filename: multitexture.ps
////////////////////////////////////////////////////////////////////////////////


/////////////
// GLOBALS //
/////////////


서로 혼합될 두 텍스쳐에 대한 텍스쳐 배열 리소스를 추가하였습니다. 텍스쳐 배열은 그래픽 카드상 성능면에서 단일 텍스쳐 리소스를 사용하는 것보다 더 효율적입니다. 텍스쳐 스위칭은 이전 DirectX 버전에서 비용이 큰 작업이어서 대부분의 엔진들을 텍스쳐와 재질 스위치 중심으로 작성되어야 했습니다. 텍스쳐 배열은 그런 수행 비용을 줄이는데 도움이 됩니다.


Texture2D shaderTextures[2];
SamplerState SampleType;


//////////////
// TYPEDEFS //
//////////////
struct PixelInputType
{
    float4 position : SV_POSITION;
    float2 tex : TEXCOORD0;
};


픽셀 쉐이더가 이번 강좌의 모든 작업이 수행되는 곳입니다. 현재 두 텍스쳐의 좌표에서 픽셀의 샘플을 취합니다. 그 후에 픽셀 컬러를 곱하여 결합하는데 감마 보정때문에 비선형이기 때문입니다. 또 감마값을 곱하는데 대부분의 모니터의 감마값에 가까운 2.0를 사용하였습니다. 혼합된 픽셀을 얻은 뒤 0~1사이로 만들고(saturate) 최종 결과로 반환합니다. 또 텍스쳐 배열의 두 텍스쳐에 접근하는데 인덱싱을 사용한 것을 기억하시기 바랍니다.


////////////////////////////////////////////////////////////////////////////////
// Pixel Shader
////////////////////////////////////////////////////////////////////////////////
float4 MultiTexturePixelShader(PixelInputType input) : SV_TARGET
{
    float4 color1;
    float4 color2;
    float4 blendColor;


    // Get the pixel color from the first texture.
    color1 = shaderTextures[0].Sample(SampleType, input.tex);

    // Get the pixel color from the second texture.
    color2 = shaderTextures[1].Sample(SampleType, input.tex);

    // Blend the two pixels together and multiply by the gamma value.
    blendColor = color1 * color2 * 2.0;
    
    // Saturate the final color.
    blendColor = saturate(blendColor);

    return blendColor;
}


Multitextureshaderclass.h


multitexture shader 코드는 TextureShaderClass에서 약간 수정하였습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MULTITEXTURESHADERCLASS_H_
#define _MULTITEXTURESHADERCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <d3dx11async.h>
#include <fstream>
using namespace std;


////////////////////////////////////////////////////////////////////////////////
// Class name: MultiTextureShaderClass
////////////////////////////////////////////////////////////////////////////////
class MultiTextureShaderClass
{
private:
	struct MatrixBufferType
	{
		D3DXMATRIX world;
		D3DXMATRIX view;
		D3DXMATRIX projection;
	};

public:
	MultiTextureShaderClass();
	MultiTextureShaderClass(const MultiTextureShaderClass&);
	~MultiTextureShaderClass();

	bool Initialize(ID3D11Device*, HWND);
	void Shutdown();
	bool Render(ID3D11DeviceContext*, int, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**);

private:
	bool InitializeShader(ID3D11Device*, HWND, WCHAR*, WCHAR*);
	void ShutdownShader();
	void OutputShaderErrorMessage(ID3D10Blob*, HWND, WCHAR*);

	bool SetShaderParameters(ID3D11DeviceContext*, D3DXMATRIX, D3DXMATRIX, D3DXMATRIX, ID3D11ShaderResourceView**);
	void RenderShader(ID3D11DeviceContext*, int);

private:
	ID3D11VertexShader* m_vertexShader;
	ID3D11PixelShader* m_pixelShader;
	ID3D11InputLayout* m_layout;
	ID3D11Buffer* m_matrixBuffer;
	ID3D11SamplerState* m_sampleState;
};

#endif


Multitextureshaderclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: multitextureshaderclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "multitextureshaderclass.h"


MultiTextureShaderClass::MultiTextureShaderClass()
{
	m_vertexShader = 0;
	m_pixelShader = 0;
	m_layout = 0;
	m_matrixBuffer = 0;
	m_sampleState = 0;
}


MultiTextureShaderClass::MultiTextureShaderClass(const MultiTextureShaderClass& other)
{
}


MultiTextureShaderClass::~MultiTextureShaderClass()
{
}


bool MultiTextureShaderClass::Initialize(ID3D11Device* device, HWND hwnd)
{
	bool result;


multitexture HLSL 쉐이더 파일은 여기서 로드됩니다.


	// Initialize the vertex and pixel shaders.
	result = InitializeShader(device, hwnd, L"../Engine/multitexture.vs", L"../Engine/multitexture.ps");
	if(!result)
	{
		return false;
	}

	return true;
}


Shutdown은 ShutdownShader 함수를 호출하여 쉐이더 관련 인터페이스들을 해제합니다.


void MultiTextureShaderClass::Shutdown()
{
	// Shutdown the vertex and pixel shaders as well as the related objects.
	ShutdownShader();

	return;
}


이제 Render 함수는 텍스쳐 배열의 포인터를 받습니다. 이는 쉐이더에 혼할 계산을 위한 두 텍스쳐의 접근을 하게 해줄 것입니다.


bool MultiTextureShaderClass::Render(ID3D11DeviceContext* deviceContext, int indexCount, D3DXMATRIX worldMatrix, 
				     D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, ID3D11ShaderResourceView** textureArray)
{
	bool result;


	// Set the shader parameters that it will use for rendering.
	result = SetShaderParameters(deviceContext, worldMatrix, viewMatrix, projectionMatrix, textureArray);
	if(!result)
	{
		return false;
	}

	// Now render the prepared buffers with the shader.
	RenderShader(deviceContext, indexCount);

	return true;
}


InitializeShader 함수는 정점 쉐이더와 픽셀 쉐이더 뿐만아니라 레이아웃, 매트릭스 버퍼와 샘플 state도 로드한다.


bool MultiTextureShaderClass::InitializeShader(ID3D11Device* device, HWND hwnd, WCHAR* vsFilename, WCHAR* psFilename)
{
	HRESULT result;
	ID3D10Blob* errorMessage;
	ID3D10Blob* vertexShaderBuffer;
	ID3D10Blob* pixelShaderBuffer;
	D3D11_INPUT_ELEMENT_DESC polygonLayout[2];
	unsigned int numElements;
	D3D11_BUFFER_DESC matrixBufferDesc;
	D3D11_SAMPLER_DESC samplerDesc;


	// Initialize the pointers this function will use to null.
	errorMessage = 0;
	vertexShaderBuffer = 0;
	pixelShaderBuffer = 0;


멀티텍스쳐 정점 쉐이더는 여기서 로드됩니다.


	// Compile the vertex shader code.
	result = D3DX11CompileFromFile(vsFilename, NULL, NULL, "MultiTextureVertexShader", "vs_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 
				       0, NULL, &vertexShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, vsFilename);
		}
		// If there was  nothing in the error message then it simply could not find the shader file itself.
		else
		{
			MessageBox(hwnd, vsFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}


멀티텍스쳐 픽셀 쉐이더는 여기서 로드됩니다.


	// Compile the pixel shader code.
	result = D3DX11CompileFromFile(psFilename, NULL, NULL, "MultiTexturePixelShader", "ps_5_0", D3D10_SHADER_ENABLE_STRICTNESS, 
				       0, NULL, &pixelShaderBuffer, &errorMessage, NULL);
	if(FAILED(result))
	{
		// If the shader failed to compile it should have writen something to the error message.
		if(errorMessage)
		{
			OutputShaderErrorMessage(errorMessage, hwnd, psFilename);
		}
		// If there was  nothing in the error message then it simply could not find the file itself.
		else
		{
			MessageBox(hwnd, psFilename, L"Missing Shader File", MB_OK);
		}

		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreateVertexShader(vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), NULL, 
					    &m_vertexShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex shader from the buffer.
	result = device->CreatePixelShader(pixelShaderBuffer->GetBufferPointer(), pixelShaderBuffer->GetBufferSize(), NULL, 
					   &m_pixelShader);
	if(FAILED(result))
	{
		return false;
	}

	// Create the vertex input layout description.
	// This setup needs to match the VertexType stucture in the ModelClass and in the shader.
	polygonLayout[0].SemanticName = "POSITION";
	polygonLayout[0].SemanticIndex = 0;
	polygonLayout[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
	polygonLayout[0].InputSlot = 0;
	polygonLayout[0].AlignedByteOffset = 0;
	polygonLayout[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[0].InstanceDataStepRate = 0;

	polygonLayout[1].SemanticName = "TEXCOORD";
	polygonLayout[1].SemanticIndex = 0;
	polygonLayout[1].Format = DXGI_FORMAT_R32G32_FLOAT;
	polygonLayout[1].InputSlot = 0;
	polygonLayout[1].AlignedByteOffset = D3D11_APPEND_ALIGNED_ELEMENT;
	polygonLayout[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
	polygonLayout[1].InstanceDataStepRate = 0;

	// Get a count of the elements in the layout.
	numElements = sizeof(polygonLayout) / sizeof(polygonLayout[0]);

	// Create the vertex input layout.
	result = device->CreateInputLayout(polygonLayout, numElements, vertexShaderBuffer->GetBufferPointer(), 
					   vertexShaderBuffer->GetBufferSize(), &m_layout);
	if(FAILED(result))
	{
		return false;
	}

	// Release the vertex shader buffer and pixel shader buffer since they are no longer needed.
	vertexShaderBuffer->Release();
	vertexShaderBuffer = 0;

	pixelShaderBuffer->Release();
	pixelShaderBuffer = 0;

	// Setup the description of the matrix dynamic constant buffer that is in the vertex shader.
	matrixBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
	matrixBufferDesc.ByteWidth = sizeof(MatrixBufferType);
	matrixBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
	matrixBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
	matrixBufferDesc.MiscFlags = 0;
	matrixBufferDesc.StructureByteStride = 0;

	// Create the matrix constant buffer pointer so we can access the vertex shader constant buffer from within this class.
	result = device->CreateBuffer(&matrixBufferDesc, NULL, &m_matrixBuffer);
	if(FAILED(result))
	{
		return false;
	}

	// Create a texture sampler state description.
	samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
	samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
	samplerDesc.MipLODBias = 0.0f;
	samplerDesc.MaxAnisotropy = 1;
	samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
	samplerDesc.BorderColor[0] = 0;
	samplerDesc.BorderColor[1] = 0;
	samplerDesc.BorderColor[2] = 0;
	samplerDesc.BorderColor[3] = 0;
	samplerDesc.MinLOD = 0;
	samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

	// Create the texture sampler state.
	result = device->CreateSamplerState(&samplerDesc, &m_sampleState);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


ShutdownShader는 InitializeShader 함수에서 셋업된 모든 인터페이스들을 해제합니다.


void MultiTextureShaderClass::ShutdownShader()
{
	// Release the sampler state.
	if(m_sampleState)
	{
		m_sampleState->Release();
		m_sampleState = 0;
	}

	// Release the matrix constant buffer.
	if(m_matrixBuffer)
	{
		m_matrixBuffer->Release();
		m_matrixBuffer = 0;
	}

	// Release the layout.
	if(m_layout)
	{
		m_layout->Release();
		m_layout = 0;
	}

	// Release the pixel shader.
	if(m_pixelShader)
	{
		m_pixelShader->Release();
		m_pixelShader = 0;
	}

	// Release the vertex shader.
	if(m_vertexShader)
	{
		m_vertexShader->Release();
		m_vertexShader = 0;
	}

	return;
}


OutputShaderErrorMessage 함수는 정점 쉐이더나 픽셀 쉐이더의 HLSL 파일을 컴파일중 문제가 있다면 에러를 파일에 기록합니다.


void MultiTextureShaderClass::OutputShaderErrorMessage(ID3D10Blob* errorMessage, HWND hwnd, WCHAR* shaderFilename)
{
	char* compileErrors;
	unsigned long bufferSize, i;
	ofstream fout;


	// Get a pointer to the error message text buffer.
	compileErrors = (char*)(errorMessage->GetBufferPointer());

	// Get the length of the message.
	bufferSize = errorMessage->GetBufferSize();

	// Open a file to write the error message to.
	fout.open("shader-error.txt");

	// Write out the error message.
	for(i=0; i<bufferSize; i++)
	{
		fout << compileErrors[i];
	}

	// Close the file.
	fout.close();

	// Release the error message.
	errorMessage->Release();
	errorMessage = 0;

	// Pop a message up on the screen to notify the user to check the text file for compile errors.
	MessageBox(hwnd, L"Error compiling shader.  Check shader-error.txt for message.", shaderFilename, MB_OK);

	return;
}


SetShaderParameters는 렌더링전 매트릭스와 텍스쳐 배열을 설정합니다.


bool MultiTextureShaderClass::SetShaderParameters(ID3D11DeviceContext* deviceContext, D3DXMATRIX worldMatrix, 
						  D3DXMATRIX viewMatrix, D3DXMATRIX projectionMatrix, 
						  ID3D11ShaderResourceView** textureArray)
{
	HRESULT result;
	D3D11_MAPPED_SUBRESOURCE mappedResource;
	MatrixBufferType* dataPtr;
	unsigned int bufferNumber;


	// Transpose the matrices to prepare them for the shader.
	D3DXMatrixTranspose(&worldMatrix, &worldMatrix);
	D3DXMatrixTranspose(&viewMatrix, &viewMatrix);
	D3DXMatrixTranspose(&projectionMatrix, &projectionMatrix);

	// Lock the matrix constant buffer so it can be written to.
	result = deviceContext->Map(m_matrixBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource);
	if(FAILED(result))
	{
		return false;
	}

	// Get a pointer to the data in the constant buffer.
	dataPtr = (MatrixBufferType*)mappedResource.pData;

	// Copy the matrices into the constant buffer.
	dataPtr->world = worldMatrix;
	dataPtr->view = viewMatrix;
	dataPtr->projection = projectionMatrix;

	// Unlock the matrix constant buffer.
	deviceContext->Unmap(m_matrixBuffer, 0);

	// Set the position of the matrix constant buffer in the vertex shader.
	bufferNumber = 0;

	// Now set the matrix constant buffer in the vertex shader with the updated values.
	deviceContext->VSSetConstantBuffers(bufferNumber, 1, &m_matrixBuffer);


여기가 렌더링전에 텍스쳐 배열이 설정되는 곳입니다. PSSetShaderResources 함수는 텍스쳐 배열을 설정하는데 사용됩니다. 첫번째 파라미터는 배열에서 시작하는 곳(인덱스)입니다. 두번째 파라미터는 넘겨지는 배열에 든 텍스쳐의 개수 입니다. 그리고 세번째 파라미터는 텍스쳐 배열에 대한 포인터입니다.


	// Set shader texture array resource in the pixel shader.
	deviceContext->PSSetShaderResources(0, 2, textureArray);

	return true;
}


RenderShader 함수는 레이아웃, 쉐이더, 샘플러를 설정합니다. 그리고는 쉐이더를 이용하여 모델을 그립니다.


void MultiTextureShaderClass::RenderShader(ID3D11DeviceContext* deviceContext, int indexCount)
{
	// Set the vertex input layout.
	deviceContext->IASetInputLayout(m_layout);

	// Set the vertex and pixel shaders that will be used to render this triangle.
	deviceContext->VSSetShader(m_vertexShader, NULL, 0);
	deviceContext->PSSetShader(m_pixelShader, NULL, 0);

	// Set the sampler state in the pixel shader.
	deviceContext->PSSetSamplers(0, 1, &m_sampleState);

	// Render the triangles.
	deviceContext->DrawIndexed(indexCount, 0, 0);

	return;
}


Texturearrayclass.h


TextureArrayClass는 전에 사용된 TextureClass를 대체합니다. 이제 단순 단일 텍스쳐를 가지지 않고 다수의 텍스쳐를 가질 수 있고 필요로 하는 오브젝트들에게 접근할 수 있게 됩니다. 이번 강좌는 그저 텍스쳐 두개만 다루지만 쉽게 확장될 수 있습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _TEXTUREARRAYCLASS_H_
#define _TEXTUREARRAYCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx11tex.h>


////////////////////////////////////////////////////////////////////////////////
// Class name: TextureArrayClass
////////////////////////////////////////////////////////////////////////////////
class TextureArrayClass
{
public:
	TextureArrayClass();
	TextureArrayClass(const TextureArrayClass&);
	~TextureArrayClass();

	bool Initialize(ID3D11Device*, WCHAR*, WCHAR*);
	void Shutdown();

	ID3D11ShaderResourceView** GetTextureArray();

private:


여기 원소 2개짜리 텍스쳐 배열이 This is the two element texture array private variable.


	ID3D11ShaderResourceView* m_textures[2];
};

#endif


Texturearrayclass.cpp


////////////////////////////////////////////////////////////////////////////////
// Filename: texturearrayclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "texturearrayclass.h"


클래스 생성자는 텍스쳐 배열 원소들을 null로 초기화합니다.


TextureArrayClass::TextureArrayClass()
{
	m_textures[0] = 0;
	m_textures[1] = 0;
}


TextureArrayClass::TextureArrayClass(const TextureArrayClass& other)
{
}


TextureArrayClass::~TextureArrayClass()
{
}


Initialize 함수는 두 텍스쳐 파일 이름을 받아서 해당 파일로부터 텍스쳐 배열안에 두 텍스쳐 자원을 생성합니다.


bool TextureArrayClass::Initialize(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
	HRESULT result;


	// Load the first texture in.
	result = D3DX11CreateShaderResourceViewFromFile(device, filename1, NULL, NULL, &m_textures[0], NULL);
	if(FAILED(result))
	{
		return false;
	}

	// Load the second texture in.
	result = D3DX11CreateShaderResourceViewFromFile(device, filename2, NULL, NULL, &m_textures[1], NULL);
	if(FAILED(result))
	{
		return false;
	}

	return true;
}


Shutdown 함수는 텍스쳐 배열의 각 원소를 해제합니다.


void TextureArrayClass::Shutdown()
{
	// Release the texture resources.
	if(m_textures[0])
	{
		m_textures[0]->Release();
		m_textures[0] = 0;
	}

	if(m_textures[1])
	{
		m_textures[1]->Release();
		m_textures[1] = 0;
	}

	return;
}


GetTextureArray 함수는 텍스쳐 배열의 포인터를 반환하여 함수를 호출하는 객체는 텍스쳐 배열내 텍스쳐들에 접근할 수 있습니다.


ID3D11ShaderResourceView** TextureArrayClass::GetTextureArray()
{
	return m_textures;
}


Modelclass.h


ModelClass는 텍스쳐 배열을 사용하도록 수정되었습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _MODELCLASS_H_
#define _MODELCLASS_H_


//////////////
// INCLUDES //
//////////////
#include <d3d11.h>
#include <d3dx10math.h>
#include <fstream>
using namespace std;


TextureArrayClass 헤더파일이 이전의 TextureClass 헤더를 대신하여 포함되었습니다.


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "texturearrayclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: ModelClass
////////////////////////////////////////////////////////////////////////////////
class ModelClass
{
private:
	struct VertexType
	{
		D3DXVECTOR3 position;
		D3DXVECTOR2 texture;
	};

	struct ModelType
	{
		float x, y, z;
		float tu, tv;
		float nx, ny, nz;
	};

public:
	ModelClass();
	ModelClass(const ModelClass&);
	~ModelClass();

	bool Initialize(ID3D11Device*, char*, WCHAR*, WCHAR*);
	void Shutdown();
	void Render(ID3D11DeviceContext*);

	int GetIndexCount();
	ID3D11ShaderResourceView** GetTextureArray();

private:
	bool InitializeBuffers(ID3D11Device*);
	void ShutdownBuffers();
	void RenderBuffers(ID3D11DeviceContext*);

	bool LoadTextures(ID3D11Device*, WCHAR*, WCHAR*);
	void ReleaseTextures();

	bool LoadModel(char*);
	void ReleaseModel();

private:
	ID3D11Buffer *m_vertexBuffer, *m_indexBuffer;
	int m_vertexCount, m_indexCount;
	ModelType* m_model;


이제 TextureClass 변수 대신 TextureArrayClass 변수를 가집니다.


	TextureArrayClass* m_TextureArray;
};

#endif


Modelclass.cpp


이전 강좌이후 바뀐 함수들만 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: modelclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "modelclass.h"


ModelClass::ModelClass()
{
	m_vertexBuffer = 0;
	m_indexBuffer = 0;
	m_model = 0;


생성자에서 새 TextureArray변수를 초기화합니다.


	m_TextureArray = 0;
}


bool ModelClass::Initialize(ID3D11Device* device, char* modelFilename, WCHAR* textureFilename1, WCHAR* textureFilename2)
{
	bool result;


	// Load in the model data,
	result = LoadModel(modelFilename);
	if(!result)
	{
		return false;
	}

	// Initialize the vertex and index buffers.
	result = InitializeBuffers(device);
	if(!result)
	{
		return false;
	}


이번 모델에서 텍스쳐배열에 로드되고 혼합된 결과로 렌더링 될 텍스쳐에 대한 여러 파일 이름을 받는 LoadTextures 함수를 호출합니다.


	// Load the textures for this model.
	result = LoadTextures(device, textureFilename1, textureFilename2);
	if(!result)
	{
		return false;
	}

	return true;
}


void ModelClass::Shutdown()
{


Shutdown 함수에서 ReleaseTextures 함수를 호출하여 텍스쳐 배열을 해제합니다.


	// Release the model textures.
	ReleaseTextures();

	// Shutdown the vertex and index buffers.
	ShutdownBuffers();

	// Release the model data.
	ReleaseModel();

	return;
}


호출하는 객체에게 모델 렌더링에 사용될 텍스쳐 배열로의 접근을 가능케 해주는 GetTextureArray 함수를 새로 가집니다.


ID3D11ShaderResourceView** ModelClass::GetTextureArray()
{
	return m_TextureArray->GetTextureArray();
}


LoadTextures 함수는 함수에 매개변수로 들어온 두 텍스쳐를 로딩을 하기위해 TextureArrayClass 객체를 생성하고 초기화하도록 바뀌었습니다.


bool ModelClass::LoadTextures(ID3D11Device* device, WCHAR* filename1, WCHAR* filename2)
{
	bool result;


	// Create the texture array object.
	m_TextureArray = new TextureArrayClass;
	if(!m_TextureArray)
	{
		return false;
	}

	// Initialize the texture array object.
	result = m_TextureArray->Initialize(device, filename1, filename2);
	if(!result)
	{
		return false;
	}

	return true;
}


ReleaseTextures 함수는 TextureArrayClass 객체를 해제합니다.


void ModelClass::ReleaseTextures()
{
	// Release the texture array object.
	if(m_TextureArray)
	{
		m_TextureArray->Shutdown();
		delete m_TextureArray;
		m_TextureArray = 0;
	}

	return;
}


Graphicsclass.h


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = true;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
#include "cameraclass.h"
#include "modelclass.h"


GraphicsClass에서 MultiTextureShaderClass에 대한 헤더를 포함합니다.


#include "multitextureshaderclass.h"


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();
	bool Render();

private:
	D3DClass* m_D3D;
	CameraClass* m_Camera;
	ModelClass* m_Model;


여기서 새로운 MultiTextureShaderClass 객체를 생성하였습니다.


	MultiTextureShaderClass* m_MultiTextureShader;
};

#endif


Graphicsclass.cpp


이전 강좌에서 바뀐 함수만을 다룰 것입니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "graphicsclass.h"


GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
	m_Camera = 0;
	m_Model = 0;


생성자에서 새 mulltitexture shader 객체를 null로 초기화합니다.


	m_MultiTextureShader = 0;
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{
	bool result;
	D3DXMATRIX baseViewMatrix;

		
	// Create the Direct3D object.
	m_D3D = new D3DClass;
	if(!m_D3D)
	{
		return false;
	}

	// Initialize the Direct3D object.
	result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK);
		return false;
	}

	// Create the camera object.
	m_Camera = new CameraClass;
	if(!m_Camera)
	{
		return false;
	}

	// Initialize a base view matrix with the camera for 2D user interface rendering.
	m_Camera->SetPosition(0.0f, 0.0f, -1.0f);
	m_Camera->Render();
	m_Camera->GetViewMatrix(baseViewMatrix);

	// Create the model object.
	m_Model = new ModelClass;
	if(!m_Model)
	{
		return false;
	}


이제 ModelClass 객체는 좀 다르게 초기화됩니다. 이번 강좌에는 간단히 사각형 평면 위에서 잘 보여지는지 확인만을 목적으로 square.txt 모델을 로드합니다. 또 이전 강좌에서처럼 단일 텍스쳐가 아닌 텍스쳐 배열을 위한 두 텍스쳐를 로드합니다.


	// Initialize the model object.
	result = m_Model->Initialize(m_D3D->GetDevice(), "../Engine/data/square.txt", L"../Engine/data/stone01.dds", 
				     L"../Engine/data/dirt01.dds");
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the model object.", L"Error", MB_OK);
		return false;
	}


여기 새 multitexture shader 객체를 생성하고 초기화합니다.


	// Create the multitexture shader object.
	m_MultiTextureShader = new MultiTextureShaderClass;
	if(!m_MultiTextureShader)
	{
		return false;
	}

	// Initialize the multitexture shader object.
	result = m_MultiTextureShader->Initialize(m_D3D->GetDevice(), hwnd);
	if(!result)
	{
		MessageBox(hwnd, L"Could not initialize the multitexture shader object.", L"Error", MB_OK);
		return false;
	}

	return true;
}


void GraphicsClass::Shutdown()
{


Shutdown 함수에서 multitexture shader를 해제합니다.


	// Release the multitexture shader object.
	if(m_MultiTextureShader)
	{
		m_MultiTextureShader->Shutdown();
		delete m_MultiTextureShader;
		m_MultiTextureShader = 0;
	}

	// Release the model object.
	if(m_Model)
	{
		m_Model->Shutdown();
		delete m_Model;
		m_Model = 0;
	}

	// Release the camera object.
	if(m_Camera)
	{
		delete m_Camera;
		m_Camera = 0;
	}

	// Release the D3D object.
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}


bool GraphicsClass::Frame()
{


블렌딩 효과를 좀더 쉽게 보기 위해 카메라의 위치를 약간 더 가깝게 설정하였습니다.


	// Set the position of the camera.
	m_Camera->SetPosition(0.0f, 0.0f, -5.0f);

	return true;
}


bool GraphicsClass::Render()
{
	D3DXMATRIX worldMatrix, viewMatrix, projectionMatrix, orthoMatrix;


	// Clear the buffers to begin the scene.
	m_D3D->BeginScene(0.0f, 0.0f, 0.0f, 1.0f);

	// Generate the view matrix based on the camera's position.
	m_Camera->Render();

	// Get the world, view, projection, and ortho matrices from the camera and D3D objects.
	m_D3D->GetWorldMatrix(worldMatrix);
	m_Camera->GetViewMatrix(viewMatrix);
	m_D3D->GetProjectionMatrix(projectionMatrix);
	m_D3D->GetOrthoMatrix(orthoMatrix);

	// Put the model vertex and index buffers on the graphics pipeline to prepare them for drawing.
	m_Model->Render(m_D3D->GetDeviceContext());


multitexture shader를 모델 렌더링에 사용합니다. 주의할 점은 텍스쳐 배열을 ModelClass에서 입력으로 쉐이더에 보내야 합니다.


	// Render the model using the multitexture shader.
	m_MultiTextureShader->Render(m_D3D->GetDeviceContext(), m_Model->GetIndexCount(), worldMatrix, viewMatrix, projectionMatrix,
				     m_Model->GetTextureArray());

	// Present the rendered scene to the screen.
	m_D3D->EndScene();

	return true;
}


Summary


이제 두 텍스쳐를 고르게 결합하고 감마 보정을 적용시킬 쉐이더를 갖게 되었습니다. 또 향상된 그래픽스 퍼포먼스를 위해 텍스쳐 배열을 어떻게 사용하는지에 대해서도 배웠습니다.




연습하기


1. 코드를 재컴파일하고 프로그램을 실행하여 결과 이미지 확인하기. 종료하기 위해 esc버튼을 누르세요.


2. 다른 텍스쳐들로 바꿔서 결과 보기.

728x90
728x90

윤성우씨의 "열혈강의 TCP/IP 소켓 프로그래밍" 책 끝부분의 IOCP 예제를 c++형식으로 바꾸었다.


main.cpp

#include "IOCPServer.h"

int main()
{
    IOCPServer *s = new IOCPServer();

    s->Init();

    s->Run();

    delete s;
    return 0;
}


별 내용 없다.


IOCPServer.h

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <process.h>
#include <winsock2.h>

#define PORT        9088
#define BUFSIZE     1024
#define READ        3
#define WRITE       5

///////////////////////////////////////////////
// Structure Definition
typedef struct          // 소켓 정보를 구조체화
{
    SOCKET      hClntSock;
    SOCKADDR_IN clntAddr;
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

typedef struct          // 소켓의 버퍼 정보를 구조체화
{
    OVERLAPPED overlapped;
    CHAR       buffer[BUFSIZE];
    WSABUF     wsaBuf;
    int        rwMode;
} PER_IO_DATA, *LPPER_IO_DATA;


////////////////////////////////////////////////
// Class definition
class IOCPServer {
public:
    IOCPServer();
    ~IOCPServer();

    bool Run();
    bool Init();
    void Cleanup();

    bool setSocket();

    static unsigned int __stdcall _CompletionThread(void *p_this);
    UINT WINAPI CompletionThread();

private:
    HANDLE  m_hCompletionPort;

    SOCKET m_hServSock;
    SOCKADDR_IN m_servAddr;
};

//////////////////////////////////////////
// Function forward declaration
void ErrorHandling(LPCSTR pszMessage);


중간에 _CompletionThread() 멤버함수가 static인 이유는 _beginthreadex() 함수의 인자때문으로


_CompletionThread()가 호출되면 바로 CompletionThread() 멤버함수를 호출한다.


IOCPServer.cpp

#include "IOCPServer.h" IOCPServer::IOCPServer() { } IOCPServer::~IOCPServer() { closesocket(m_hServSock); WSACleanup(); } bool IOCPServer::Init() { WSAData wsaData; SYSTEM_INFO systemInfo; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { // Load Winsock 2.2 DLL ErrorHandling("WSAStartup() error!"); } // Completion Port 생성 m_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); GetSystemInfo(&systemInfo); // 쓰레드를 CPU 개수*2 만큼 생성 for (int i = 0; i<systemInfo.dwNumberOfProcessors*2; i++) { _beginthreadex(NULL, 0, _CompletionThread, (LPVOID)this, 0, NULL); } setSocket(); return true; } void IOCPServer::Cleanup() { } bool IOCPServer::setSocket() { // IOCP를 사용하기 위한 소켓 생성 (Overlapped I/O 소켓, 윈도우용) m_hServSock = WSASocket(PF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED); if (m_hServSock == INVALID_SOCKET) { ErrorHandling("WSASocket() error!"); } // 바인딩할 소켓 주소정보 memset(&m_servAddr, 0, sizeof(m_servAddr)); m_servAddr.sin_family = AF_INET; m_servAddr.sin_addr.s_addr = htonl(INADDR_ANY); m_servAddr.sin_port = htons(PORT); if (bind(m_hServSock, (SOCKADDR *)&m_servAddr, sizeof(m_servAddr)) == SOCKET_ERROR) { ErrorHandling("bind() error!"); } if (listen(m_hServSock, 10) == SOCKET_ERROR) { ErrorHandling("listen() error!"); } return true; } bool IOCPServer::Run() { LPPER_IO_DATA perIoData; LPPER_HANDLE_DATA perHandleData; DWORD dwRecvBytes; DWORD i, dwFlags; while (TRUE) { SOCKET hClntSock; SOCKADDR_IN clntAddr; int nAddrLen = sizeof(clntAddr); hClntSock = accept(m_hServSock, (SOCKADDR *)&clntAddr, &nAddrLen); if (hClntSock == INVALID_SOCKET) { ErrorHandling("accept() error!"); } // 연결된 클라이언트의 소켓 핸들 정보와 주소 정보를 설정 perHandleData = new PER_HANDLE_DATA; perHandleData->hClntSock = hClntSock; memcpy(&(perHandleData->clntAddr), &clntAddr, nAddrLen); // 3. Overlapped 소켓과 Completion Port 의 연결 CreateIoCompletionPort((HANDLE)hClntSock, m_hCompletionPort, (DWORD)perHandleData, 0); // 클라이언트를 위한 버퍼를 설정, OVERLAPPED 변수 초기화 perIoData = new PER_IO_DATA; memset(&(perIoData->overlapped), 0, sizeof(OVERLAPPED)); perIoData->wsaBuf.len = BUFSIZE; perIoData->wsaBuf.buf = perIoData->buffer; perIoData->rwMode = READ; dwFlags = 0; // 4. 중첩된 데이타 입력 WSARecv(perHandleData->hClntSock, // 데이타 입력 소켓 &(perIoData->wsaBuf), // 데이타 입력 버퍼 포인터 1, // 데이타 입력 버퍼의 수 &dwRecvBytes, &dwFlags, &(perIoData->overlapped), // OVERLAPPED 변수 포인터 NULL ); } return true; } unsigned int __stdcall IOCPServer::_CompletionThread(void *p_this) { IOCPServer* p_IOCPServer = static_cast<IOCPServer*>(p_this); p_IOCPServer->CompletionThread(); // Non-static member function! return 0; } UINT WINAPI IOCPServer::CompletionThread() { HANDLE hCompletionPort = (HANDLE)m_hCompletionPort; SOCKET hClntSock; DWORD dwBytesTransferred; LPPER_HANDLE_DATA perHandleData; LPPER_IO_DATA perIoData; DWORD dwFlags; while (TRUE) { // 5. 입.출력이 완료된 소켓의 정보 얻음 GetQueuedCompletionStatus(hCompletionPort, // Completion Port &dwBytesTransferred, // 전송된 바이트 수 (LPDWORD)&perHandleData, (LPOVERLAPPED *)&perIoData, INFINITE); //printf("GQCS error code : %d (TID : %d)\n", GetLastError(), GetCurrentThreadId()); if (perIoData->rwMode == READ) { puts("message received!"); if (dwBytesTransferred == 0) // 전송된 바이트가 0일때 종료 (EOF 전송 시에도) { puts("socket will be closed!"); closesocket(perHandleData->hClntSock); delete perHandleData; delete perIoData; continue; } // 6. 메세지. 클라이언트로 에코 memset(&(perIoData->overlapped), 0, sizeof(OVERLAPPED)); perIoData->wsaBuf.len = dwBytesTransferred; perIoData->rwMode = WRITE; WSASend(perHandleData->hClntSock, &(perIoData->wsaBuf), 1, NULL, 0, &(perIoData->overlapped), NULL); // RECEIVE AGAIN perIoData = new PER_IO_DATA(); memset(&(perIoData->overlapped), 0, sizeof(OVERLAPPED)); perIoData->wsaBuf.len = BUFSIZE; perIoData->wsaBuf.buf = perIoData->buffer; perIoData->rwMode = READ; dwFlags = 0; WSARecv(perHandleData->hClntSock, &(perIoData->wsaBuf), 1, NULL, &dwFlags, &(perIoData->overlapped), NULL); } else // WRITE Mode { puts("message sent!"); delete perIoData; } } return 0; } void ErrorHandling(LPCSTR pszMessage) { fputs(pszMessage, stderr); fputc('\n', stderr); exit(1); }


CreateIoCompletionPort() 함수는 특이하게도 이름처럼 completion port(이하 cp)를 생성하는데도 쓰지만


cp에 소켓을 등록하는데에도 쓰인다.


Run() 멤버 함수는 호출되면 클라이언트의 연결을 받아들이고 cp에 등록 후 WSARecv()를 호출하는 과정을 반복한다.


그리고 CompletionThread() 에서 각 스레드는 GetQueuedCompletionStatus() 를 호출하고 입출력이 완료된 소켓이


큐에 올라올때까지 대기한다. 입출력이 완료된(입력이 완료된) 소켓이 올라오면 받은 데이터를 그대로 WSASend()를 이용하여 보내주고


다시 WSARecv()호출한다.


실행한 모습

728x90

'프로그래밍 > C++' 카테고리의 다른 글

[C++] Reference Counting  (2) 2020.11.04
힙(Heap)과 완전 이진 트리(Complete binary tree)  (1) 2019.10.31
[c] SHA-1 구조 및 코드  (2) 2017.12.09
[c] AES 구조 및 코드  (2) 2017.11.15
[c] DES 구조 및 코드  (21) 2017.10.26
728x90

나중에 쓸 것 같아서 (이러고 쓰지 않는다.) 대충 서버와 메시지를 주고 받을 수 있게 만들었다.


기본 화면 구성이다.



IP와 Port를 받아서 소켓을 생성하고 Input datas에 아무 문자열을 입력하면 보내면


에코서버에서 똑같은 문자열을 보내며 그 결과를 토스트객체를 통해 화면에 띄운다. 이게 다다. 매우 초라하다.


예외처리를 하지 않았기 때문에 소켓 생성 없이 SEND 버튼을 누르면 앱이 뻗는다. 꼭 미리 누르자.


AndroidManifest.xml 파일에 네트워킹 사용을 위해


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


이 퍼미션을 넣는 것을 잊으면 안된다.

참고 : 에코서버 코드



MainActivity.java

package com.example.hyunseo.testingapp;

import android.app.ProgressDialog;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.*;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button m_bCreateSocket;
    private Button m_bSend;
    private EditText m_etInputIP;
    private EditText m_etInputPort;
    private EditText m_etSendData;
    private ProgressDialog m_pdIsLoading;

    private SocketManager m_hSocketManager;

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

        m_etInputIP = (EditText) findViewById(R.id.editText_inputIP);
        m_etInputPort = (EditText) findViewById(R.id.editText_inputPort);
        m_etSendData = (EditText) findViewById(R.id.editText_sendData);
        m_pdIsLoading = new ProgressDialog(this);

        // 클릭 이벤트 리스너 등록
        m_bCreateSocket.setOnClickListener(this);
        m_bSend.setOnClickListener(this);
    }

    private Handler m_Handler = new Handler() {
        public void handleMessage(Message msg) {
            switch(msg.what) {
                case 0: // 소켓 생성 완료
                    // 로딩화면 제거
                    m_pdIsLoading.dismiss();
                    break;
                case 1: // 데이터 수신 완료
                    // 수신 데이터 토스트로 띄움.
                    Toast.makeText(MainActivity.this, "server responded : " + msg.obj, Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    };

    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button_CreateSocket:
                String IP = m_etInputIP.getText().toString();
                int Port = Integer.parseInt(m_etInputPort.getText().toString());

                m_pdIsLoading.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                m_pdIsLoading.setMessage("Loading..");
                m_pdIsLoading.show();

                m_hSocketManager = new SocketManager(IP, Port, m_Handler);
                break;

            case R.id.button_Send:
                m_hSocketManager.sendData(m_etSendData.getText().toString());
                break;
        }
    }
}


안드로이드에서는 아이스크림 샌드위치 버전부터(아마 맞음) 소켓 생성같은 네트워킹 작업들을 메인 UI 스레드에서는


못하도록 해놓았다. 그래서 SocketManager에서 별도의 스레드를 생성하여 네트워킹을 관리하며 메인 스레드에서는 핸들러를 통한


화면 갱신만을 하고 있다. 여기서 ProgressDialog는 흔히 "로딩중"을 표시하는 객체로



요 화면이다. 소켓 생성이 끝나면 핸들러를 통해 메시지를 받아 ProgressDialog를 제거한다.


SocketManager.java

package com.example.hyunseo.testingapp; import android.os.Handler; import android.os.Message; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.util.Iterator; public class SocketManager { private String IP; private int Port; private SocketChannel m_hSocketChannel; private Selector m_hSelector; private readDataThread m_readData; private sendDataThread m_sendData; private Handler m_handler; public SocketManager(String ip, int port, Handler h) { this.IP = ip; this.Port = port; this.m_handler = h; // thread objects의 작업 할당 및 초기화 m_readData = new readDataThread(); m_readData.start(); } // m_createSocket thread 안에서 실행 private void setSocket(String ip, int port) throws IOException { // selector 생성 m_hSelector = Selector.open(); // 채널 생성 m_hSocketChannel = SocketChannel.open(new InetSocketAddress(ip, port)); // 논블로킹 모드 설정 m_hSocketChannel.configureBlocking(false); // 소켓 채널을 selector에 등록 m_hSocketChannel.register(m_hSelector, SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITE); } public void sendData(String data) { m_sendData = new sendDataThread(m_hSocketChannel, data); m_sendData.start(); } private void read(SelectionKey key) throws Exception { // SelectionKey로부터 소켓채널을 얻어온다. SocketChannel sc = (SocketChannel) key.channel(); // ByteBuffer를 생성한다. ByteBuffer buffer = ByteBuffer.allocate(1024); int read = 0; // 요청한 클라이언트의 소켓채널로부터 데이터를 읽어들인다. read = sc.read(buffer); buffer.flip(); String data = new String(); CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); data = decoder.decode(buffer).toString(); // 메시지 얻어오기 Message msg = m_handler.obtainMessage(); // 메시지 ID 설정 msg.what = 1; // 메시지 정보 설정3 (Object 형식) msg.obj = data; m_handler.sendMessage(msg); // 버퍼 메모리를 해제한다. clearBuffer(buffer); } private void clearBuffer(ByteBuffer buffer) { if (buffer != null) { buffer.clear(); buffer = null; } } /*********** inner thread classes **************/ public class sendDataThread extends Thread { private SocketChannel sdt_hSocketChannel; private String data; public sendDataThread(SocketChannel sc, String d) { sdt_hSocketChannel = sc; data = d; } public void run() { try { // 데이터 전송. sdt_hSocketChannel.write(ByteBuffer.wrap(data.getBytes())); } catch (Exception e1) { } } } public class readDataThread extends Thread { public readDataThread() { } public void run() { try { setSocket(IP, Port); } catch (IOException e) { e.printStackTrace(); } // 소켓 생성 완료를 메인UI스레드에 알림. m_handler.obtainMessage(); m_handler.sendEmptyMessage(0); // 데이터 읽기 시작. try { while(true) { // 셀렉터의 select() 메소드로 준비된 이벤트가 있는지 확인한다. m_hSelector.select(); // 셀렉터의 SelectoedSet에 저장된 준비된 이벤트들(SelectionKey)을 하나씩 처리한다. Iterator it = m_hSelector.selectedKeys().iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey) it.next(); if (key.isReadable()) { // 이미 연결된 클라이언트가 메시지를 보낸경우... try { read(key); } catch (Exception e) { } } // 이미 처리한 이벤트므로 반드시 삭제해준다. it.remove(); } } } catch (Exception e) { } } } }


아주 중구난방이다.


기본 소켓은 블럭킹 방식이기 때문에  SocketChannel을 이용하여 논블럭킹 방식으로 소켓을 생성하였다.



크게 중요한 스레드 객체가 두개있다. 소켓 생성 후 소켓들이 등록된(클라이언트라 하나밖에 음슴) Selector를 돌며


읽을 수 있는 상태의 소켓이 있으면 데이터를 읽는 readDataThread와 단순히 서버로 데이터를 보내는 sendDataThread이다.


readDataThread에서 수신 가능한 소켓이 있으면 read() 메소드를 호출하여 핸들러를 통해 UI 스레드로 보내고


UI 스레드에서 Toast를 이용하여 화면에 띄운다.





728x90
728x90


엡실론 핵발전소는 저쪽에 있습니다.

마을 아래 오른쪽으로 가면

배가 한 척이 있습니다.



발전소 공사 현장.. 어 잠깐만. 너 놉캣이지?



널 발전소로 데려가라는 오더가 있었어.

근데 너 상꼬맹이구나. 켈린대장은 무슨 생각이지?



불청객 등장.



오 지져스, 이제 꼬맹이 두명?

이 사항에 대해 보스한테 무전좀 때려야겠는데...



여보세요? 여기 7번도로에 문제가 있는데요.



여기 꼬맹이 둘이 발전소로 가겠다고 하고 있어요. 한명은 레인져들이 알려준 애같고.



다른 한명은 정열적인 불꽃머리를 하고 있는데. 마치 보스 머리 같아요. (띠오 아빠인듯.)



보스께서 너희 둘다 데려오라 하신다.

보스가 무슨생각을 하시는지 모르겠지만 명령을 받았으니.



워우! 여기가 아빠가 일하는 곳?



여긴 애들 있을 곳이 아닌데...

보스는 무슨 생각이신지.



띠오 아빠 등장

너희 둘다 도착했구나. 따라오렴.



아빠!



확인해보세요. 놉캣이랑 저 이제 어엿한 트레이너에요!

배지도 두개있음!



그래그래

너가 자랑스럽구나 띠오.



네게 여행을 보낸것이 옳은 선택이었던 같구나.



그래, 놉캣. 켈린리 그러던데 내게 줄게 있다며?



(편지를 본 뒤)

그가 무슨 생각인지 모르지만 대장의 결정을 존중해야지.



놉캣 네 아버지는 네가 폐발전소를 보기를 원하신다.



너도 알다시피 십년전 사고가 발생했었지.

네가 네 어머니를 잃은 곳이고.



나는 그녀를 불렀지만 그녀는 사라져 버렸어.



듣기 힘들다는거 안다.

그래도 발전소를 보는 것은 네게 매우 필요한 마무리(?)를 줄거다.



근데 아주 위험하단다.



방사능은 모두 제거되었는데도 포켓몬들에게 뭐가가 있어.... (중략)



너 버려진 발전소를 탐험하는 거지 놉캣?

멋진데? 나도 갈래!!!! (중략)



그치만 아빠~~~!

놉캣은 되는데 왜 저는?

(넌 주인공이 아니니까.)



삶은 항상 공정하지 않다 띠오. 내가 선장한테 널 육지로 데려가라고 얘기하마.



놉캣 너에 대해선 섬을 조사할수 있게 내 권한을 줬다.



폐발전소는 남쪽이다. 조심해햐 한다.



회복이 필요하면 휴게소에 기계가 있단다.

몇몇 직원들과 포켓몬 트레이너들도 있다.



아마 너랑 배틀을 원할 수 있어.

그리고 특히 야생 포켓몬을 조심해라.



그 야생포켓몬들은 뭔가 달라. 너도 알게 될거다.

네가 여기에 온 목적을 찾기를 바란다.



여기 야생 포켓몬들 보통이 아냐 조심해.

이거 받아 유용할 거야.



개이득!

밑으로 가죠.



이 섬은 애들 놀이터가 아냐!




여기 포켓몬들 뭔일 있는지 뭐 좀 알아냈슈?




진화! 샤미드랑 쥬피썬더 합친 느낌이네요.



내 포켓몬 훈련점.




여기가. 옛 발전소다.



내가 여기 조사하러 얼마나 많이 왔는지 말도 못할 정도다.



수년 전 일어난 모든 일들을 설명할 뭔가를 찾으려 했지.

(중략)



곧 부술거다. 그래서 이번이 무슨 일이 있었는지 밝혀낼 마지막 기회다.



비록 나는 더 찾을게 있다고는 믿지 않지만..

(중략)



내 추적기를 주마. 무슨 일이 생기고 네가 다치면...



우리가 갈거야. 약속하지. 내 생각에



안으로 가기전에 회복도 시키고 보급품도 챙겨가는게 좋겠구나.

안에서 뭘 발견할 지 모르니.


당당히 들어갑시다.



타버린 노트 득!



레포트 1



........................ 주륵 읽을 수 없어.


편의상 여기를 "1층"이라 하겠습니다.



여긴 2층. 바로 위 문으로 ㄱㄱ



여긴 3층 왼쪽 위로 갑시다.



레포트 2



역시..............................


다시 3층에서 위로 갑시다.



레포트 3!



역시나.


여기서 왼쪽 아래 통로는 2층의 가운데 문으로 통합니다.

먼저 오른쪽 위로 긔긔



아이템 득! 2층 왼쪽 문 열쇠입니다.



레포트 4!




2층 왼쪽 문으로 가면 나오는 3층입니다. 위에서 문 열고 들어 갑시다.



레포트 5



선택지가 있는데 암거나 해도 됩니다.



침입자 죽인다. 부순다. 파.괴.한.다.



스파크 두방짜리.



방사선은 당신을 어지럽게 만든다.



눈을 뜨기 힘겹다.



<취이익>

놉캣? 놉캣? 들리니?

(중략)



놉캣 괜찮니? 우리가 가서 네 상태가 나쁜것을 발견했다.



네 아버지에게 네가 발견한 것을 전할 수 있겠다.

(중략)


다시 Bealbeach City로 갑시다.



막상 가니까 별 얘기 안합니다.


바로 체육관으로 갑시다.



트러블을 준비해!! .... 내 말은.. 배틀 하자!




넌 타이타닉처럼 가라앉을 거야! (인성의 상태가?)




물타입 포켓몬 있니?




너 허접이구나!




이따가 잉어킹 구운거나 먹어야지.

(포켓몬 세계관에선 역시 포켓몬을 먹습니다. ㄷㄷ)




막다른 길이지롱!



즐기고 있니? 난 칼리 빌비치 시티 체육관 관장이다!



딱 봐도 바닷가 포켓몬 쓸것 같지? 짧은 얘기 하나 해주지.



안전요원이 없을 때 수영하러 가는게 좋겠다고 생각했지.



근데 갑자기 해류가 내 발을 쓸어버렸어!

난 내가 익사할 거라고 생각했지.



그런데 그때 내 밑에 뭔가가 느껴졌어!

그건 바로 Brainoar (뇌고기 최종진화형) 였어 그리고는 날 안전한 곳으로 옮겨줬지.



그때부터 난 포켓몬 트레이너와 안전요원이 되기로 결심했어.




오늘은 여기까지 끝!!

728x90

+ Recent posts