2014년 12월 30일 화요일

2014년을 마감하며 도서 추천을 하고자 한다.

여기저기서 풍문으로 들은 책들을 모아서 추천한다 -_-;






이중 개발자 영어랑, 프로그래머 수학으로 생각하라는 구입했다 @.@;

2014년 12월 26일 금요일

TCP/IP 소켓 프로그래밍 15강

파트2가 시작되었다.(그래봐야 아무도 안봐 ㅡㅜ)
파트2는 리눅스 기반을 위주로 설명하고 있다.
파트2 - 15강은 소켓과 표준 입출력에 대해 알아볼 것이다.
표준 입출력을 통해서 네트워크 데이터 송수신 하는 법을 알아보자

15-1 : 표준 입출력 함수의 장점


표준 입출력을 통해서 데이터 송수신을 하면 장점은 두가지가 있다.
-표준 입출력 함수는 이식성이 좋다.
-표준 입출력 함수는 버퍼링을 통한 성능 향상에 도움을 준다.

표준 입출력 함수는 모든 os에서 지원하니 다 쓸수 있다.
소켓을 생성하면 os는 입출력을 위한 버퍼를 생성하는데, 표준 입출력으로 하면 추가로
또 하나의 버퍼를 생성한다.

소켓에서 생성하는 버퍼는 TCP에서 재전송을 위해 임시 저장하는 버퍼인 반면
표준 입출력에서 생성하는 버퍼는 오로지 성능 향상을 위해 만들어지는 버퍼다.
그러므로 속도 향상에 도움이 된다.

표준 입출력 함수 사용에 있어서 몇가지 불편한 사항


-양방향 통신이 쉽지 않다
-상황에 따라서 fflush 함수의 호출이 빈번히 등장할 수 있다.
-파일 디스크립터를 FILE 구조체의 포인터로 변환해야 한다.

기본적으로 소켓은 생성시에 파일 디스크립터를 반환한다.
이 파일 디스크립터를 FILE 포인터로 변환하는 과정을 거쳐야 한다.

15-2 : 표준 입출력 함수 사용하기


fdopen 함수를 이용한 FILE 구조체 포인터로의 변환

#include <stdio.h>
FILE * fdopen(int fildes, const char* mode);
-> 성공시 변환된 FILE 구조체 포인터, 실패시 NULL 반환

-fildes : 변환할 파일 디스키립터를 인자로 전달
-mode : 생성할 FILE 구조체 포인터의 모드 정보 전달

소스를 보자

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
FILE *fp;
//open 함수를 사용해서 파일을 생성했으므로 파일 디스크립터가 반환된다.
int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
if(fd==-1)
{
fputs("file open error", stdout);
return -1;
}
//fdopen 함수를 통해서 파일 디스크립터를 FILE 포인터로 변환하고 있다.
//이 때 두번째 인자로 "w"를 전달해 출력모드의 FILE 포인터가 반환된다.
fp=fdopen(fd, "w");
//fp를 통해서 얻은 포인터를 기반으로 표준 출력함수인 fputs 함수를 호출하고 있다.
fputs("Network C programming \n", fp);
//FILE 포인터를 이용해서 파일을 닫고 있다. 이 경우 파일자체가 완전히 종료되기 때문에 파일 디스크립터를 이용해서 또 다시 종료할 필요는 없다.
fclose(fp);
return 0;
}

fileno  함수를 이용한 파일 디스크립터로의 변환


fdopen 함수의 반대기능을 제공하는 함수다.

#include <stdio.h>
int fileno(FILE * stream);
-> 성공시 변환된 파일 디스크립터, 실패시 -1 반환

소스를 보자.

#include <stdio.h>
#include <fcntl.h>

int main(void)
{
FILE *fp;
int fd=open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
if(fd==-1)
{
fputs("file open error", stdout);
return -1;
}
printf("First file descriptor: %d \n", fd); 
//fdopen 함수호출을 통해서 파일 디스크립터를 FILE 포인터로, 
fp=fdopen(fd, "w");
fputs("TCP/IP SOCKET PROGRAMMING \n", fp);
//fileno 함수호출을 통해서 이를 다시 디스크립터로 변환했다.
  printf("Second file descriptor: %d \n", fileno(fp));
fclose(fp);
return 0;
}

15-3 : 소켓 기반에서의 표준 입출력 함수 사용


서버측 소스를 보자.(4강의 에코 서버에서 변경 부분만 볼 것이다)
  
if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
error_handling("bind() error");
if(listen(serv_sock, 5)==-1)
error_handling("listen() error");
clnt_adr_sz=sizeof(clnt_adr);

for(i=0; i<5; i++)
{
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
if(clnt_sock==-1)
error_handling("accept() error");
else
printf("Connected client %d \n", i+1);
//변경 부분이다.
readfp=fdopen(clnt_sock, "r");
writefp=fdopen(clnt_sock, "w");
while(!feof(readfp))
{
fgets(message, BUF_SIZE, readfp);
fputs(message, writefp);
//fflush 함수로 바로 데이터 전송을 보장하고 있다.
fflush(writefp);
}
fclose(readfp);
fclose(writefp);
}
close(serv_sock);
return 0;
}

클라이언트 부분을 보자.

int main(int argc, char *argv[])
{
int sock;
char message[BUF_SIZE];
int str_len;
struct sockaddr_in serv_adr;
//FILE 포인터를 선언했다.
FILE * readfp;
FILE * writefp;
........................
//fdopen 함수를 사용했다.
readfp=fdopen(sock, "r");
writefp=fdopen(sock, "w");

while(1) 
{
fputs("Input message(Q to quit): ", stdout);
fgets(message, BUF_SIZE, stdin);
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n"))
break;

fputs(message, writefp);
//fflush 함수로 바로 전송을 보장했다.
fflush(writefp);
  fgets(message, BUF_SIZE, readfp);
printf("Message from server: %s", message);
}
fclose(writefp);
fclose(readfp);
return 0;
}
이번강은 별로 어렵지 않은 강이였다.
그저 소켓 기반에서도 표준 입출력 함수를 사용할 수 있다는 것을 알 수 있다.
하지만 적용에 따른 부가적인 코드의 발생 때문에 자주 이용하진 않는다고 한다.

