우선순위

종류 

연산자 

연산 방향 

 1

 1차 연산자

( ) [ ] . -> 

-> 

 2

 단항 연산자

 - + + - - ~ ! * & sizeof (type) 

<-

 3, 4

 산술 연산자

 * / % + -

 -> 

 5

 비트 이동 연산자

 << >>

 -> 

 6

 관계 연산자

 < <= > >=

 -> 

 7

 동등 연산자

== != 

 -> 

 8, 9, 10

 비트 논리 연산자 

& ^ | 

 -> 

 11, 12

 논리 연산자 

&& || 

 -> 

 13

 조건 연산자 

? : 

  -> 

 14

 대입 연산자 

= += -= *= /= %= &= ^= |= <<= >>= 

 <-

 15

 콤마 연산자 

 -> 

(연산자의 종류)



1. 연산자와 피연산자


- 프로그램은 CPU가 처리하는 명령들의 묶음이다. 일반적으로 연산자는 컴파일되면 명령어로 바뀌므로 연산자를 배우는 것은 결국 명령을 익히는것이다. 이든 연산 명령에는 연산의 대상이 되는 데이터가 필요한데 이를 피연산자라고 한다.


++a    10%3    (a>10)&&(a<20)


연산자는 보통 기능에 따라 분류되지만 피연사자의 개수에 따라 나눌수도 있다.

피연산자가 하나면 단항연산자, 2개면 이항연산자, 3개명 삼항연산자





2. 산술 연산자와 대입 연산자


- 산술 연산자는 +, -, *, /, %가 있는데 모두 2개의 피연산자를 사용한다. 이중 -연산자는 피연산자를 하나만 사용하여 그 부호를 바꾸는 연산자로도 쓰인다.


a = 10;    sum= a+b;


대입연산자 오른쪽의 변수는 값의 변화가 없고 왼쪽의 변수는 값이 바뀐다.


일반적으로 a와b같은 변수는 데이터를 저장하는 메모리(ram)에 존재하는 반면 연산은 CPU에서 이뤄진다. 연산할 때는 메모리에 있는 변수의 값을 CPU로 복사하여 사용하므로 아무리 많은 ㅇ녀산을 수행해도 피연산자 a, b값은 변하지 않는다. 반면 대입 연산을 수행한 sum, sub, mul, dnv의 값은 변하게 된다.





3. 나눗셈 연산자와 나머지 연산자


- 나눗셈에는 / 연산자를 사용한다. 나눗셈 연산자를 사용할 때는 피연산자의 형태에 따라 결과가 달라지니 주의해야한다.


나눗셈 연산자는 정수로 연산할 때 몫을 구하고 실수로 연산할 때는 소수점까지 구한다.


나눗셈 연산자의 이런 특징은 수학적 나눗셈을 기대한 경우 활당한 결과가 되겠지만 몫을 구할 때에는 유용하게 쓰인다. 반면에 몫은 뺀 나머지만을 구할 때는 나머지 연산자인 %기호를 사용한다. 실수 연산에는 나머지 개념이 없으므로 나머지 연산자의 피연산자로는 반드시 정수만을 사용한다.





4. 증감 연산자


증감 연산자는 단항 연산자를 피연산자의 값을 1증가시키거나 1감소시킨다.

증가시킬 때는 '++' 연산자를, 감소시킬 때는 '--' 연산자를 사용한다. 증감 연산자는 하나의 연산자로 대인 연산까지 수행하므로 변수의 값을 1씩 증가시키거나 감소시킬때 쉽게 표현할수 있다.


++a --a


증감 연산자는 특이하게도 ++a 뿐만 아니라 a++ 형태와 같이 사용할수있따. 피연산자의 앞에 놓이면 전위형, 뒤에 놓이면 후위형이라고 한다.


증감 연산식이 단독으로 사용된 경우엔 전위형과 후위형의 결과는 같다. 그러나 다른 연산자와 함께 쓰이면 다른 연산의 결과에 영향을 미친다. 

증감 연산자의 후위형은 다른 연산자와 함께 사용될 때 가장 나중에 연산된다.





5. 관계 연산자


관계 연산자에는 대소 관계 연산자와 동등 관계 연산자가 있다.


대소 관계는 < 또는 > 등의 기호를 사용하고, 동등 관계는 ==나 != 기호를 사용한다. 이들 연산자는 모두 피연산자를 2개 사용하며, 연산의 결과값은 1또는 0이 된다. 컴파일러는 0은거짓으로 -이 아닌 값은 참으로 판단하기에 관계식을 실행 조건 검사에 사용할수있다. 



관계 연산의 결과는 주로 판단의 근거로 사용된다.


예를 들어 a와 b값이 무엇인지 모르지만 둘 중에 큰값을 택한다면 a>b 연산을 수행항 ㅕ결곽밧을 확인하면 된다. 결과가 1이면 a가 b보다 크므로 a를 택하고 결과가 0이면 b가 크거나 최소한 같으므로 b를 택하면 된다. 





6. 논리 연산자


논리 연산자는 &&, ||, ! 세개이다.


&& 연산자는 2개의 피연산자가 모두 참일 때만 연산결과가 참이 되고 || 연산자는 둘 중에 하나라도 참이면 참이 된다. ! 연산자는 피연산자를 하나 사용하여 그 참과 거짓을 바꿀때 사용한다. 

'C' 카테고리의 다른 글

c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터의 크기와 데이터 타입  (0) 2018.07.14
C언어 포인터 기초 개념  (0) 2018.07.14
블로그 이미지

루우지

포인터의 크기는 애플리케이션의 호환성과 다른 환경으로의 이식 가능성을 고민할때 문제가 된다. 최근에 널리 사용되는 대부분의 운영체제 환경에서 포인터의 크기는 일반적으로 포인터 타입에 상관없다. 예를 들면 char에대한 포인터는 구조체에 대한 포이터와 크기가 같다. C표준에서 모든 데이터 타입에 대한 포인터의 크기가 같아야 한다고 명시하고 있지는 않지만, 일반적으로 포인터의 크기는 동일하다. 하지만 함수에 대한 포인터와 데이터에 대한 포인터의 크기는 다를 수도 있다.



포인터의 크기는 사용하는 장비와 컴파일러에 따라 다르다. 예를 들어 윈도우버전에서 포인터의 크기는 32 또는 64bit이며 오래된 dos 그리고 왼도우 3.1 os에서 포인터는 16또는 32비트다.




1. 메모리 모델

- 64bit 컴퓨터 도입으로 C기본 데이터 타입의 메모리 크기 차이가 활실해졌다. 컴퓨터와 컴파일러들은 c 기본 데이터 타입의 메모리 할당을 위해 저 마다 다른 옵션의 메모리 모델을 가지고 있다. 이 메모리 모델들을 설명하기 위해 일반적으로 아래와 같은 표기법을 사용한다.



I In L Ln LL LLn P Pn



각 대문자는 정수(Integer), 롱(Long), 포인터(Pointer)에 해당하며, 각 소문자는 타입에 할당된 비트 수다. 


 C Data Type

LP64 

ILP64 

LLP64 

ILP32 

LP32 

char 

short

16 

16 

16 

16 

16 

__int32 

 

32 

 

 

 

int 

32 

64 

32 

32 

16 

long 

64 

64 

32 

32 

32 

long long 

 

 

64 

 

 

pointer 

64 

64 

64 

32 

32 




2. 사전 정의된 포인터 관련 데이터 타입

- 포인터를 다룰 때 다음 네가지의 사전 정의된 데이터 타입이 종종 사용된다


1) size_t -> 안전한 크기 타입 제공을 위해 사용

2) ptrdiff_t -> 포인터 연산을 처리하기 위해 사용

3) intptr->t와 uintptr_t -> 포인터 주소를 저장하기 위해 사용



3. size_t 타입에 이해

- size_t 타입은 c언어에서 임의의 객체가 가질 수 있는 최대 크기를 나타낸다. 크기를 표현하는데 음수의 사용은 의미가 없기 때문에 size_t는 부호 없느 ㄴ정수를 사용한다. size_t 타입을 사용하은 이유는 시스템에서 주소 지정이 가능한 메로리 영역과 일치하는 크기를 선언하는 이식 가능한 방법을 제공하기 위해서이다. 


