본문 바로가기
코스웨어/11년 내장형하드웨어

[내장형]김동화 2011년7월 5일 수업내용

by 알 수 없는 사용자 2011. 7. 5.
728x90
반응형

- 함수 포인터

⇒ 변수나 파일을 포인터 변수를 이용하여 접근하듯이, 함수도 포인터를 이용하여 호출할 수 있다. 함수 포인터(function pointer)는 함수의 실행코드의 시작주소를 가지고 있으며, 함수 포인터를 이용하여 함수를 호출할 수 있다. 배열 이름이 메모리 주소값을 가지는 상수이듯이, 함수 이름도 함수의 시작코드의 주소값을 가지는 주소값 상수로 볼 수 있다.

⇒ 반환자료형 (* 함수 포인터 변수 이름) (인수1, 인수2, ……, 인수 n);


// 함수 포인터 예제
#include<stdio.h>
int(*test(void)) (const char *, ...);
int(*(*test2(void))(void)) (const char *, ...);
int main()
{
        
int (*fp) (const char *, ...)=0// printf를 가리키는 함수 포인터
                                  // 포인터 변수 fp을 0으로 초기화 했다.
        
fp = printf;
        fp(
"Hi!!!\n");
  
        
return 0;
}
int(*test(void)) (const char *, ...)
{
        
return printf;
}
int(*(*test2(void))(void)) (const char *, ...)
{
        
return test;
}

⇒ main() 안에서 int (*fp)(const char *, ...) = 0; 가 선언되었다. 이것은 printf를 가리키는 함수 포인터이다.