16강은 입출력 스트림의 분리에 대한 나머지 이야기를 알아볼 것이다.

2014년 12월 21일 일요일

TCP/IP 소켓 프로그래밍 14강

14강은 멀티 캐스트와 브로드 캐스트를 알아 볼 것이다.

14-1 : 멀티캐스트


우리가 인터넷 방송을 한다고 가정했을 때 멀티미디어 정보를 전송해야한다.
그 때 필요한 멀티 캐스트와 브로드 캐스트를 알아 볼 것이다.
먼저 멀티 캐스트를 알아보자.
멀티 캐스트는 UDP를 기반으로 한다. 그래서 UDP와 구현방식이 매우 유사하다.
차이점은 멀티 캐스트에서의 데이터 전송은 특정 그룹에 가입(등록)되어 있는 다수의 호스트가 된다는 점이다.
멀티 캐스트 방식을 이용하면 단 한번에 데이터 전송으로 다수의 호스트에게 데이터를 전송 할 수 있다.

멀티캐스트의 데이터 전송방식과 멀티캐스트 트래픽 이점


멀티캐스트의 데이터 전송특성은 다음과 같이 정리할 수 있다.
-멀티캐스트 서버는 특정 멀티캐스트 그룹을 대상으로 데이터를 딱 한번 전송한다.
-한번 전송하더라도 그룹에 속하는 클라이언트는 모두 데이터를 수신한다.
-멀티캐스트 그룹의 수는 IP주소 범위 내에서 얼마든지 추가가 가능하다
-특정 멀티캐스트 그룹으로 전송되는 데이터를 수신하려면 해당 그룹에 가입하면 된다.

여기서 말하는 멀티캐스트 그룹은
클래서 D에 속하는 IP주소(224.0.0.0~239.255.255.255)를 말한다.

딱 한번 전송으로 다수가 수신이 가능한 이유는 라우터가 패킷을 복사해서 다수의 호스트에 전달하는 방식이기 때문이다. 물론 이 방식은 트래픽 측명에서 부정적이다.
그렇게 때문에  TTL이라는 것을 설정해야 한다.

라우팅(Routing)과 TTL(Time to Live), 그리고 그룹으로의 가입방법

멀티캐스트 패킷의 전송을 위해서는 TTL이라는 것을 반드시 설정해야한다.
패킷을 얼마나 멀리 전달할 것인가를 결정하는 주 요소가 된다.
TTL은 정수로 표현하고, 이 값은 라우터를 하나 거칠 때마다 1씩 감소한다.
이 값이 0이 되면 더이상 패킷은 전달되지 않고 소멸한다.

그래서 이 값을 너무 크게 설정하면 네트워크 트래픽에 나쁜 영향을 미치고,
너무 작게 설정하면 목적지에 도달하지 않을 수 있다.

TTL 설정 방법은 다음과 같다.

int send_sock;
int time_live=64;
...........
send_sock=socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
............

멀티캐스트 그룹으로의 가입은 소켓의 옵션 설정을 통해 이뤄진다.
그룹 가입과 관련된 프로토콜 레벨은 IPPROTO_IP이고,
옵션의 이름은 IP_ADD_MEMBERSHIP이다.

멀티캐스트 Sender와 Receiver의 구현


멀티캐스트 기반에서는 서버, 클라이언트라는 표현을 대신해서 전송자(Sender라 표시한다), 수신자(Receiver라 표현한다.)라는 표현을 사용한다.
Sender는 멀티캐스트 데이터의 전송주체다.
Receiver는 멀티캐스트 그룹의 가입과정이 필요한 데이터의 수신주체다.
먼저 Sender의 소스를 보자.

int main(int argc, char *argv[])
{
int send_sock;
struct sockaddr_in mul_adr;
int time_live=TTL;
FILE *fp;
char buf[BUF_SIZE];

if(argc!=3){
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
//UDP 소켓을 사용하고 있다.  
send_sock=socket(PF_INET, SOCK_DGRAM, 0);
memset(&mul_adr, 0, sizeof(mul_adr));
//데이터를 전송할 주소정보 설정한다.
mul_adr.sin_family=AF_INET;
mul_adr.sin_addr.s_addr=inet_addr(argv[1]);  // 멀티캐스트의 IP
mul_adr.sin_port=htons(atoi(argv[2]));       // 멀티캐스트의  Port
setsockopt(send_sock, IPPROTO_IP, 
IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
if((fp=fopen("news.txt", "r"))==NULL)
error_handling("fopen() error");
//실제 데이터 전송을 이루어지는 곳
while(!feof(fp))   /* Broadcasting */
{
fgets(buf, BUF_SIZE, fp);
sendto(send_sock, buf, strlen(buf), 
0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));
sleep(2);
}
fclose(fp);
close(send_sock);
return 0;
}

이번에는 Receiver의 소스코드를 보자.

int main(int argc, char *argv[])
{
int recv_sock;
int str_len;
char buf[BUF_SIZE];
struct sockaddr_in adr;
struct ip_mreq join_adr;
if(argc!=3) {
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
  
recv_sock=socket(PF_INET, SOCK_DGRAM, 0);
  memset(&adr, 0, sizeof(adr));
adr.sin_family=AF_INET;
adr.sin_addr.s_addr=htonl(INADDR_ANY);
adr.sin_port=htons(atoi(argv[2]));
if(bind(recv_sock, (struct sockaddr*) &adr, sizeof(adr))==-1)
error_handling("bind() error");
//멀티캐스트의 IP 주소
join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]);
//그룹에 가입할 IP 주소
join_adr.imr_interface.s_addr=htonl(INADDR_ANY);
 //멀티캐스트 그룹에 가입 
setsockopt(recv_sock, IPPROTO_IP, 
IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));
  
while(1)
{
//멀티 캐스트 데이터 수신
str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
if(str_len<0) 
break;
buf[str_len]=0;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}

두 소스에서 PORT번호 는 당연히 일치 시켜야 한다.
실행 순서는 중요하지 않지만 아무래도 방송이다 보니
Receiver을 늦게 실행하면, 그 이전에 전송된 멀티캐스트 데이터는 수신이 불가능하다.