malloc 함수와 strlen 함수가 size_t타입을 반환하는 인자로 사용되는 대표적인 함수이다. 일반적으로 32 system에서는 32bit, 64 system에서는 64bit이다. 



4. 포인터와 sizeof연산자 사용하기

- 포인터의 크기를 확인하는데 sizeof 연산자를 사용할 수 있다. 아래 코드는 char 포인터에 대한 크기를 출력한다.


printf("Size of *char: %d", sizeof(char*));


결과:

size of *char: 4


함수 포인터의 크기는 다를 수 있다. 보통의 주어진 환경의 운영체제와 컴파일러 조합에따라 결정된다 많은 컴파일러는 32비트나 64비트 애플리케이션 생성을 지원하며 같은 프로그램도 컴파일 옵션에 따라 다른 포인터 크기를 가진다. 



5. intptr_t uintptr_t 사용하기

- intptr_t와 uintptr_t 타입은 포인터의 주소를 저장하는데 사용된다. 이 두 타입은 다른 환경으로 이식이 가능하고 안전한 포인터 선언 방법을 제공하며, 시스템 내부에서 사용하는 포인터와 같은 크기다. 포인터를 정수 표현으로 변환할때 유용하게 사용할수 있다. 


uintptr_t타입은 intptr_t 타입의 부호 없는 버전에 해당된ㄷ. 대부분의 연산에서 intptr_t 타입을 사용하는 것이 좋으며, uintptr_t 타입은 intptr_t 타입만큼 유연하지 않고 사용이 제한적이다. 아래는 intptr_t 타입의 사용 방법을 보여준다.


int num;

intptr_t *pi = &num;


다음의 코드를 실행하면 문법 에러가 발생한다.


uintptr_t *pu = &num;


error: invalid converstio nfrom 'int*' to 'uintptr_t* {aka unsigned int*}'


위의 에러는 타입이 맞지 않아 생기는 에러이다. 캐스팅을 해주면 정상 할당된다.


uintptr_t *pu = (uinttpr_t *)&num;

'C' 카테고리의 다른 글

c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터의 크기와 데이터 타입  (0) 2018.07.14
C언어 포인터 기초 개념  (0) 2018.07.14
블로그 이미지

루우지

포인터는 C언어 전반에 많은 영향을 미치며 언어 자체에 많은 유연함을 제공한다. 포인터는 동적 메모리 할당의 매우 중요한 부부이기도 하며, 배열의 표기법과 밀접한 관련이 있다. 


오랫동안 포인터는 C언어 학습에 걸림돌이 되었는데 포인터의 기본적이 개념은 매우 간단하다. 포인터는 메모리 위치의 주소를 저장하는 변수이다. 하지만 포인터 연산자를 사용하고, 암호와도 같은 난해한 표기법을 식별하려고 할 때부터 이 개념이 급격하게 복잡해진다. 포인터를 이해하는 열쇠는 C프로그램에서 메모리가 어떻게 관리되는지 이해하는데 있다. 포인터는 메모리의 주소를 담고 있기 때문에 메모리가 구성되고 관리되는 방법을 이해하지 못한다면 포인터의 동작 방식을 이해하기 쉽지 않다. 그렇기에 메모리의 구성 방법을 확실하게 이해하고 나면 포인터를 이해가기가 쉽다.


1. 포인터와 메모리

컴파일된 C 프로그램은 다음의 세 종류의 메모리를 사용한다.



1) 정적/전역

 - 정적(STATIC)으로 선언된 변수들은 정적/전역 메모리에 할당된다. 전역(GLOBAL)변수들 또한 같은 미모레이 할당된다. 정적/전역 변수들은 프로그램이 시작될 때 할당되며 프로그램이 종료될 때까지 메모리 공간에 남아 있다. 모든 함수에서 접그할 수 있는 전역 변수와는 달리 정적 변수의 접근 범위는 해당 변수를 선언한 함수로 제한된다.


2) 자동/로컬

 - 자동 변수는 함수 안에서 선언되고 함수가 호출될 때 생성된다. 자동 변수의 접근 범위는 선언된 함수로 제한되며, 함수가 호출되는 동안에만 존재한다. 일반적으로 블록문 안ㅅ에서 선언된 변수의 범위는 해당 블록을 ㅗ제한된다.


3) 동적

 - 동적메모리는 힙메모리 영역에 할당되고 필요한 경우 해제된다. 포인터를 사용하여 할당된 메모리 영역을 참조하며, 포인터의 의해 접근이 제한된다. 메모리를 해제하지 않는 한 메모리에 존재한다.


 

접근 범위 

수명 

전역(GLOBAL) 

전체파일 

어플리케이션 실행 동안 유지 

정적(STATIC)

선언된 함수 

어플리케이션 실행 동안 유지 

자동(LOCAL) 

선언된 함수 

함수 실행 동안 유지 

동적(DYNAMIC) 

참조하는 포인터 

메모리 해제 전까지 유지 



포인터 변수는 다른 병수 및 객체 또는 함수의 메모리상 주소를 포함하고 있다. 여기서 객채란, malloc 함수 같은 메모리 할당 함삼수를 사용하여 할당된 메모리를 말한다. 포인터는 일반적으로 문자 타입 포인터와 같이 포인터가 가리킬 대상에서 따라 특정 타입을 가지도록 선언되며 정수, 문자, 문자열 또는 구조체와 같은 어떠한 c 데이터 타입도 될 수 있다. 그러나 포인터 자체는 포인터가 참조하는 데이터 타입의 속성을 가지지 않는다. 포인터는 단지 주소만을 가지고 있다.




2. 포인터를 잘 알아야 하는 이유

  • 빠르고 효율적인 코드 작성
  • 다양한 문제에 대한 효과적인 해결 방법 제공
  • 동적 메모리 할당 지원
  • 작고 간결한 표현의 사용
  • 큰 오버헤드 없이 데이터 구조를 포인터로 전달
  • 함수의 매게변수로 전달된 데이터 보호



3. 포인터를 사용할때 발생할 수 있는 문제점

  • 배열이나 데이터 구조의 경계를 넘는 접근
  • 소멸한 자동/로컬 변수의 참조
  • 할당 해제된 힙 메모리의 참조
  • 아직 할당되지 않은 포인터에 대한 역참조


4. 포인터 선언하기
- 포인터 변수는 데이터 타입과 별표 그리고 변수 이름을 순서대로 나열하여 선언한다. 

 int num;
 int *pi;


별표는 변수를 포인터로 선언하는 데도 사용되지만 두 수를 곱하거나 포인터를 역참조하는데 사용 된다. 포인터를 선언하는데 있어 다음과 같은 사항을 기억 해둬야한다. 초기화 하지 않은 메모리에 대한 포인터는 문제가 될 수 있다. 그런 포인터를 역참조하면 포인터가 유효한 주소를 가리지 않을 것이고, 설령 유효한 주소를 가리킨다 하더라도 그 주오세는 유효한 데이터가 없을 가능성이 높다. 유효하지 않은 주소라는 것은 프로그램의 접근이 허용되지 않는 주소를 의미한다. 이러한 유효하지 않은 주소에 대한 접근은 프로그램의 비정상적 종료의 원인이 되는데 수많은 심각한 문제를 일으킬수 있다.
  • pi에는 정수 변수의 주소가 할당되어야 한다.
  • 이 변수들은 초기화되지 않았으므로 쓰레기값을 가진다.
  • 포인터 구현 자체에는 포인터가 어떤 종류의 데이터를 참조하는지 포인터의 내용이 유효한지를 추측할 만한 내용이 포함되어 있지 않다.
  • 그러나 포인터의 타입은 지정되어 있고 포인터가 올바르게 사용되지 않으면 컴파일러는 오류를 표시한다.



5. 포인터 선언을 읽는 방법

 - 포인터 선언을 이해하기 쉽게 읽는 방법의 비결은 뒤에서부터 읽는 것이다. 


 const int *pci;


1) const int *pci;    -> 포인터 변수 pci

2) const int *pci;    -> 정수를 가리키는 포인터 변수 pci

3) const int *pci;    -> 상수 정수를 가리키는 포인터 변수 pci



6. 주소 연산자

- 주소 연산자(&)는 변수의 주소를 반환한다. 아래와 같이 주소연산자를 사용하여 pi 포인터를 num 변수의 주소로 초기화할 수 있다.

 

 int num = 0;

 int *pi = &num;


