- 함수 포인터
⇒ 변수나 파일을 포인터 변수를 이용하여 접근하듯이, 함수도 포인터를 이용하여 호출할 수 있다. 함수 포인터(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 |
… |
⇒ *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이 출력된다.
⇒ 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 |
… |
가 된다.
- 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
※ 제대로 보이는지 모르겠습니다. 업로드 하는데 계속 에러가 나네요. 사진이나 글자도 올린거랑 다르게 간격이 마음대로 나오는 듯 합니다. 나중에 수정하겠습니다. 이해가 부족하다 보니 빠지거나 틀린부분이 많을 줄 압니다. 지적과 보충 부탁드립니다~~!!^^
'코스웨어 > 11년 내장형하드웨어' 카테고리의 다른 글
[내장형] 2011년 7월 11일 일일 보고서(선주) (14) | 2011.07.11 |
---|---|
2011.07.08 [내장형]심재원_수업내용 (11) | 2011.07.08 |
[내장형]이수란_20110707_struct & TCPechoClient (21) | 2011.07.07 |
[내장형]윤민석 2011년 7월 6일 수업내용 (21) | 2011.07.06 |
[내장형]이성재 2011년7월4일 수업내용 (15) | 2011.07.04 |
[내장형]최남식-2011년7월1일 수업내용 (11) | 2011.07.02 |
[내장형]김정환 - 2011년 06월 30일 일일 보고서 (10) | 2011.06.30 |
[내장형]김수만_2011년6월29일_수업내용 (22) | 2011.06.30 |