14-2 : 브로드캐스트(Broadcast)


브로드캐스트 역시 한번에 여러 호스트에게 데이터를 전송하는 것은 멀티캐스트와 유사하다. 하지만 전송이 이루어지는 범위가 동일한 네트워크로 연결되어 있는 호스트로, 데이터 전송의 대상이 제한된다.

브로드캐스트의 이해와 구현방법


브로드캐스트는 동일한 네트워크에 연결되어 있는 모든 호스트에게 동시에 데이터를 전송하기 위한 방법이다. 역시 UDP를 기반으로 사용한다.

그리고 IP주소 형태에 따라 다음과 같이 두 가지 형태로 구분이 된다.

-Directed 브로드캐스트
-Local 브로드캐스트

이 둘의 차이점은 IP주소에 있다.
Directed 브로드캐스트의 IP주소는 네트워크 주소를 제외한 나머지 호스트 주소를 전부 1로 설정해서 얻을 수 있다.

Local 브로드캐스트를 위해서는 255.255.255.255 라는 IP 주소가 특별히 예약되어 있다.
IP 주소가 UDP 예제와 유일한 차이점이다.
다만 기본적으로 생성되는 소켓은 브로드캐스트 기반이 아니니 변경해줘야 한다.

sender 소스를 보자.

int main(int argc, char *argv[])
{
int send_sock;
struct sockaddr_in broad_adr;
FILE *fp;
char buf[BUF_SIZE];
//SO_BROADCAST의 옵션 정보를 1로 변경하기 위한 변수 초기화
int so_brd=1;
if(argc!=3) {
printf("Usage : %s <Boradcast IP> <PORT>\n", argv[0]);
exit(1);
}
  //UDP 소켓을 사용하도록 설정
send_sock=socket(PF_INET, SOCK_DGRAM, 0);
memset(&broad_adr, 0, sizeof(broad_adr));
broad_adr.sin_family=AF_INET;
broad_adr.sin_addr.s_addr=inet_addr(argv[1]);
broad_adr.sin_port=htons(atoi(argv[2]));
//SOL_SOCKET의 옵션정보를 변수 bcast에 저장된 값인 1로 변경하는데,
//이는 브로드캐스트 기반의 데이터 전송이 가능함을 의미한다.
setsockopt(send_sock, SOL_SOCKET, 
SO_BROADCAST, (void*)&so_brd, sizeof(so_brd));
if((fp=fopen("news.txt", "r"))==NULL)
error_handling("fopen() error");

while(!feof(fp))
{
fgets(buf, BUF_SIZE, fp);
sendto(send_sock, buf, strlen(buf), 
0, (struct sockaddr*)&broad_adr, sizeof(broad_adr));
sleep(2);
}

close(send_sock);
return 0;
}

이번에는 receiver 소스를 보자.

int main(int argc, char *argv[])
{
int recv_sock;
struct sockaddr_in adr;
int str_len;
char buf[BUF_SIZE];
if(argc!=2) {
printf("Usage : %s  <PORT>\n", argv[0]);
exit(1);
}
//UDP 설정
recv_sock=socket(PF_INET, SOCK_DGRAM, 0);
//주소 설정
memset(&adr, 0, sizeof(adr));
adr.sin_family=AF_INET;
adr.sin_addr.s_addr=htonl(INADDR_ANY);
adr.sin_port=htons(atoi(argv[1]));
if(bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr))==-1)
error_handling("bind() error");
  
while(1)
{
str_len=recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
if(str_len<0) 
break;
buf[str_len]=0;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}

위 소스는 전형적인 UDP 소켓 소스니 더 설명은 필요 없을 듯하다.

14-3 : 윈도우 기반으로 구현하기


윈도우 기반에서도 그대로 적용되지만 약간 헤더파일의 선언만 바뀐다.
소스를 보자.( 왜 윈도우는 따로 놀려고 하는가 ㅡㅡ)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>     // for IP_MULTICAST_TTL option

#define TTL 64
#define BUF_SIZE 30
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hSendSock;
SOCKADDR_IN mulAdr;
//TTL 설정
int timeLive=TTL;
FILE *fp;
char buf[BUF_SIZE];

if(argc!=3) {
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!"); 
 //UDP 사용
hSendSock=socket(PF_INET, SOCK_DGRAM, 0);
//주소 설정
memset(&mulAdr, 0, sizeof(mulAdr));
mulAdr.sin_family=AF_INET;
mulAdr.sin_addr.s_addr=inet_addr(argv[1]); 
mulAdr.sin_port=htons(atoi(argv[2]));      
//소켓 설정
setsockopt(hSendSock, IPPROTO_IP, 
IP_MULTICAST_TTL, (void*)&timeLive, sizeof(timeLive));
if((fp=fopen("news.txt", "r"))==NULL)
ErrorHandling("fopen() error");

while(!feof(fp))
{
fgets(buf, BUF_SIZE, fp);
sendto(hSendSock, buf, strlen(buf), 
0, (SOCKADDR*)&mulAdr, sizeof(mulAdr));
Sleep(2000);
}
closesocket(hSendSock);
WSACleanup();
return 0;
}

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

receiver 소스를 보자.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>     // for struct ip_mreq

#define BUF_SIZE 30
void ErrorHandling(char *message);

int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hRecvSock;
SOCKADDR_IN adr;
struct ip_mreq joinAdr;
char buf[BUF_SIZE];
int strLen;


if(argc!=3) {
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!"); 
  //UDP 소켓으로 설정, 주소 설정
hRecvSock=socket(PF_INET, SOCK_DGRAM, 0);
  memset(&adr, 0, sizeof(adr));
adr.sin_family=AF_INET;
adr.sin_addr.s_addr=htonl(INADDR_ANY);
adr.sin_port=htons(atoi(argv[2]));
if(bind(hRecvSock, (SOCKADDR*) &adr, sizeof(adr))==SOCKET_ERROR)
ErrorHandling("bind() error");
joinAdr.imr_multiaddr.s_addr=inet_addr(argv[1]);
joinAdr.imr_interface.s_addr=htonl(INADDR_ANY);
  //브로드 캐스트 그룹 설정
if(setsockopt(hRecvSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, 
(void*)&joinAdr, sizeof(joinAdr))==SOCKET_ERROR)
ErrorHandling("setsock() error");
  
while(1)
{
strLen=recvfrom(hRecvSock, buf, BUF_SIZE-1, 0, NULL, 0);
if(strLen<0) 
break;
buf[strLen]=0;
fputs(buf, stdout);
}
closesocket(hRecvSock);
WSACleanup();
return 0;
}

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