num변수가 100번지에 위치하고 있다면 포인터 변수 pi가 가리키는 주소는 100번지가 된다.


 num = 0

 pi = num


위와 같은 코드를 컴파일 할경우 컴파일러는 'error: invalid conversion from 'int' to 'int*'와 같은 에러 메세지를 반환할것이다.

변수 pi는 정수형 포인터 변수이고 num은 정수형 변수이다. 결과적으로 num 이갖고 있는 0이라는 값을 정수형 포인터 변수에 초기화 하려고 하는 과정에서 에러가 난다. 두 변수는 같은 크기의 바이트를 사용하지만(4byte) 같은 데이터 타입이 아니다. 그러나 포인터로 캐스팅하는 것은 가능하다.


 pi = (int *)num;


컴파일 하면 오류는 생성되지 않는다. 다만 포인터 변수가 가리키는 0번지는 대부분 운영체제에서 프로그램이 사용할 수 있는 유요한 주소가 아니다. 그러기에 비정상 종료가 될것이다.



7. 포인터값 출력하기

- 위 예제에서 사용한 변수들은 쉬운 설명을 위해 100과 104 같은 주소를 사용하고 있다고 쳤지만 아래의 코드를 실행해보면 다음 과 같은 결과를 볼 수 있다.


int main()

{

int num = 0;

int *pi = &num;


printf("address of num: %d value: %d\n", &num, num);

printf("address of pi: %d value: %d\n", &pi, pi);


    return 0;

}


결과 : 

address of num: 7339564 value: 0

address of pi: 7339552 value: 7339564



8. 간접지정 연산자로 포인터 역참조하기

- 간접지정 연산자(*)는 포인터 변수가 가리키는 위치의 값을 반환하며 '참조 연산자로 포인터 값의 참조'는 종종 포인터 역참조라 불린다. 다음 예제는 변수 num과 pi가 선언되고 초기화 되었다.


 int num = 5;

 int *pi =&num;


그리고 다음 코드에서 변수 num의 값 5를 출력하기 위해 간접지정 연산자를 사용한다.


 printf("%d\n", *pi);


그리고 또한 좌변값에(lvalue)에 참조 연산자의 결과를 사용할 수 있다. 좌변값은 할당 연사자의 좌측에 위치한 피연산자를 말한다. 

 

 *pi = 200;

 printf("%d", num) // 200 출력


포인터 변수 pi가 가리키는 정수(num)에 200을 할당한다. pi는 변수 num을 가리키고 있으며, 200이 변수 num에 할당된다.



9. 함수 포인터

- 포인터는 함수를 가리키도록 선언될 수 있다. 함수포인터 선언은 일반적인 포인터 표기법에 비해 다소 복잡하다. 


 void (*foo)();


이름이 foo이며 매개변수와 반환값이 없는 함수포인터 선언방법을 보여준다.



10. void 포인터

- void 포인터는 어떤 타입의 데이터도 참조할 수 있는 범용 포인터다. 아래의 void 포인터 선언의 예제가 있다.

 

 void *pv;


void 포인터 선언에 두가지 흥미로운 점

  • void 포인터는 char 포인터와 같은 표현과 메모리 정렬 방법을 사용한다.
  • void 포인터는 다른 포인터와 절대 같지 않다. 하지만 NULL 값이 할당된 두 개의 void 포인터는 서로 같다. void 포인터의 실제 동작은 시스템에 따라 다른다.

모든 포인터는 void 포인터에 할당될 수 있으며, 할당된 포인터는 다시 원래의 타입으로 캐스팅하여 사용할 수있다. 원래의 타입으로 캐스팅이일어나면 포인터의 값은 기존 값과 같게된다. 


int main()

{

int num;

int *pi = &num;

printf("value of pi:pp", pi);

void* pv = pi;

pi = (int *)pv;

printf("value of pi:pp", pi);


return 0;

}


결과: 

value of pi:006FF874
value of pi:006FF874

void 포인터는 데이터 타입의 포인터에 사용되며, 함수 포인터에는 사용되지 않는다. void 포인터에 sizeof 연산자가 사용될 수있다. 그러나 void 자체에는 sizeof 연산자를 사용할 수 없다.

size_t size = sizeof(void*);  // 유효함
size_t size = sizeof(void)    // 유효하지 않음



'C' 카테고리의 다른 글

c언어 배열  (0) 2018.07.22
C언어 산술, 관계, 논리연산자  (0) 2018.07.22
C언어 포인터의 크기와 데이터 타입  (0) 2018.07.14
C언어 포인터 기초 개념  (0) 2018.07.14
블로그 이미지

루우지

2018년도 기사 실기 2회차 기사퍼스트 가답안입니다. 


* 알고리즘 (배점 25점)

1. 순서도 (배점 10점)

- 이차원배열 ㄹ: 16년3회 기사기출문제 100% 동일, 16년3회 기출풀이 강의

- C, K, R, TR, -1

 

2. C언어 (배점 5점)

1부터 10까지 숫자들에 대한 약수 구하기, C언어 강의

i % j == 0

 

3. C언어 (배점 4점)

- 5개 입력 주어지고 홀수 갯수 출력, C언어 강의

- !=

 

3. 자바언어 (배점 6점)

- 배열 크기, 자바 강의

- 3, 5

 

* 데이터베이스 (배점 25점)

1. SQL-DCL (배점 10점) 4강

commit, rollback, grant, revoke, cascade

 

2. SQL-속성 추가 (배점 4점) 4강

- alter, add

 

3. 스키마 (배점 3점) 1강

- 내부, 개념, 외부

 

4. 연산자 기호 표시 (배점 8점) 3강

- 합집합 (∪)
- 교집합 (∩)
- 차집합 (-)
- 카티션 프로덕트 (X)
- 셀렉트 (σ)
- 프로젝트 (π)
- 조인 (▷◁)
- 디비젼 (÷)

 

* 신기술 (배점 25점)

1. 용어 (배점 15점) 1강, 시스템 관리 1강, 업무 2강

포렌식

- DDOS: 영문 4글자로 입력 

- 킬스위치 (kill switch): 분실한 정보기기를 원격으로 조작해 개인 데이터를 삭제하고 사용을 막는 일종의 자폭 기능

- Zero day attack(제로데이 공격): 15년3회 산업기사 기출, 2강

BCP

 

2. 용어 (배점 10점) 전산영어 1강, 시스템 관리 2강

- 지그비(zigbee): 집에서 전등을 끄고 켜고 자동으로...

- SAN(storage area network): NAS 와 달리 서버 옆에 있는 고속 네트워크로 서버 간에 정보 교환 가능..서로 다른 저장 장치  

- 크라우드소싱(crowd sourcing): 군중과 아웃소싱의 합성어

- IPV6

- 커넥티드 (스마트 ): 인터넷이 가능한 자동차

 

* 업무프로세스 (배점 15점)

1. 지문속답 & 용어: 07년4회 기사기출문제 (1강)

- AS-IS, 사무국, 서면평가 

 

* 전산영어 (배점 10점)

- GIS: geographic information ... (신기술 기사기출, 시스템 관리2강)
- DHCP: 다이나믹 IP, 아이피 자동 할당 서버이름.... 4글자로 입력
- KERNEL: 시스템 중심..6글자로 입력. (기출, 1강)
- BLUETOOTH (산업기사 기출용어집)
- ALPHAGO: 이세돌 바둑 (시스템 관리1강)



알고리즘      25점

데이터베이스  21점

신기술        14점

업무프로세스  15점

전산영어        2점

총합         77점




몇번의 낙방끝에 가답안 채점을 해보니 합격한거같습니다.

회사다니며 실제로 공부한 시간은 20시간 내외 인데 시험이 너무 쉽게 나왔습니다.

몇번 떨어진 경험치가 축적된건진 모르겠지만 이번엔 서술형이 전혀 없었고 문제 난이도도 최하였던거같네요.



신기술도 나온 문제를 보면 다 한번씩 출제됬던 문제들이 태반이였던점을 보면 이번에는 합격률을 높이고 싶었나 봅니다.

전공자분들이라면 알고리즘은 간단하게 공부하시고

데이터베이스는 깊이있게 공부하시고 신기술과 업무프로세스는 단어암기 위주로 하시는게 효율적인거 같습니다.