int(*test(void)) (const char *, ...) 함수를 살펴보면

 → printf의 원형 int (*) (const char *, ...)에서 (* 다음에 test(void)를 삽입한 모습임을 알 수 있다.

⇒ test와 test2 함수는 위 소스에서 출력과는 관계가 없다.




// 인자를 가진 함수 포인터 문제 풀이

#include <stdio.h>

//int printf(const char *, ...); printf를 반환한다는뜻
//test1(float f);
int(*test1(float f))(const char *, ...);

//test2(char c);
int (*(*test2(char))(float))(const char *, ...)

int main()
{
        
int (*fp)(const char *, ...)=0;
        
//int printf(const char *, ...);

        //fp1 선언
        
int (*(*fp1)(float f))(const char *, ...)=0// 포인트가 1개, 함수 선언이 아니다.

        //fp2 선언
        
int (*(*(*fp2)(char c))(float))(const char *, ...)=0

                                // 포인트가 1개(3개가 아니다.), 함수 선언이 아니다.
        
fp2 = test2;
        fp1 
= fp2('a');
        fp  
= fp1(0.1);

        fp(
"Hi....\n");
        
return 0;
}

int (*test1(float f))(const char *, ...)
{
        printf(
"float : %0.1f\n", f);
        
return printf;
}

int (*(*test2(char c))(float))(const char *, ...)
{
        printf(
"char : %c\n", c);
        
return test1;
}

⇒ test1 함수 선언

 → int printf(const char *, ...);

 → int (*)(const char *, ...);

 → int (* 까지 test1 앞에 복사

 → int (*test1(float f) 다음에 나머지 )(const char *, ...);를 붙여 넣는다.


⇒ fp1 선언

 → int (*test1(float f))(const char *, ...);를 fp1으로 선언하기 위해 붙여넣고.

 → int (*fp)(const char *, ...)=0;와 int printf(const char *, ...); 를 비교해 보면 printf가 (*fp)로 바뀌었음을 알 수 있다.

 → 따라서 int (*test1(float f))(const char *, ...)를 같게 하면

 → int (*(*fp1)(float f))(const char *, ...)=0; 으로 바꾸어 진다.


⇒ test2 함수 선언

 → test1의 타입이 필요하므로 복사 - int (*test1(float f))(const char *, ...)

 → test1을 (*)로 변경 - int (*(*)(float f))(const char *, ...)

 → int (*(* 까지 test2 앞에 복사

 → int (*(*(test2(char c) 다음에 나머지 )(float f))(const char *, ...)를 붙여넣는다.

 → int (*(*(test2(char c))(float))(const char *, ...);가 된다.


⇒ fp2 선언

 → int (*(*(test2(char c))(float))(const char *, ...);를 fp2로 선언하기 위해 붙여넣고.

 → fp1과 같이 int (*(*fp1))(const char *, ...)=0;와 int printf(const char *, ...); 를 비교하면 (*(*(fp2)))가 된다.

 → 따라서 int (*(*(*fp2)(char c))(float))(const char *, ...)=0; 으로 바꾸어진다.



- 다중 포인터 개념

int A = 100;

int *P = &A;

int **PP = &P;

int ***PPP = &PP;

 

⇒ 정리해보면 아래 표와 같다.

 

*

**

***

A → 100(값)

*A → error

**A → error

***A → error

P → 1000(주소)

*P → 100(값)

**P → error

***P → error

PP → 2000(주소)

*PP → 1000(주소)

**PP → 100(값)

***PP → error

PPP → 3000(주소)

*PPP → 2000(주소)

**PPP → 1000(주소)

***PPP → 100(값)



// 다중 포인터 예제
#include<stdio.h>
int main()
{
        
int A = 100;
        
int *P = &A;
        
int **PP = &P;
        
int ***PPP = &PP;

        printf(
"------------------\n");
        printf(
"&A   : %08X\n"&A);
        printf(
"&P   : %08X\n"&P);
        printf(
"&PP  : %08X\n"&PP);
        printf(
"&PPP : %08X\n"&PPP);

        printf(
"------------------\n");
        printf(
"A    : %d\n", A);
        printf(
"P    : %08X\n", P);
        printf(
"PP   : %08X\n", PP);
        printf(
"PPP  : %08X\n", PPP);
 
        printf(
"------------------\n");
        
//printf("*A    : %d\n", *A); 
       //error - 곱셈기호 '*' 로 해석된다.
        
printf("*P    : %d\n", *P);
        printf(
"*PP   : %08X\n", *PP);
        printf(
"*PPP  : %08X\n", *PPP);
  
        printf(
"------------------\n");
        
//printf("**A    : %d\n", **A); - error
        //printf("**P    : %08X\n", **P); - error
        
printf("**PP   : %d\n", **PP);
        printf(
"**PPP  : %08X\n", **PPP);
  
        printf(
"------------------\n");
        
//printf("***A    : %d\n", ***A); - error
        //printf("***P    : %08X\n", ***P); - error
        //printf("***PP   : %08X\n", ***PP); - error
        
printf("***PPP  : %d\n", ***PPP);
  
        
return 0;
}

⇒ 출력 결과         

             

 


- 구조체
#include<stdio.h>
typedef struct
{
        
int A;
        
int B;
        
int C;
}EMB;
int main()
{
        
int array[6];
        EMB *stp;
        
unsigned char *UCP;
        stp 
= (EMB *)array;
        UCP 
= (unsigned char *)array;
  
        *UCP 
= 0x64;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
  
        ++UCP;
        *UCP 
= 0x63;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
  
        ++UCP;
        *UCP 
= 0x62;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
  
        ++UCP;
        *UCP 
= 0x61;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
        ++UCP;
        *UCP 
= 0x00;
  
        printf(
"===========================\n");
        printf(
"stp -> A : %d\n", stp -> A);  
        printf(
"stp -> B : %d\n", stp -> B);  
        printf(
"stp -> C : %d\n", stp -> C);  

        printf(
"==========================\n");
        printf(
"array[0] : %d\n", array[0]);  
        printf(
"array[1] : %d\n", array[1]);  
        printf(
"array[2] : %d\n", array[2]);  

        printf(
"===========================\n");
        stp 
= (EMB *)&array[1];
  
        printf(
"stp -> A : %d\n", stp -> A);  
        printf(
"stp -> B : %d\n", stp -> B);  
        printf(
"stp -> C : %d\n", stp -> C);  

        printf(
"===========================\n");
        UCP 
= (unsigned char *)array;
        ++UCP;
        stp 
= (EMB *)UCP;

        printf(
"stp -> A : %d\n", stp -> A);  
        printf(
"stp -> B : %d\n", stp -> B);  
        printf(
"stp -> C : %d\n", stp -> C);  

        
return 0;
}

⇒ 출력 결과         

             

 

⇒ 메모리 모양 예상

64

00

00

00

63

00

00

00

62

00

00

00

61

00

00

00

⇒ 출력 결과를 통해 stp -> A부터 C까지와 array[0]부터 array[2]까지는 같은 값을 가지는 것을 알 수 있다.

64

00

00

00

63

00

00

00

62

00

00

00

61

00

00

00

⇒ 출력 결과(%08X)         

             

   

⇒ *UCP = 0x64; 를 통해 int array[6]에 1바이트씩 값을 넣고 있는데 처음에는 64 00 00 00 의 순서로 값이 들어간 것을 확인할 수 있다. 단, CPU가 little endian방식을 취하므로 들어가는 것과 출력이 다르다. (%d로 출력하면 100, 99, 98이 출력된다.)

⇒ 세 번째 출력의 stp -> A부터 C까지를 확인해 보면

        stp = (EMB *)&array[1];

   를 통해 A가 array[1]에서 시작되어 C가 array[3]까지 표시된다.

64

00

00

00

63

00

00

00

62

00

00

00

61

00

00

00

 → %d로 출력하면 98, 97, 96이 출력된다.

⇒ 출력 결과(%08X)         

             

 

     UCP = (unsigned char *)array;

        ++UCP;

        stp = (EMB *)UCP;

 → 위의 소스로 1바이트만큼 이동한 UCP가 stp에 대입되고 따라서 stp가 가리키는 값은

64

00

00

00

63

00

00

00

62

00

00

00

61

00

00

00

가 된다.

⇒ 출력 결과(%08X)         

             

 

→ %d로 출력         

             

 



- TCP 프로토콜을 이용산 소켓 프로그래밍

server

clinet

서버에 소켓 생성(client와 연결을 위해 필요한 소켓) - socket()

서버에 주소 할당 - bind()

연결 요청 대기모드로 설정 - listen()

클라이언트 연결 요청시 새로운 소켓 생성 - accept()

송(write(), send()) / 수신(read(), recv())

클라이언트 소켓 닫기 - close()

소켓 생성 - socket()

 

 

 

연결 요청(접속할 서버의 IP, PORT) - connect()

송(write(), send()) / 수신(read(), recv())

소켓 닫기  - close()

        

⇒ TCP → 연결형 프로토콜(전화와 비슷)

        → 두 시스템 간 연결이 성공한 후에 데이터 전송 시작함                 

                

                                

(3-way handshaking)

        → 연결 요청이 왔을 때 위와 같이 3번 이동하며 연결한다.

        

1) 송신 호스트가 SYN Flag값을 1로 설정한 TCP packet과 임의의 Sequence Number를 수신 호스트로 보낸다.

        Ex)  송신  -------------------------->  수신

                        SYN=1, SEQ=J

2) 수신 호스트가 Session성립을 원하면 SYN Flag를 1로 설정하고 ACK를 송신 호스트가 보낸 SEQ번호의 다음 번호로 정하고 수신 호스트에 따로 설정한 SEQ번호를 보낸다.

        Ex)  송신  <--------------------------  수신

                        SYN=1, ACK=J+1, SEQ=K

3) 송신 호스트는 ACK 값을 수신지 호스트가 보낸 SEQ번호의 다음 번호로 정하여 수신 호스트에 보낸다.

        Ex)  송신  -------------------------->  수신

                        ACK=K+1

        → 신뢰성 보장(순차적 데이터 전달, 데이터 재전송)

        → 데이터 경계를 구분하지 않는다.


⇒ UDP → 비연결형 프로토콜(편지와 비슷)

        → 연결 없이 통신 가능

        → 비신뢰적인 데이터 전송(전송순서에 상관없이 가장 빠른 전송 지향, 전송도중 데이터가 손실되어도 재전송 되지 않음.)

        → 데이터의 경계를 구분함(Datagram 서비스)



⇒ 연결형 서버 → TCP 프로토콜을 이용, TCP 소켓을 사용한다.

   비연결형 서버 → UDP 소켓 사용. (연결 요청을 하지 않는다.)

⇒ iterative server → 순서대로 client와 송/수신

                     → client와 서비스 시간이 짧은 경우에 사용한다.

   concurrent server → server와 client 서비스 시간이 불균형 적일 때 사용한다.

⇒ client 연결 요청 간 새로운 소켓 생성(accept())부터 client 소켓 닫기(close()) 까지를 반복시키면 iterative server가 된다.



// helloworld_iterative_server.c에서 연결 요청 수락 부분에 반복문 사용을 통한 iterative server 만들기
     

        // 소켓에 주소 할당

        if( bind(serv_sock, (struct sockaddr*)& serv_addr, sizeof(serv_addr)) == -1)

        {

                error_handling("bind() error");

        }

        // 연결 요청 대기상태로 진입

        if(listen(serv_sock, 5== -1)

        {

                error_handling("listen() error");

        }       

        // 연결 요청 수락

        clnt_addr_size = sizeof(clnt_addr);       

        for(;;) // iterative 서버를 만들기 위해 반복한다.

        {

        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

        serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

        // IP주소, INADDR_ANY - 사용중인 ip의 주소를 자동으로 찾아준다.

        // htonl() - little endian 방식을 big endian 방식으로 바꾸어 준다.
     printf("%s\n", inet_ntoa(clnt_addr.sin_addr)); // IP 주소 출력 

        printf("%d\n", ntohs(clnt_addr.sin_port)); // PORT 번호 출력 
     
if(clnt_sock == -1)

        {

                error_handling("accept() error");

        }

        // 데이터 전송

        write(clnt_sock, message, sizeof(message));

        sleep(5);

        // 연결 종료

        close(clnt_sock);
     }

        return 0;
}
void error_handling(char *message)
{
        fputs(message, stderr);
        fputc(
'\n', stderr);
        exit(
1);
}  

⇒ iterative server 만들기에서 client 소켓 닫기를 하지 않으면 생성된 소켓이 계속 남아있게 된다.

⇒ write() / read() 는 send() / recv()로 바꾸어 사용할 수 있다. 단, linux에서만 가능.

⇒ send()와 recv()에 대해 살펴보면(man 2 send 명령으로도 찾을 수 있다.)

 → send() 호출의 선언은 아래와 같다.

    int send(int sockfd, const void *msg, int len, int flags);

 → sockfd는 socket()를 통해서 얻었거나 accept()를 통해서 새로 구한, 데이터를 보낼 소켓의 기술자, msg는 보낼 데이터를 가리키는 포인터, len은 보낼 데이터의 바이트 수,  flags는 대부분의 경우 0으로 해야 한다.

 → recv() 호출의 선언은 아래와 같다.

    int recv(int sockfd, void *buf, int len, unsigned int flags);

 → sockfd는 읽어올 소켓의 기술자이며 buf는 정보를 담을 버퍼. len은 버퍼의 최대 크기, flags는 대부분의 경우 0으로 세팅해야 한다.

⇒ write(clnt_sock, message, sizeof(message));

 → send(cnt_sock, message, sizeof(message), 0); 으로 바꾸어 쓸 수 있다.


- udpecho_server/ udpecho_client 테스트

⇒ udpecho_client    

           

⇒ udpecho_server         

          

 

        
※ 제대로 보이는지 모르겠습니다. 업로드 하는데 계속 에러가 나네요. 사진이나 글자도 올린거랑 다르게 간격이 마음대로 나오는 듯 합니다. 나중에 수정하겠습니다.  이해가 부족하다 보니 빠지거나 틀린부분이 많을 줄 압니다. 지적과 보충 부탁드립니다~~!!^^

728x90