역시 소스를 받아서 직접 해보자.

이로써 14강은 끝났다. 그와 동시에 파트1도 끝났다.
파트1은 기본적인 소켓 프로그래밍을 배웠고
파트2는 리눅스 기반 프로그래밍을 배운다.
파트3는 윈도우 기반 프로그래밍을 배운다.

파트1이 끝났으니 난 당분간 밀린 동영상 강의를 보면서 복습을 하고자 한다.
그다음 파트2로 넘어가고 이곳에 요약 정리 할 것이다.
책을 6권을 빌려서 얼렁 봐야하는 것도 있다 ㅡㅡ;;;

해피 메리크리스마스~ 해피 뉴이어~ ^0^

TCP/IP 소켓 프로그래밍 13강

13-1 send & recv 입출력 함수


지금까지 윈도우 기반 예제에서는 send&recv함수를 사용했지만 마지막 매개변수에 0 이외의 인자를 전달한 적이 없다.
이번 강에서는 입출력 함수의 마지막 매개변수에 넣는 옵션들에 대해 알아볼 것이다.
양이 많은 관계로 소스는 다운 받아서 해보길 바란다 여기는 주요 함수를 적으려고 한다

리눅스에서의 send & recv


#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf, size_f nbytes, int flags);
-> 성공시 전송된 바이트 수 , 실패시 -1 반환

-sockfd : 데이터 전송 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
-buf : 전송할 데이터를 저장하고 있는 버퍼의 주소 값 전달
-nbytes : 전송할 바이트 수 전달
-flags : 데이터 전송시 적용할 다양한 옵션 정보 전달

#include <sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t nbytes, int flags);
-> 성공시 수신한 바이트 수 (단 EOF 전송시 0), 실패시 -1 반환

-sockfd :  데이터 수신 대상과의 연결을 의미하는 소켓의 파일 디스크립터 전달
-buf : 수신된 데이터를 저장할 버퍼의 주소 값 전달
-nbytes : 수신할 수 있는 최대 바이트 수 전달
-flags : 데이터 수신시 적용할 다양한 옵션 정보 전달

send/recv 함수의 마지막 매개변수에는 옵션 정보가 전달되어야 한다.
옵션 정보는 비트 OR 연산자 ( | 연산자)를 이용해서 둘 이상을 전달 할 수 있다.
매개 변수에 전잘할 수 있는 옵션의 종류를 보자

MSG_OOB : 긴급 데이터(Out-of-band data)의 전송을 위한 옵션(send/recv)
MSG_PEEK : 입력버퍼에 수신된 데이터의 존재 유무 확인을 위한 옵션 (recv)
MSG_DONTROUTE : 로컬 네트워크 상에서 목적지를 찾을 때 사용되는 옵션(send)
MSG_DONTWAIT : 넌-블로킹(Non-blocking) IO 요구에 사용되는 옵션(send/recv)
MSG_WAITALL :  요청한 바이트 수에 해당하는 데이터가 전부 수신 될 때까지, 호출된 함수가 반환되는 것을 막기 위한 옵션(recv)

(1)MSG_OOB : 긴급 메시지의 전송


옵션 MSG_OOB는 'Out-of-band data'라 불리는 긴급 메시지의 전송에 사용된다.
긴급으로 무언가 처리하려면 몇 경로가 달라야 한다.
소스 코드를 보자 (일부만 볼 것 이다)
int main(int argc, char *argv[])
{
int sock;
struct sockaddr_in recv_adr;

if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}

sock=socket(PF_INET, SOCK_STREAM, 0);
  memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family=AF_INET;
recv_adr.sin_addr.s_addr=inet_addr(argv[1]);
recv_adr.sin_port=htons(atoi(argv[2]));

if(connect(sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr))==-1)
error_handling("connect() error!");

write(sock, "123", strlen("123"));
//긴급 메세지 전송
send(sock, "4", strlen("4"), MSG_OOB);
write(sock, "567", strlen("567"));
//긴급 메세지 전송
send(sock, "890", strlen("890"), MSG_OOB);
close(sock);
return 0;
}

단지 저리 옵션만 지정해주면 긴급 메세지로 인식하고 전송하게 된다.
하지만 긴급 메시지 수신에는 좀더 복잡하다. 소스를 보자(역시 일부를 본다)

int main(int argc, char *argv[])
{
struct sockaddr_in recv_adr, serv_adr;
int str_len, state;
socklen_t serv_adr_sz;
struct sigaction act;
char buf[BUF_SIZE];

if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}

act.sa_handler=urg_handler;
sigemptyset(&act.sa_mask);
act.sa_flags=0;

acpt_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&recv_adr, 0, sizeof(recv_adr));
recv_adr.sin_family=AF_INET;
recv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
recv_adr.sin_port=htons(atoi(argv[1]));

if(bind(acpt_sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr))==-1)
error_handling("bind() error");
listen(acpt_sock, 5);

serv_adr_sz=sizeof(serv_adr);
recv_sock=accept(acpt_sock, (struct sockaddr*)&serv_adr, &serv_adr_sz);
//내용이 길어 따로 설명한다.
fcntl(recv_sock, F_SETOWN, getpid()); 
//긴급 메세지를 수신하게 되면 운영제체는 SIGURG 시그널을 발생시켜 프로세스가 등록한 시그널 핸들러가 호출되게 한다.
state=sigaction(SIGURG, &act, 0);

while((str_len=recv(recv_sock, buf, sizeof(buf), 0))!= 0)
{
if(str_len==-1)
continue;
buf[str_len]=0;
puts(buf);
}
close(recv_sock);
close(acpt_sock);
return 0;
}