아침 7시에 일어날때 시험을 봐야하나 말아야하나 진지하게 고민했는데

오늘 안갔더라면 정말 후회할뻔했네요 ..ㅋㅋ ~_~






블로그 이미지

루우지

OSI 7계층, TCP/IP 4계층

C++ 2018.05.17 17:02

소켓  프로그램


프로토콜별 계층 구조

국제표준화기구인 ISO에서는 서로 다른 긱종의 컴퓨터 간에 이루어지는 통신을 위해 OSI 7계층을 규정하고 있다. TCP/I 프로토콜에서는 TCP/IP 4계층 구조를 규정하고 있다. 이와 같이 계층별로 역할을 나누어서 규정하는 이융는 상호 접속에 필요한 통신 규약을 정의하고, 유사 기능을 갖는 모듈을 동일 계층으로 묶기 위해서이다.


OSI 7계층 구조나 TCP/IP 4계층 구조를 설명하기에 앞서 프로토콜 계층 구조를 이해하기 쉽도록 일상의 예를 가지고 설명하고자 한다. 다른 지역의 친구에게 편지를 보내는 경우이다.


1. 사용자 계층

- 사용자 S는 친구에게 보낼 편지를 작성하고 봉투에 넣는다. 겉면에 보내는  사람과 받는 사람의 주소를 적은 후 우편함에 넣는다.

- 이 과정에서 사용자 S는 편지가 어딜 경유해서 목적지에 도착하고, 친구에게 어떻게 전달되는지 알지 못해도된다. 우체국이 역할을 대행해주기 때문


2. 우체국 계층

- 우체국에서는 편지함의 편지를 수거해서 목적지별로 분류하고, 해당 편지를 최종 목적지로 전달하기 위해 다음 경유지를 결정하낟. 예를 들어 사용자 S에게 사용자 D로 편지를 보내는 경우를 살펴보면 사용자 S에서 보낸 우편물은 S눙체국에서 우편물을 분류한다. 그런 다음 목적지에 따라 다음 경유지를 결정하는데 최종 목적지가 D 인 경우 우편물의 다음 경유지는 C1 우체국으로 결정된다.

- 최종 목적지가 D인 우편 봉투에 발신지 S와 다음 경유지 C1을 적은 딱지를 추가로 붙여서 우편 수송차로 보낸다.


3. 우편 수송자 계층

- 우편 수송차에서는 우편 봉투에 무착된 딱지의 착신지, 즉 다음 경유지를 보고 이동을 해서 다음  경유지로 우편물을 전달한다.

- 이 과정에서 최초 발신지가 어딘지, 최종 목적지가 어디인지는 알필요가 없다. 

- 단지 우편 봉투에 추가로 붙은 딱지 정보만을 보고 우편물을 전달한다.


4. 우체국 계층

- 우편물을 받은 C1 우체국에서는 우편 봉투와 발신과 수신정보를 보고 또다심 다음 경유지를 결정한다.


5. 우편 수송차 계층

- 우편봉투에 추가로 붙이는 딱지에는 발신지로 C1과 착신지로 D를 적어 우편 수송차로 보낸다.


6. 우체국 계층

- 우체국 D는 우편 수송차 계층을 통해 우체국 C1에서 받은 우편물을 ㅇ루편봉투의 발신과 수신 정보를 비교한다. 우편물을 사용자에게 전달할 준비를 한다.


7 사용자 계층

- 사용자 D는 사용자 S가 보낸 편지를 최종 우체국인 D를 통해 전달받는다.      


이와 같은 루틴이 통신망에서도 OSI 7 Layer 와 TCP/IP 4 Layer 등의 프로토콜 구조를 제공한다.




OSI 7계층과 TCP/IP 4계층 비교

우편물을 보내는 예에서는 계층을 3개로 나누었다. 그런데 ISO에서는 7개의 수직적인 계층으로 나누고 다른 계층과는 무관하게 저마다 독립적인 기능을 지원하도록 하였다.  OSI 7계층은  최상위 계층인 응용 계층으로 부터 시작해서 표현 계층, 세션계층, 전송 계층, 네트워크 계층, 데이터 링크 계층, 물리 계층으로 구분된다. 가 곅층마다 고유한 서비스를 제공하고 이를 위한 프로토콜이 존재한다.


1. 응용 계층(Application Layer)

- 응용 프로세스를 네트워크에 연결할 수 있게 해서 자료를 송수신할 수 있는 창구를 제공한다. 사용자가 이메일을 전송하고나 웹 브라우저를 통해 웹 서 버에 연결하면 해당 서비스는 응용 계층에서 smtp, pop3, http 등의 프로토콜을 이용해 서비스한다.


2. 표현 계층(Presentation Layer)

-통신하는 컴퓨터간의 데이터 표현의 차이를 해결하기 위해 자료의 형식을 변환해 주거나 공통의 형식을 제공해주는 계층이다. 아스키 코드와 ebcdic 코드의 변환, 그래픽 정보나 영상 정보를 jpeg나 mpeg등으로 변환해서 전송하는 기능을 수행한다. 또한 네트워크 보안을 위해 자료를 암호화해서 전송하고 수시니측에서는 이를 해동하는 기능도 수행한다. 효율적으로 전송하기 위해 자 료를 압축하고 압축을 푸는 기능도 수행한다.


3. 세션 계층(Session Layer)

- 응용 계층 사이에 연결을 설정하고, 유지하고 종료하는 기능을 수행한다. 이를 위해 전송 계층으로 전송할 자료의 순서를 결정하고, 자료의 점검이나 복구를 위해 동기 위치등을 지정한다.


4. 전송 계층(Transport Layer)

- 통신하는 컴퓨터간에 자료를 전송하는 계층이다. 송신측에서는 전송할 데이터를 패킷으로 분할한다. 수신측에서는 분할된 패킷을 다시 조합해서 본래의 자 료로 만들고 상위 계층으로 전달한다. 자료가 수신측에 올바르게 전송될 수 있도록 보장하는 기증도 수행한다.


5.  네트워크 계층(Network Layer)

- 라우팅 프로토콜을 이요해서 최적의 전송 경로를 선택해서 이를 통해 자료를 전송하도록 한다. 이를 위해 IP주소와 같은 논리 주소 체계와, 지리적으로 떨어져 있는 네트워크상의 두 컴퓨터 사이에 최종 목적지까지 정확하게 연결되도록 하는 연결성을 제공한다.


6. 데이터링크 계층(Data Link Layer)

- 물리적인 전송 링크를 통해 자료를 안전하게 전송하는 계층이다. 전송 자료의 비트들을 프레임이라는 놀리 단위로 구성해서 최종 목적지까지 전송하기 위해 인접한 컴퓨터로 자료를 안전하게 전송한다.


7. 물리 계층 (Physical Layer)

- 컴퓨터로 서로 연결하는 물리적인 링크의 활성화/비활성화, 링크 상태를 유지하기 위해 물리적인 링크의 전기적, 기계적, 규약적, 기능적 명세를 정의한다.

- osiy 7계층에 비해 TCP/IP 4계층은 미국 국방성이 개발한 알파넷의 통신 프로토콜이다.

                                                                                                                                                                                                                                                                                                                                         

TCP/IP 4계층에서는 좀더 단순하게 응용계층, 전송 계층, 인터넷 계층 네트워크 액세스 계층으로 규정하고 있다.


1. 응용 계층

- 응용 계층은 OSI 7계층에서 세션, 표현, 응용 계층에 해당한다. 텔넷, FTP, SMTP 등과 같은 TCP와 UDP기반의 응용 프로그램을 구현할 때 사용한다.


2. 전송 계층

- 전송 계층은 SOI 7 계층에서 전송 계층에 해당하며 통신 노드 간의 연결을 제어하고 자료의 송수신을 담당한다. 프로토콜로는 스트림 형태의 연결형 서비스인 TCP와 데이터그램 형태의 비연결형 UDP가 있다.


3. 인터넷 계층

- 인터넷 계층은 OSI 7 계층에서 네트워크 계층에 해당한다. 통신 노드 간의 IP 패킷을 전송하는 기능과 라우팅 기능을 담당한다. 프로토콜로는 IP, ICMP, A RP, RARP가 있다. IP는 데이터그램 방식의 비연결형 서비스만을 제공한다.


4. 네트워크 액세스 계층