void urg_handler(int signo)
{
int str_len;
char buf[BUF_SIZE];
str_len=recv(recv_sock, buf, sizeof(buf)-1, MSG_OOB);
buf[str_len]=0;
printf("Urgent message: %s \n", buf);
}

파란 부분을 설명한다.
fcntl 함수는 파일 디스크립터의 컨트롤이 사용된다.

파일 디스크립터 recv_sock 이 가리키는 소켓의 소유자 (F_SETOWN)를 getpid 함수가 반환하는 ID의 프로세스로 변경시킨다.

소켓의 소유자는 운영체제다. SIGURG 시그널을 핸들링할 때에는 반드시 시그널을 처리할 프로세스를 지정해줘야 한다. 그리고 getpid는 이 함수를 호출한 프로세스의 ID를 반환한다. 현재 실행중인 프로세스를 SIGURG 시그널의 처리 주체로 지정하는 것이다.
소스를 받아 실행해보면 긴급 메세지를 추가 한다고 더 빨리 데이터가 전송되는 것도 아니고 데이터도 1바이트밖에 되지 않는다.
긴급하게 보내려면 별도의 통신 경로가 확보되어서 고속으로 데이터가 전송되어야 한다
하지만 TCP는 별도의 통신 경로를 제공하지 않고 있다.
다만 TCP에 존재하는 Urgent mode라는 것을 이용해서 데이터를 전송해줄 뿐이다.

Urgent mode의 동작원리

TCP의 긴급 메시지는 병원으로의 빠른 이동은 보장하지 않는다.
대신 수신자에게 빠른 조치를 요구하는 것이다.

URG=1 : 긴급메시지가 존재하는 패킷이다
URG Pointer : Urgent Pointer의 위치가 오프셋의 몇번째 오프셋에 있는지 표시

(2)MSG_PEEK 입력버퍼 검사하기


MSG_PEEK옵션은 MSG_DONTWAIT 옵션과 함께 설정되어 입력버퍼에 수신 된 데이터가 존재하는지 확인하는 용도로 사용된다.
MSG_PEEK 옵션을 주고 recv함수를 호출하면 입력버퍼에 존재하는 데이터가 읽혀지더라도 입력버퍼에서 데이터가 지워지지 않는다.
때문에 MSG_DONTWAIT 옵션과 묶여서 블로킹 되지 않는 데이터의 존재유무를 확인하기 위한 함수의 호출 구성에 사용된다.

소스를 보자. (일부만 볼 것이다.)
int main(int argc, char *argv[])
{
int acpt_sock, recv_sock;
struct sockaddr_in acpt_adr, recv_adr;
int str_len, state;
socklen_t recv_adr_sz;
char buf[BUF_SIZE];
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);
exit(1);
}

acpt_sock=socket(PF_INET, SOCK_STREAM, 0);
memset(&acpt_adr, 0, sizeof(acpt_adr));
acpt_adr.sin_family=AF_INET;
acpt_adr.sin_addr.s_addr=htonl(INADDR_ANY);
acpt_adr.sin_port=htons(atoi(argv[1]));

  if(bind(acpt_sock, (struct sockaddr*)&acpt_adr, sizeof(acpt_adr))==-1)
error_handling("bind() error");
listen(acpt_sock, 5);

recv_adr_sz=sizeof(recv_adr);
recv_sock=accept(acpt_sock, (struct sockaddr*)&recv_adr, &recv_adr_sz);

while(1)
{
//recv함수를 호출하면서 MSG_PEEK을 옵션으로 전달하고 있다.
//MSG_DONTWAIT 옵션을 함께 전달하는 이유는 데이터가 존재하지 않아도 블로킹 상태에 두지 않기 위해서이다.
str_len=recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK | MSG_DONTWAIT);
if(str_len>0)
break;
}

buf[str_len]=0;
printf("Buffering %d bytes: %s \n", str_len, buf);
 //recv 함수를 한번 더 호출하고 있다. 이번에는 아무런 옵션도 설정하지 않았다.
//때문에 이번에 읽어들이 데이터는 입력버퍼에서 지워진다.
str_len=recv(recv_sock, buf, sizeof(buf)-1, 0);
buf[str_len]=0;
printf("Read again: %s \n", buf);
close(acpt_sock);
close(recv_sock);
return 0;
}

13-2 : readv & writev 입출력 함수 


데이터의 송수신의 효율성을 향상시키는데 도움이 되는 함수들이다.
readv & writev 함수의 기능은 "데이터를 모아서 전송하고, 모아서 수신하는 기능의 함수"다
먼저 writev 함수를 보자.

#include <sys/uio.h>
ssize_t writev(int filedes, const struct iovec* iov, int iovcnt);
-> 성공시 전동된 바이트 수, 실패시 -1 반환

-fileds : 데이터 전송의 목적지를 나타내는 소켓의 파일 티스크립터 전달
-iov : 구조체 iovec배열의 주소 값 전달, 구조체 iovec의 변수에는 전송할 데이터의 위치 및 크기 정보가 담긴다
-iovcnt : 두 번째 인자로 전달된 주소 값이 가리키는 배열의 길이정보 전달.

그리고 위의 함수 두 번째 인자로 전달되는 배열의 구조체 iovec는 다음과 같이 정의되어 있다.

struct iovec
{
    void * iov_basel; //버퍼의 주소 정보
    size_t iov_len;     //버퍼의 크기 정보
}

구조체 iovec는 전송할 데이터가 저장되어 있는 버퍼의 주소값과 실제 전송할 데이터의 크기 정보를 담기 위해 정의 되었다.
예제를 보자

#include <stdio.h>
#include <sys/uio.h>

int main(int argc, char *argv[])
{
struct iovec vec[2];
char buf1[]="ABCDEFG";
char buf2[]="1234567";
int str_len;

vec[0].iov_base=buf1; //위치는  buf1
vec[0].iov_len=3; //길이는 3
vec[1].iov_base=buf2; //위치는 buf2
vec[1].iov_len=4; //길이는 4
str_len=writev(1, vec, 2); //1은 콘솔로 출력하겠다는 뜻
puts("");
printf("Write bytes: %d \n", str_len);
return 0;
}

이번에는 readv 함수를 보자. writev 함수와 반대로 생각하면 된다.

#include <sys/uio.h>
ssize_t readv(int fileds, const struct iovec* iov, int iovcnt);
-> 성공 시 수신된 바이트 수, 실패시 -1 반환

-fileds : 데이터를 수신할 파일(혹은 소켓)의 파일 디스크립터 인자로 전달
-iov : 데이터를 저장할 위치와 크기 정보를 담고 있는 iovec 구조체 배열의 주소 값 전달
-iovcnt : 두 번째 인자로 전달된 주소 값이 가리키는 배열의 길이정보 전달

예제를 보자.
#include <stdio.h>
#include <sys/uio.h>
#define BUF_SIZE 100

int main(int argc, char *argv[])
{
struct iovec vec[2];
char buf1[BUF_SIZE]={0,};
char buf2[BUF_SIZE]={0,};
int str_len;
//5 바이트만 저장하겠다.
vec[0].iov_base=buf1;
vec[0].iov_len=5;
//나머지는 이곳에 저장하겠다.
vec[1].iov_base=buf2;
vec[1].iov_len=BUF_SIZE;

//0 이기 때문에 콘솔로 수신한다는 뜻
str_len=readv(0, vec, 2);
printf("Read bytes: %d \n", str_len);
printf("First message: %s \n", buf1);
printf("Second message: %s \n", buf2);
return 0;
}

readv & writev 함수의 적절한 사용

모든 데이터의 전송을 위해서는 여러 번의 write함수 호출이 요구되는데, 
이를 딱 한번의 writev 함수 호출로 대신할 수 있으니 효율적이다.
마찬가지로 입력 버퍼에 수신된 데이터를 여러 저장소에 나눠서 읽어 들이고 싶은 경우에도 여러번 read 함수를 호출하는 것보다 딱 한번 readv함수를 호출하는 것이 효율적이다.

13-3 : 윈도우 기반으로 구현하기



윈도우에는 리눅스에서 보인 형태의 시그널 핸들링이 존재하지 않는다. 그렇게 때문에 다른 방법을 고민해야 한다. 그 방법이 select 함수 사용하는 것이다.
select 함수의 세 가지 관찰 항목은 다음과 같다.
-수신한 데이터를 지니고 있는 소켓이 존재하는가?
-블로킹 되지 않고 데이터의 전송이 가능한 소켓은 무엇인가?
-예외 상황이 발생한 소켓은 무엇인가?

이중 마지막 "예외 상황이 발생한 소켓은 무엇인가?"가 핵심이다.
긴급 메세지를 예외 상황으로 취급하면 MSG_OOB 옵션을 사용 가능하다.
예제를 보자. 다음은 긴급 메세지를 전송하는 코드의 일부다.

int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hSocket;
SOCKADDR_IN sendAdr;
if(argc!=3) {
printf("Usage : %s <IP> <port>\n", argv[0]);
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
ErrorHandling("WSAStartup() error!");

hSocket=socket(PF_INET, SOCK_STREAM, 0);
memset(&sendAdr, 0, sizeof(sendAdr));
sendAdr.sin_family=AF_INET;
sendAdr.sin_addr.s_addr=inet_addr(argv[1]);
sendAdr.sin_port=htons(atoi(argv[2]));


if(connect(hSocket,
               (SOCKADDR*)&sendAdr,sizeof(sendAdr))==SOCKET_ERROR)
          ErrorHandling("connect() error!");

send(hSocket, "123", 3, 0);
//긴급 메세지 설정
send(hSocket, "4", 1, MSG_OOB);
send(hSocket, "567", 3, 0);
//긴급 메세지 설정
send(hSocket, "890", 3, MSG_OOB);
closesocket(hSocket);
WSACleanup();
return 0;
}

이번에는 긴급 메시지를 수신하는 코드다. select 함수를 사용한다.

int main(int argc, char *argv[])
{
WSADATA wsaData;
SOCKET hAcptSock, hRecvSock;
SOCKADDR_IN recvAdr;
SOCKADDR_IN sendAdr;  
int sendAdrSize, strLen;
char buf[BUF_SIZE];
int result;
fd_set read, except, readCopy, exceptCopy;
struct timeval timeout;
if(argc!=2) {
printf("Usage : %s <port>\n", argv[0]);  
exit(1);
}
if(WSAStartup(MAKEWORD(2, 2), &wsaData)!=0)
ErrorHandling("WSAStartup() error!");
hAcptSock=socket(PF_INET, SOCK_STREAM, 0);
memset(&recvAdr, 0, sizeof(recvAdr));
recvAdr.sin_family=AF_INET;
recvAdr.sin_addr.s_addr=htonl(INADDR_ANY);
recvAdr.sin_port=htons(atoi(argv[1]));

if(bind(hAcptSock, (SOCKADDR*)&recvAdr, sizeof(recvAdr))==SOCKET_ERROR)
ErrorHandling("bind() error");
if(listen(hAcptSock, 5)==SOCKET_ERROR)
ErrorHandling("listen() error");
sendAdrSize=sizeof(sendAdr);
hRecvSock=accept(hAcptSock, (SOCKADDR*)&sendAdr, &sendAdrSize);
FD_ZERO(&read);
FD_ZERO(&except);
FD_SET(hRecvSock, &read);
FD_SET(hRecvSock, &except);

while(1)
{  
readCopy=read;
exceptCopy=except;
timeout.tv_sec=5;
timeout.tv_usec=0; 
//select 함수를 사용해서 긴급 메시지를 선별해서 우선 처리한다.
result=select(0, &readCopy, 0, &exceptCopy, &timeout);

if(result>0)
{
if(FD_ISSET(hRecvSock, &exceptCopy))
{
strLen=recv(hRecvSock, buf, BUF_SIZE-1, MSG_OOB);
buf[strLen]=0;
printf("Urgent message: %s \n", buf);
}

if(FD_ISSET(hRecvSock, &readCopy))
{
strLen=recv(hRecvSock, buf, BUF_SIZE-1, 0);
if(strLen==0)
{
break;
closesocket(hRecvSock);
}
else 
{   
buf[strLen]=0;
puts(buf); 
}
}
}
}
closesocket(hAcptSock);
WSACleanup();
return 0; 
}