- 네트워크 액세스 계층은 OSI 7계층에서 물리 계층과 데이터링크 계층에 해당한다. LAN, X.25, 패킷망, 위성 통신, 다이얼업 모뎀 등에 사용된다. 특히 이더넷에서는 CSMA/CD MAC프로토콜을 사용하며 IEEE 802.3 MAC 표준으로 규정되어 있다.


이렇게 구분된 통신 프로토콜 계층 구조를 기반으로 하여 프로그램으로 각 계층별 자료를 제어하면서 통신할 수 있는 것이다. 소켓 프로그램은 전송 계층에서 제공하는 통신 함수를 호출해서 자료를 송수신한다.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  

블로그 이미지

루우지

가상함수


가상함수는 virtual 예약어를 앞에 붙여서 선언한 메서드를 말한다. 따라서 가상 함수라는 말은 메서드라는 말을 내포하여, 이 가상 함수는 기본적으로 '자기 부정'을 전제로 작동한다. 달리 말해 파생 형식 에서 메서드를 재정의하면 가거의 정의가 완전히 무시된다는 특징이 있다. java와 비교하자면 자식클래스에서의 부모클래스의 함수를 오버라이드 하는 개념으로 이해했다. 하지만 재정의 이후에 호출하는 과정에서는 자바와는 조금 다른 방식을 취한다.


가상함수는 다음과 같이 선언한다.

virtual void PrintData();


// #include "stdafx.h" #include

using namespace std; class CMyData { public: virtual void PrintData() { cout << "CMyData: " << m_nData << endl; } void TestFunc() { cout << "***TestFunc()***" << endl; PrintData(); cout << "****************" << endl; } protected: int m_nData = 10; }; class CMyDataEx : public CMyData { public: virtual void PrintData() { cout << "CMyDataEx: " << m_nData * 2 << endl; } }; int main() { CMyDataEx a; a.PrintData(); CMyData &b = a; b.PrintData(); a.TestFunc(); }


위의 예제에서 CMyData::PrintData() 함수를 가상 함수로 선언했는데, 이 함수는 내부 멤버 데이터인 m_nData의 값을 출력한다. 그리고 32번 행에서 이 PrintData() 함수를 재정의했다. 여기까지는 기존의 일반 베서드를 재정의한 것과 크게 다르지 않기 때문에 호출하게 되면 CMyDataEx 클래스의 PrintData() 메서드를 호출한게 된다.



CMyData  &b = a;

b.PrintData();

재밌는 코드가 등장하게 되는데 실형식은 CMyDataEx이며 참조 형식은 기본 클래스인 CMyData에 속한 참조자 b가 선언됐다. 그리고 b를 통해 PrintData()를 호출했습니다. 일반 메서드의 경우 실 형식은 중요하지 않고 참조 형식이 무엇인지에 따라 어떤 메 서드가 호출되는지 결정이 났지만 가상함수는 다르다. 가상 함수는 일반 메서와 달리 참조 형식이 무엇이든 실 형식의 메서드를 호출한다.


"일반 메서드는 참조 형식을 따르고, 가상함수는 실 형식을 따른다.



	
void TestFunc()
{
		cout << "***TestFunc()***" << endl;

		PrintData();
		cout << "****************" << endl;
}


PrintData(); 는 누구를 호출할까? TestFunc() 함수는 기본 클래스인 CMyData의 멤버이다. 그러므로 위의 PrintData()는 기본적으로 CMyData 클래스의 PrintData() 함수를 말하는 것이다. 그런데 문제는 PrintData() 함수가 '가상 함수'라는 사실이다. 이렇게 되면 지금 PrintData() 함수는 '미래'에 재정의된 함수일 수 있다.

만일 파생형식(자식)에서 PrintData() 가상 함수를 재정의한다면 위의 PrintData()는 미래의 함수를 호출하는 것이다.


가상함수


가상함수는 virtual 예약어를 앞에 붙여서 선언한 메서드를 말한다. 따라서 가상 함수라는 말은 메서드라는 말을 내포하여, 이 가상 함수는 기본적으로 '자기 부정'을 전제로 작동한다. 달리 말해 파생 형식 에서 메서드를 재정의하면 가거의 정의가 완전히 무시된다는 특징이 있다. java와 비교하자면 자식클래스에서의 부모클래스의 함수를 오버라이드 하는 개념으로 이해했다. 하지만 재정의 이후에 호출하는 과정에서는 자바와는 조금 다른 방식을 취한다.


가상함수는 다음과 같이 선언한다.

virtual void PrintData();


// #include "stdafx.h" #include

using namespace std; class CMyData { public: virtual void PrintData() { cout << "CMyData: " << m_nData << endl; } void TestFunc() { cout << "***TestFunc()***" << endl; PrintData(); cout << "****************" << endl; } protected: int m_nData = 10; }; class CMyDataEx : public CMyData { public: virtual void PrintData() { cout << "CMyDataEx: " << m_nData * 2 << endl; } }; int main() { CMyDataEx a; a.PrintData(); CMyData &b = a; b.PrintData(); a.TestFunc(); }


위의 예제에서 CMyData::PrintData() 함수를 가상 함수로 선언했는데, 이 함수는 내부 멤버 데이터인 m_nData의 값을 출력한다. 그리고 32번 행에서 이 PrintData() 함수를 재정의했다. 여기까지는 기존의 일반 베서드를 재정의한 것과 크게 다르지 않기 때문에 호출하게 되면 CMyDataEx 클래스의 PrintData() 메서드를 호출한게 된다.



CMyData  &b = a;

b.PrintData();

재밌는 코드가 등장하게 되는데 실형식은 CMyDataEx이며 참조 형식은 기본 클래스인 CMyData에 속한 참조자 b가 선언됐다. 그리고 b를 통해 PrintData()를 호출했습니다. 일반 메서드의 경우 실 형식은 중요하지 않고 참조 형식이 무엇인지에 따라 어떤 메 서드가 호출되는지 결정이 났지만 가상함수는 다르다. 가상 함수는 일반 메서와 달리 참조 형식이 무엇이든 실 형식의 메서드를 호출한다.


"일반 메서드는 참조 형식을 따르고, 가상함수는 실 형식을 따른다.



	
void TestFunc()
{
		cout << "***TestFunc()***" << endl;

		PrintData();
		cout << "****************" << endl;
}


PrintData(); 는 누구를 호출할까? TestFunc() 함수는 기본 클래스인 CMyData의 멤버이다. 그러므로 위의 PrintData()는 기본적으로 CMyData 클래스의 PrintData() 함수를 말하는 것이다. 그런데 문제는 PrintData() 함수가 '가상 함수'라는 사실이다. 이렇게 되면 지금 PrintData() 함수는 '미래'에 재정의된 함수일 수 있다.

만일 파생형식(자식)에서 PrintData() 가상 함수를 재정의한다면 위의 PrintData()는 미래의 함수를 호출하는 것이다.



------------- 실행 화면 -------------


CMyDataEx: 20

CMyDataEx: 20

***TestFunc()***

CMyDataEx: 20

****************

계속하려면 아무 키나 누르십시오 . . .

블로그 이미지

루우지

[C++] Template 템플릿

C++ 2018.05.13 17:50

클래스 템플릿

- '클래스 템플릿'은 클래스를 찍어내는 모양자라고 생각하면 이해하기 쉽다. 그리고 이 모양자에 구멍이 뚫려 있는 부분은 '자료형'이다. 클래스 템플릿은 다음과 같인 선언할 수 있다.


template<typename T>

class 클래스명 {

.....

}


함수 템플릿처럼 선언에 앞서 template 예약어와 tpyename 예약어를 적어 넣어야한다. 그러나 함수 템플릿과 달리 인스턴스를 선언할떄는 typename을 반드시 적어야 한다.


template<typename T>

class MyData

{

public(T param) : m_Data(param) { }

T GetData() const { return m_Data; }


operator T() { return m_Data; }

void SetData(T param) { m_Data = param; }


private :

// T 형식의 멤버 변수 선언

T m_Data;


]


int _tmain(int argc_ TCJAR* argv[])

{

CMyData<int> a(5);

cout << a << endl;

CMyData<double> b(123.45);

cout << b << endl;

CMyData<char*> c("Hello");

cout << c << endl;


return 0;

}