최대한 줄여서 쓰다보니 영 설명이 부실하다 ㅡㅡ;;;
역시 소스코드를 다운 받아 직접 실행해가며 해보면 어렵지는 않을 것이다.

다음 강은 멀티캐스트 & 브로드캐스트에 관해서 알아볼 것이다.

2014년 12월 19일 금요일

TCP/IP 소켓 프로그래밍 12강

IO 멀티플렉싱에 대해서 알아볼려고 한다.

12-1 IO멀티플렉싱 기반의 서버

멀티프로세스 서버는 단점이 있었다. 다중 접속 서버의 구현을 위해서는 클라이언트가 연결요청이 있을 때마다 새로운 프로세스를 생성했다.
적은 접속에는 무리가 없지만 많은 클라이언트들의 연결요청이 있으면 서버에 과부하를 초래한다. 이 과부하를 줄이기 위한 대안이 IO멀티플렉싱이다.

멀티플렉싱이란? 하나의 통신채널을 통해서 둘 이상의 데이터를 전송하는데 사용되는 기술

더 쉽게 말하자면 한개의 프로세스로 여러개의 소겟을 생성해서 동시 접속을 한다는 것이다.
물론 무한정 막 동시 접속을 하진 못한다. 그래서 요청이 오면 요청 순서대로 빠르게 왔다 갔다 하면서 대응하는 것이다. 이것을 도와주는 함수가 select 함수다.

12-2 : select 함수의 이해와 서버의 구현

select 함수를 이용하면 한 곳에 여러 개의 파일 디스크립터를 모아놓고 동시에 이들을 관찰할 수 있다. (관찰 항목 각각을 이벤트라고 부른다)

이 함수의 호출 과정을 보자

파일 디스크립터의 설정, 검사의 범위 지정, 타임아웃 설정 -> select 함수의 호출 -> 호출결과 확인

1)파일 디스크립터의 설정

먼저 관찰하고자 하는 파일 디스크립터를 모아야 한다.
모을 때 (수신,전송,예외)에 따라서 구분해서 모아야 한다.
이때 사용되는 것이 fd_set형 변수다.

이 변수에 들어가는 매크로 함수는 다음과 같다

-FD_ZERO(fd_set* fdset) 모든 비트를 0으로 초기화
-FD_SET(int fd, fd_set *fdset) 파일 디스크립터 정보를 등록
-FD_CLR(int fd, fd_set *fdset) 파일 디스크립터 정보를 삭제
-FD_ISSET(int fd, fd_set *fdset) 호출 결과를 확인하는 용도

2)검사(관찰)의 범위 지정과 타임아웃의 설정

select 함수를 보자. (매개 변수가 좀 많다)

#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *readset, fd_set *writeset,
                           fd_set *exceptset, const struct timeval *timeout);

-maxfd : 검사 대상이 되는 파일 디스크립터의 수
-readset : fd_set형 변수에 수신된 데이터 존재여부에 관심 있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소 값을 전달한다.
-writeset : fd_set형 변수에 블로킹 없는 데이터 전송의 가능여부에 관심있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소값을 전달한다.
-exceptset : fd_set형 변수에 예외상황의 발생여부에 관심이 있는 파일 디스크립터 정보를 모두 등록해서 그 변수의 주소 값을 전달한다
-timeout : select 함수 호출 이후에 무한정 블로킹 상태에 빠지지 않도록 타임아웃을 설정하기 위한 인자를 전달한다

반환 값 : 오류발생시에는 -1이 반환되고, 타임 아웃에 의한 반환시에는 0이 반환된다. 그리고 관심대상으로 등록된 파일 디스크립터에 해당 관심에 관련된 변화가 발생하면 0보다 큰 값이 반환되는데, 이 값은 변화가 발생한 파일 디스크립터의 수를 의미한다.

이 함수를 호출에 앞서 결정해야 할 것이 2가지 있다.
-파일 디스크립터의 관찰 범위는 얼마인지
-select 함수의 타임아웃 시간을 어떻게 해야하나

예제를 보자.

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 30

int main(int argc, char *argv[])
{
fd_set reads, temps;
int result, str_len;
char buf[BUF_SIZE];
struct timeval timeout;
//fd_set형 변수를 초기화 한다.
FD_ZERO(&reads);
//파일 디스크립터 0의 위치를 1로 설정해 준다.
FD_SET(0, &reads); // 0 is standard input(console)

/* //1번만 초기화하면 문제가 생기기에 주석 처리 했다.
timeout.tv_sec=5;
timeout.tv_usec=5000;
*/

while(1)
{
temps=reads; //원본을 유지하기 위해 복사한다.
//select 함수 실행전에 반복 초기화 시켜준다.
timeout.tv_sec=5;
timeout.tv_usec=0;
//데이터에 변화가 있으면 변화된 갯수를 를 반환한다, 데이터 변화가 없으면 0을 반환한다.
result=select(1, &temps, 0, 0, &timeout);
if(result==-1)
{
puts("select() error!");
break;
}
else if(result==0)
{
puts("Time-out!");
}
else //데이터 변화가 있을 경우
{
if(FD_ISSET(0, &temps))  //표준 입력이 맞다면
{
str_len=read(0, buf, BUF_SIZE);
buf[str_len]=0;
printf("message from console: %s", buf);
}
}
}
return 0;
}

이 역시 소스를 다운 받아서 해봐야 더 수월하게 알 수 있다.
물론 책도 구입해서 공부하는 것이 제일 좋다;;;
현재 보고 있는 책은 "윤성우 열혈 TCP/IP 소켓 플로그래밍"이다
소스 다운 받기

13강에는 다양한 입출력 함수들에 대해 알아보겠다.

TCP/IP 소켓 프로그래밍 11강

도서 추천했다가 다시 11강으로 연결한다.
11강은 프로세스간의 통신법을 알아보자~

11- 1 : 프로세스간 통신의 기본 개념
컴퓨터와 컴퓨터의 통신은 소켓을 통해서 데이터를 주고 받는데
프로세스와 프로세스는 파이프를 통해서 데이터를 주고 받는다.(라임이 맞네?!)

파이프?? 파이프라면 그 어깨 형들이 들고다니는 그 쇠파이프??
맞다 그 파이프다~! 그걸로 빨대 꽂듯이 꽂아서 데이터를 쭉 빨아먹...
미안하다 그게 아니지만 개념은 같다ㅡㅡ;

먼저 파이프를 꽂으려면 파이프를 생성해야 한다.

-파이프 생성 함수-

#include <unistd.h>
int pipe(int filedes[2]);
-filed[0] : 파이프로부터 데이터를 수신하는데 사용되는 파일 디스크립터 저장,(파이프의 출구)
-filed[1] : 파이프로부터 데이터를 전송하는데 사용되는 파일 디스크립터 저장,(파이프의 입구)

위의 함수를 이용하면 프로세스 끼리 통신이 가능해진다.
예제를 보자.
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[])
{
int fds[2];
//파이프 생성하고 보낼 메세지다
char str[]="Who are you?";
char buf[BUF_SIZE];
pid_t pid;
//파이프 생성
pipe(fds);
//부모 자식 프로세스에 파이프가 생김
pid=fork();
//자식 프로세스에 적용되는 부분
if(pid==0)
{
write(fds[1], str, sizeof(str)); //문자열 보냄
}
else //부모 프로세스에 적용되는 부분
{
read(fds[0], buf, BUF_SIZE); //문자열 받음
puts(buf);
}
return 0;
}

위의 예제를 보면 일방적인 통신이다. 양방향 통신이 가능하게 할 수도 있다.
예제를 보자.
#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[])
{
int fds[2];
//보낼 메세지들이다.
char str1[]="Who are you?";
char str2[]="Thank you for your message";
char buf[BUF_SIZE];
pid_t pid;
//파이프 생성 했다
pipe(fds); 
//자식 프로세서 생성했다.
pid=fork();
//자식 프로세서가 수행할 부분이다. (자식은 0이랑께)
if(pid==0)
{
//부모에게 Who are you?를 날린다.(이런 호로자식...)
write(fds[1], str1, sizeof(str1)); 
sleep(2); //2초 딜레이 한다(이유는 충돌 방지)
//부모에게서 온 메세지를 읽는다.(Thank you for your message)
read(fds[0], buf, BUF_SIZE); 
printf("Child proc output: %s \n",  buf);
}
else //부모 프로세서가 수행할 부분이다.
{
//자식 놈(?)이 Who are you?를 보냈다.
read(fds[0], buf, BUF_SIZE);
//받은 메세지를 출력한다.
printf("Parent proc output: %s \n", buf);
//자식 프로세스에게 Thank you for your message를 보냈다.
write(fds[1], str2, sizeof(str2));
//바로 종료되는 것을 막기 위해 일부러 딜레이 줬다.
sleep(3);
}
return 0;
}

위의 예제와 같이 하나의 파이프로 양방향 통신이 가능하긴 하다.
문제는 위와 같이 언제 메세지가 올지 바로 알수 있는 경우에는 약간의 딜레이로 
수신을 할수는 있지만. 대부분의 경우에는 메세지가 언제 올지 알수 없다.
만약 딜레이를 주석처리하고 실행하면 자식이 보내고 자식이 바로 받아서 읽는다
아이고 의미없다~~
이런 비극을 막기 위해서는 아예 수신 전용, 송신 전용 파이프를 2개 만들어서 하는게 좋다
예제를 보자.

#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[])
{
int fds1[2], fds2[2];
char str1[]="Who are you?";
char str2[]="Thank you for your message";
char buf[BUF_SIZE];
pid_t pid;
//파이프를 2개 생성한다.
pipe(fds1), pipe(fds2);
//자식 프로세스를 생성
pid=fork();
if(pid==0)
{
write(fds1[1], str1, sizeof(str1)); //fds1 파이프를 사용해서 메세지 보낸다.
read(fds2[0], buf, BUF_SIZE);  //fds2 파이프를 사용해서 메세지 받는다.
printf("Child proc output: %s \n",  buf);
}
else
{
read(fds1[0], buf, BUF_SIZE); //fds1파이프를 사용해서 메세지 받는다.
printf("Parent proc output: %s \n", buf); //출력
write(fds2[1], str2, sizeof(str2)); //fds2파이프를 사용래서 메세지 보낸다.
sleep(3);
}
return 0;
}

이번 강은 무지하게 쉬웠다. 파이프 생성해서 서로 통신만 하게 연결만 시키면 된다.
역시 소스를 다운받아서 직접 해보자

12강은 IO 멀티플렉싱을 알아볼 것이다.

2014년 12월 15일 월요일

도서 추천 - 데이터 베이스 편

솔직히 데이터베이스는 직접 샘플 DB 다운 받아
쿼리문 날리면서 연습하는게 가장 좋은 것 같다.
물론 SQL을 공부를 해야한다.
학교 다닐 때 DB 공부할 때 도움 되었던 책들을 소개하고자 한다.




특히 헤드퍼스트 SQL은 DB를 처음 공부할 때 도움이 많이 되었다.

도서 추천 - 하드웨어 편

전자계산기 조직응용기사 자격증을 따기 위해
하드웨어를 공부를 하면서 관심을 가지게 되었다.
(실기 한번 떨어졌다 ㅡㅡ;; 내년 상반기엔 꼭...;;)
하드웨어를 공부하다보니 프로그램이 어차피 하드웨어가 있어야 돌아가는 것인데
너무 무관심했다는 생각이 들었다.
기초가 부족했던 나에게 큰 도움 되었던 책들을 소개한다.





필독서에 겹친 책도 있다. 그만큼 강력 추천한다는 의미기도 하다 ㅎㅎ;
특히 64비트 멀티코어 os 원리와 구조는 두개 합쳐서 10만원이 넘는데도 구입했다 ;;
직장인이 이정도 투자는 해줘야..ㅜㅠ(정작 사놓고 못본건 함정..)
os를 만들어보고 싶은 것은 나의 욕망이다.
하드웨어와 소프트웨어, 네트워크를 아우르는 os 제작....
언제나 자금과 시간, 게으름이 문제이긴하다....
일단은 아두이노로 조금씩 하고 있다 ;;