이러한 예제 소스가 있을때 굵게 칠해진 행을 보면 클래스 템플릿으로 인스턴스를 선언할때 각각에 맞는 자료형을 정의했다 .이렇게 하면 int, double, char*에 맞는 클래스를 컴파일러가 찍어낸다. 참고로 찍어서만드어진 클래스를 '템플릿 클래스'라고 한다. 

Template 이라는 단어는 모형자라는 의미를 가진단어로, C++에서 템플릿은 어떤 제품을 만들어내는 틀, 예를 들어 붕어빵에 비교해 보자면, 붕어빵을 만들어 내는 틀을 템플릿이라 말 할 수 있다. 템플릿의 특징은 기능은 이미 결정되어 있지만, 데이터 타입은 결정되어 있지 않는다는 특징을 가지고 있다. 생성된 세 템플릿 클래스는 모두 동일하게 변환 생성자, 형변환 연산자, SetData() 메서드를 가진다. 


템플릿 매개변수

템플릿을 선언할 때 다음과 같이 형식을 여러개 작성할 수도 있습니다.

Template<Typename T, tpyename T2>


그런데 여러 형 식 중 일부는 다음과 같이 형식을 구체적으로 작성해도 상관없다.

Template<Typename T, int nSize>




tmeplate<typename T, int nSize>

class CMyArray{

    ......

}


int _tmaint(int argc, _TCJAR* argv[])

{

CMyArray<int, r> arr;

arr[0] = 10;

arr[1] = 20;

arr[3] = 30;

...

}


tmeplate<typename T, int nSize>라고 선언했고 CMyArray<int, 3> arr; 라고 선언했다. 이렇게 함으로 arr 요소의 개수는 3이된다. 여기서 재밌는 사실은 템플릿 매개변수는 클래스 템플릿 내부에서도 모두 접근할 수 있다는 점이다. 




템플릿 특수화

- 템플릿을 사용하면 자료형에 관계없이 프로그램을 만들 수 있따. 그러나 간혹 특별한 형식이 있을 경우 나머지 다른 형식들과 전혀 다른 코드를 적용해야 할 때가 있씁니다. 가장 대표적인 예인 '포인터'는 일반적인 형식들과 달리 간접 지정 연산을 실행해야하는 경우가 있다. 또한 문자열에 '덧셈'을 적용할 경우 일반 형식과 전혀 다른 코드를 작성해야 한다.



함수 템플릿 특수화


template<typename T>

T Add(T a,T b) {

return a + b;

}


template<>

char* Add(char *pszLeft, char *pszRight)

{

int nLenLeft = strlen(pszLeft);

int nLenRight = strlen(pszRight);

char *pszResult = new char[nLenLeft + nLenRight = 1];


strcpy_s(pszResult, nLenLeft + 1, pszLeft);

strcpy_s(pszResult + nLenLeft, nLenRight + 1, pszRight);



return pszResult;

}


int _tmain(int argc, _TCHAR* argv[])

{

int nResult = Add<int>(3, 4);

cout << nResult << endl;


char *pszResult = Add<char*>("hello", "world");

cout << pszResult << endl;

delete [] pszResult


return 0;

}


위으 소스는 함수 템플릿인 Add()를 두가지 형태로 정의한 것이다. 여기서 두가지라고 한 것은 문자열을 더하는 경우와 나머지 모든 자료형을 위한 경우로 나눈 것이다. 한마디로 문자열을 특별히 분리해서 (특수화해서) 처리하는 것이다.

int nResult = Add<int>(3, 4); 호출한 Add() 템플릿 함수는 첫번째 함수에서 만들어지고 char *pszResult = Add<char*>("hello", "world"); 는 두번째 add 함수로 생성된다.


템플릿을 특수화할 때 typename을 아무것도 기술하지 않았다. 마치 함수를 다중 정의하듯 함수 템플릿을 여러 번 정의하는 경우 컴팡일러는 이를 


"특정 형식은 개발자인 내가 직접 정의할 테니 별도로 생성하지 말라"


는 것으로 인식한다. 다만 이와 같이 묵시적 의미가 전달되려면 두 매개 변수와 변환 형식이 모두 같아야한다. 


블로그 이미지

루우지

일반적으로 소켓 프로그램은 서비스를 요청하는 클라이언트측과  클라이언트로부터의요청을 받아 서비스하는 서버측, 이렇게 두 곳에 상주하는 프로그램으로 구성된다. 우리가 구현할 소켓 스포르갬은 c언어로 작성했던 한 줄 짜리 문자열 출력 프로그램에 네트워크 기능을 부여하여 호스트사이세서 서로 통신하며 자료를 송수신할 수 있게 한것이다. 즉 클라이언트 프로그램이 네트워크상에서 통신 채널을 통해 서버측에 연결되면 서버 프로그램은 즉시 문자열 hello world를 클라이언트에게 전송하고 클라이언트 프로그램은 전송받은 문자열을 화면에 출력한다.


일반 프로그램에서는 문자열  출력이 한 컴퓨터에서 이루어졌던 것에 비해 소켓프로그램에서는 네트워크상에 있는 호스트 간에 문자열을 받아 출력하기 때문에 소켓과 같은 네트워크 연결 장치가 필요하다. 그리고 호스트 간에 서로 연결된 이후에는 소켓을 통해문자열을 송수신하느 코그다 있어야한다.


1. 연결 요청: 클라이언트 프로그램은 소켓 API 한수를 호출하여 서버 프로그램에 연결을 요청한다.

2. 문자열 전송 : 연결 요청을 받은 서버 프로그램은 클라이언트 프로그램과 연결되자마자 문자열 HELLO, WORLD를 클라이언트에게 전송한다.

3. 화면 출력 : 클라이언트 프로그램은 전송받은  문자열을 자신의 화면에 출력한다.


1번과 3번 과정은 클라이언트가 수행하는 기능이고 서버는 2번을 수행한다.




Windows 기반 MFC Socket 프로그램 구현

MS 윈도우 기반의 소켓 프로그램은 Win32 API 함수를 이용해서 개발하거나 MFC LIBㄹ을 이용해서 개발한다. MFC 라이브러리를 이용해보겠다.


MFC는 소켓과 관련해서 두 개의 클래스를 제공한다.

CObject

-> CAsyncSocket

-> CSocket


CObject 클래스에 상속된 CAsyncSocket 클래스는 비동기 소켓을 지원한다. 여기서 비동기 소켓이란 송수신 함수 등을 호출할 때호출하자마자 바로반환하고 다음 코드를 실행하는 소켓을 말한다. -> 비동기


CSocket은 CAsyncSocet 클래스를 상속한 클래스로 동기 소켓을 지원한다. 비동기 소켓과 달리 동기 소켓은 송수신 함수 등을 호출할 때 함수가 내부 코드를 모두 수행할때까지 반환핮지 않고 기다린다. -> 동기



클라이언트 프로그램

프로젝트명 : HelloClient

대화상자 기반, 고급기능에서 windows 소켓 체크


GUI 설계


컨트롤 

ID 

변수 

Caption 

Static Text 

IDC_STATIC_STATUS 

m_static_status 

서버가 보내준 자료를 출력합니다. 

Button Control 

IDC_BUTTON_CONNECT 

 

연결 




HelloClientDlg.h

#pragma once #include "afxwin.h" // CmfcsocketDlg dialog class CmfcsocketDlg : public CDialogEx { // Construction public: CmfcsocketDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_MFCSOCKET_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnStnClickedStaticStatus(); CStatic m_static_status; public: afx_msg void OnBnClickedButtonConnect(); };

mfc 마법사를 통해 스태틱 컨트롤과 버튼 컨트롤에 멤버변수와 헨들러함수를 등록한다.






HelloClientDlg.cpp

void CmfcsocketDlg::OnBnClickedButtonConnect()

{

// TODO: Add your control notification handler code here

CSocket socket;

socket.Create();

socket.Connect(_T("127.0.0.1"), 9000);


int cbRcvd;

char buffer[1024];

CString strBuffer = _T("");


if ((cbRcvd = socket.Receive(buffer, 1024)) > 0) {

strBuffer = (LPCSTR)(LPSTR)buffer;

//strBuffer.Format("%s", buffer);

m_static_status.SetWindowText(strBuffer.Left(cbRcvd));

}

socket.Close();

}

등록한 버튼 클릭 함수를 구현한다.


위의 연결 버튼을 클릭하게 되면 OnBnClickedButtonConnect() 가 실행된다. 소켓 프로그램을 위해 CSocket 클래스의 socket 개개체를 생성해서 서버와 연결하고 서버로부터 문자열을 받아온다.


1. socket.create() 함수를 호출해서 소켓을 생성하고

2. socket.connect() 함수를 호출해서 IP주소와 포트를 통해 연결을 시도한다. ( 지금은 로컬호스트 주소 )

3. socket.receive() 함수를 호출해서 서버에서 전달한 문자열을 수신한다.





서버 프로그램

프로젝트명 : HelloServer

대화상자 기반, 고급기능에서 windows 소켓 체크


GUI 설계


 컨트롤

ID 

변수 

CAPTION 

Static Text 

IDC_STATIC_STATUS 

m_static_status 

Server Stop 

Button Control

IDC_BUTTON_START  

m_button_start 

시작 




1. CListenSocket 클래스 생성 (base CSockt 상속)

- 클라이언트의 연결 요청을 받으면 OnAccept 메세지 처리기가 실행되어 연결 요청을 처리한다.


ListenSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CListenSocket : public CSocket { public: CListenSocket(CHelloServerDlg* pHelloServerDlg); ~CListenSocket(); CHelloServerDlg* m_pHelloServerDlg; public: virtual void OnAccept(int nErrorCode); // 속성창을 이용하면 자동으로 함수 등록가능 };

등록한 버튼 클릭 함수를 구현한다.



ListenSocket.cpp

#include "stdafx.h" #include "ListenSocket.h" #include "HelloServerDlg.h" CListenSocket::CListenSocket(CHelloServerDlg* pHelloServerDlg) { m_pHelloServerDlg = pHelloServerDlg; } CListenSocket::~CListenSocket() { } void CListenSocket::OnAccept(int nErrorCode) { // TODO: Add your specialized code here and/or call the base class m_pHelloServerDlg->ProcessAccept(); CSocket::OnAccept(nErrorCode); }

생성자 파라메터를 바꿔주고 OnAccept() 함수를 통해 인자로 받은 dlg 변수의 주소를 m_pHelloServerDlg 변수에 담는다.

클라이언트로부터 연결 요청이 오면 메시지 처리기 OnAccept가 호출된다. OnAccept 메세지 처리기에서는 연결 처리를 직접 하지 않고 이를 대화 상자 CHelloServerDlg 객체에서 처리하도록 CHelloServerDlg 객체의 메서드 ProcessAccept를 호출한다. 





2. CServiceSocket 클래스 생성 (base CSockt 상속)

- 클라이언트의 연결 요청을 받아들이고 해당 클라이언트에 대한 처리를 수행할 CServiceSocket객체를 생성





ServiceSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CServiceSocket : public CSocket { public: CServiceSocket(CHelloServerDlg* pHelloServerDlg); ~CServiceSocket(); CHelloServerDlg* m_pHelloServerDlg; };

리슨소켓과 마찬가지로 메인 다이얼로그를 참조하기위해 변수선언 및 생성자 인자 수정을 해준다.




ServiceSocket.cpp

#include "stdafx.h" #include "ServiceSocket.h" #include "HelloServerDlg.h" CServiceSocket::CServiceSocket(CHelloServerDlg* pHelloServerDlg) { m_pHelloServerDlg = pHelloServerDlg; } CServiceSocket::~CServiceSocket() { }

ServiceSocket.h

#pragma once #include "afxsock.h" class CHelloServerDlg; class CServiceSocket : public CSocket { public: CServiceSocket(CHelloServerDlg* pHelloServerDlg); ~CServiceSocket(); CHelloServerDlg* m_pHelloServerDlg; };

리슨소켓과 마찬가지로 메인 다이얼로그를 참조하기위해 변수선언 및 생성자 인자 수정을 해준다.




HelloServerDlg.cpp

#pragma once #include "afxwin.h" #include "ListenSocket.h" #include "ServiceSocket.h" // CHelloServerDlg dialog class CHelloServerDlg : public CDialogEx { // Construction public: CHelloServerDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_HELLOSERVER_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: CStatic m_static_status; CButton m_button_start; public: CListenSocket* m_pListenSocket; CServiceSocket* m_pServiceSocket; void ProcessAccept(); afx_msg void OnBnClickedButtonsStart(); };

헤더파일에 구현에 필요한 변수와 함수들을 선언해준다. 

OnBnClickButtonStart는 이벤트 처리 마법사를 이용하여 등록한다.



ServiceSocket.cpp

void CHelloServerDlg::ProcessAccept() { m_pServiceSocket = new CServiceSocket(this); char sndBuffer[] = "Hello, world"; if (m_pListenSocket->Accept(*m_pServiceSocket)) { m_static_status.SetWindowText(_T("Acceepted")); m_pServiceSocket->Send(sndBuffer, (int)strlen(sndBuffer)); m_static_status.SetWindowText(_T("Send Hello, World")); } else { delete m_pServiceSocket; } } void CHelloServerDlg::OnBnClickedButtonsStart() { // TODO: Add your control notification handler code here UINT nPort = 9000; m_pListenSocket = new CListenSocket(this); m_static_status.SetWindowText(_T("Create Listen Socket")); if (!m_pListenSocket->Create(nPort)) { m_static_status.SetWindowText(_T("Cannot Create Socket")); return; } else { m_static_status.SetWindowText(_T("Socket Create success")); if (!m_pListenSocket->Listen()) { m_static_status.SetWindowText(_T("can not listen")); return; } } }

위의 두개의 함수를 구현한다.


시작 버튼을 누르게 되면 CListenSocket 객체를 생성하고 CListenSocket 객체의 Create 메서드를 호출해서 클라이언트로부터 연결 요청을 받을 듣기 소켓을 생성한다. 그런 다음 CListenSockt 객체의 Listen 메서드를 호출해서 클라이언트로부터 연결 요청을 받을 수있는 LISTEN 상태가 된다.


클라이언트로부터 연결 요청이 외면 CListenSocket 개개체의 OnAccept 메서드가 실행된다. CListenSocket 객체의 OnAccept 메서드에서 클라이언트와의 연결처리를 수행하지 않고, 대화 상자 CHelloServerDlg 객체에서 일괄 처리할 수 있도록 CHelloServerDlg 객체의 ProcessAccept 메서드를 호출한다. CHelloServerDlg 객체의 ProcessAceept 메서드에서는 클라이언트와 연결하여 자료를 송수신할 CServiceSocket 객체를 생성하고, CListenSocket 객체의 Accept 메서드를 호출해서 CServiceSocket 객체와 클라이언트를 연결한다. 그런다음 CServicesOCKET 객체의 Send 메서드를 통해 즉시 클라이언트로 문자열 hello, word를 전송한다.




블로그 이미지

루우지

TCP/IP는 대표적인 광역 네트워크 프로토콜이다. 미국의 군사 네트워크에서 활용했떤 프로토콜이였으나 이제는 전세계로 묶는 네트워크 프로토콜로 발전했다. 


웹브라우져를 통해 URL이나 IP주소를 입력하면 해당 사이트에 접속하여 웹 페이지가 열린다. 너무나 간단해 보이지만 여기에는 아주 복잡한 네트워킹 과정이 담겨 있다. 웹 통신 프로토콜인 HTTP 프로토콜은 TCP/IP 프로토콜을 기반으로 만들어져있다.



IP주소

전세계를 연결하는 TCP/IP 네트워크에 접속하려면 IP주소를 하나 갖고 있어야한다. 이주소는 친구의 집 주소와 같은 형식은 아니지만 전 세계에서 중복되지 않는 유일한 숫자(IPV4 체계에서는 32비트 숮자)이다. 따라서 이 주소만 알면 주소지 컴퓨터로 정보를 전달할 수있다. 물론 상대뿐만 아니라 나도 유일한 IP주소를 가져야 한다.


친구에게 물건을 택배로 보낸다느 가정하에 먼저 할 일은 보낼물건을 포장하고 친구의 집 주소를 송장의 받는 사람 주소에 쓰게된다. 이렇게 송장에는 반드시 받는 사람과 받는사람의 주소를 함께 적는다. 이렇게 하면 보낼 준비는 끝난 것이다. 그 다음 택배회사에 전화를 할 것이고, 택배회사에서 물건을 가지러 오면 택배기사에게 물건을 전단하고 수령한 물건을 택배회사에서는 배달지에 따라 분류해서 지역별 집겨지로 보낸다. 그런 다음 택배기사는 배달지에 도착해서 받는 사람 주소에 친구가 정말 사는지 확인하고 물건을 전달한다. 


정보를 전달하는건 ISP회사(KT,SKT,LG)가 담당한다. 따라서 인터넷을 통해 정보를 보내려면 ISP에게서 회선을 임대한 후 인터넷으로 접속하는 유일한 IP주소를할당받고 상대 주소로 정보를 전송하거나 역으로 수신받는다.

최종적으로 정보를 수신하는 호스트의 운영체제는 어떤 프로세스가 정보를 수신해야 하는지 확인하여 처리를 완료한다.


인터넷 프로토콜 버전4는 32비트 주소체계를 갖고 있다. 32비트는 네 개의 8비트로 재구성되며, 각각의 값은 10진수로 표시하고 구분점으로 나누어진다. 

자신의 아이피 주소를 확인하려면 CMD에서 IPCONFIG 명령을 사용하면 확인 가능하다.


IP주소는 네트워크 ID와 호스트 ID로 나누어지는데 네트워크 ID는 네트워크를 식별하는 주소(서울시 종로구 종로동), 호스트 아이디는(X번지) 해당 네트워크에 속한 컴퓨터 주소이다.  네트워크 프로그래밍에서는 호스트라는 말을 사용하는데 IP주소를 갖는 장치가 반드시 PC나 서버 같은 컴퓨터만이 아니라 다양한 네트워크 장비도 될 수 있기 때문이다.



SOCKET

일반적으로 네트워크 프로그래밍이라 하면 TCP/IP SOCKET 프로그래밍을 의미한다. 바꿔서 말하면 소켓 프로그래밍 곧 네트워크 프로그래밍이라 할 수 있다. 그런데 여기서 말하는 소켓은 기존의 파일 개념과 유사하다. 일반적으로 파일이라 함은 보조기억장치에 젖아된 데이터를 말하기 마련인데, 좀더 정확히 말하자면 보조기억장치를 추상화한것이다.


하드디스크가 내부적으로 작동되는 원리르 모르더라도 파일을 열고 쓰고 닫을 수 있따면 우리는 하드시크를 다룬다고 할 수 있습니다. 마찬가지로 보통 LAN카드라고 부르는 네트워크 카드도 하나의 파일로 추상화가 가능하다. 이렇게 추상화된 파일에 정보를 저장하면 하드디스크에 저장되지 않고 네트워크 카드를 통해 외부로 전달된다.


파일이 장치를 추상화한 것이라고 가정할 때만 일 대상 장치가 네트워크 카드이면 파일이라는 말 대신에 소켓이라고 한다. 그러므로 소켓 프로맹이라는 것은 네트워크 카드를 추상화한 파일 포인터를 다루는 일로 정리된다. 그래서 우리가 파일에 대해 알고 있는 프로그래밍 지식 대부분이 소켓 프로그래밍에서 적용된다.


파일의 정보를 읽거나 쓰려면 파일을 열어야 하는것처럼 소켓도 핸들을 열어서 입출력을 하게된다. 물론 사용한후에 핸들을 닫아야 한다. 여기서 조금 전에 설명한 TCP/IP에 대한 기초 지식을 결합하면 소켓에 대한 정의가 끝난다



이후에 MFC를 이용한 TCP/IP 소켓프로그래밍 채팅프로그램을 생성해보겠다.

블로그 이미지

루우지

프로그래밍에서 동기화가 필요한 경우는 매우 다양하다.

컴퓨터에서 자원이라 함은 주로 cpu나 메모리를 의미하는데 연산에 직접적으로 관여하는 부품을 말한다. 주변 정보기기와 통신하기 위한 인터럽트나 I/O 번지도 굳이 따지면 자원으로 분류된다. 그런데 이런 자원은 늘 제한되는 특성을 갖는다. 반면에 시스템은 여러 프로세스를 동시에 실행하여 운영하게 되면서, 각각의 프로세스는 저마다의 코드에 따라 특정 자원을 점유하려는 시도를 끊임없이 하게 된다. 이런 시도를 조정할 관리 시스템이 없다면 프로세스간의 충돌이 있을것이다.


지금부터 나오는 내용은 이런 구조적인 문제를 해결하기 위해 운영체제에 도입된 객체를 활용하는 방법이다.

운영체제가 제공하는 동기화용 커널객체에는 뮤텍스, 세마포어, 이벤트 등이 있다. 사용자 모드 동기화 객체로는 크리티컬 색션이 있다.




크리티컬 섹션(Critical Section)


- 한 시스탬 내에서 여러 스레드가 실행 중이라고 하더라도 실제로 CPU를 점유하여 연산을 하는 스레드는 하나이다. 코어가 두개라면 두 스레드가, 네 개라면 네 스레드가 동시에 실행 중일수 있다. 여러 스레드는 운영체제가 정하는 스케쥴에 따라 컨텍스트 스위칭을 하면서 실행된다는 것이 더 중요하다. 


- 크리티컬 섹션 객체로 보호하는 대상은 주로 전역 객체이다. 그중에서도 메모리와 관련된 대상은 반드시 그렇게 해야 멀티스레드 환경에서 문제가 되지 않는다.




뮤텍스와 데드락(Mutex Dead-lock)


- CMutex 클래스는 커널 뮤텍스를 객체화한 MFC클래스이다. 스레드와 프로세스를 동기화 시키는데도 사용한다. 일반적으로 스레드를 동기화 시킬때에는 뮤텍스가 아니라 크리티컬 섹션을 사용할 것을 권장한다. 이유는 뮤텍스가 동기화를 하는데 드는 비용이 크리티컬 섹션에 비해 크기 때문이다. 그렇다고 체감하기는 어렵지만 효율적으로 프로그래밍 하려면 고려해야하는 영역이다.


- CCriticalSection class나 CMutex class 모두 CSyncObject class의 파생 클래스인데 이들 클래스가 Lock()과 Unlock()메서드를 가상 함수로 정의한다. 

- CCriticalSection 에서는 인자값으로 주어지는 시간이 무시 되지만 CMutex 클래스에서는 이값이 적용되는데 여기서 시간은 멀티스레드 환겨에서 특정 스레드의 코드가 안전하게 주어지면 100mx 동안 다른 스레드의 저근이 차단되고, 100ms 이후에는 Unlock() 함수를 호출하지 않아도 자동으로 Lock() 함수가 풀리도록 한다. 이와 같은 기능이 필요한 이유는 데드락을 방지하기 위해서이다.


- 데드락은 스레드간의 서로 특정 자원을 점유한 상태에서 Lock() 함수를 호출하여 다른 스레드의 접근을 차단했지만 내부 코드사에서 조건이 맞지 않아서 Unlock() 함수를 호출하지 못하고 모든 스레드의 흐름이 정지된 상태를 말한다.


- Lock()함수에 시간값 인자를 줌으로써 데드락을 피해 특수한 상황에서도 스레드가 특정 코드나 자원으 무한저응로 점유하지 못하게 할수 있는것이다.



세마포어(Semaphore)


- 세마포어는 크리티컬 섹션이나 뮤텍스가 한 번에 한 스레드나 프로세스만 특정 리소스에 접근할 수있었던 것과달리동시에 여러 스레드나 프로세스가 특정 리소스에 접근할수 있도록 임의로 허용치를 정할 수 있다. 만일 10개의 스레드가 동시에 실행되는 멀티스레드 시스템에서 3개의 스레드만 리소스에 동시 접근이 가능하다면 일럴때 적합한 동기화 객체가 바로 세마포어이다.


- 서버 응용 프로그램에서 이런 구조가 절시하게 필요하다. 만일 동시에 최대 1000명까지 처리하는 서버가 있다고가정할대 일반 서비스의 경우에는 1000명 클라이언트 모두에게 서비스가가능하지만 특정 서비스만큼은 동시 접속을 10명까지로 서시브 제한을 두어야할 수도 있다. 이럴 경우에는 세마포어는 최선의 동기화 방법이 된다.


다음에 이벤트 방식의 동기화 방법에 설명하겠다.


블로그 이미지

루우지

티스토리 툴바