1. 입출력 다중화란?
입출력 다중화 기술은 여러개의 입력과 출력을 관리하는 기술이다.
입출력 대상은 파일이므로 여러 파일의 입출력을 다룰 수 있다
1) 입출력을 관리하고자 하는 파일 지정번호의 그룹을 만든다.
2)관리 파일 그룹에 있는 파일 중 읽을 데이터가 있는 파일이 있는지를 확인해서 반환한다.
운영체제는 관리 파일 그룹에 있는 파일에 데이터 변화가 발생하면 이를 체크 한다
3)만약 읽을 데이터가 있는 파일이 있다면 읽기 함수로 데이터를 읽어서 처리한다
4)처리 후에는 2번으로 이동해서 2~4를 반복 수행한다.
2. select 함수로 입출력 다중화 구현
select 함수는 여러 파일 중 어떤 파일에 데이터 변화가 생겼는지를 알려줘서,
작업할 파일을 선택할 수 있도록 해준다.
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds : select 함수는 파일의 그룹을 관리한다. 그룹이 몇 개의 파일을 포함하는지 알려준다.
readfds : 읽을 데이터가 있는지 검사
writefds : 쓸 데이터가 있는지 검사
exceptfds : 비트 테이블의 크기 최대 1024비트의 크기를 가진다.
timeout : 입출력 변화가 있을 때까지 기다리는데 제한 시간을 지정할 수 있다.
NULL 이면 데이터 변화가 있을 때 까지 무한정 기다린다.
fd_set 관리를 위한 매크로 함수
select 함수의 핵심은 fd_set 관리에 있다. 1024비트 크기의 비트 필드로 파일 플래그 정보를 저장한다. 몇 가지 매크로 함수가 제공된다.
1) FD_ZERO(fd_set *fds) : fd_set를 초기화 한다. 모든 프래그 값을 0으로 만든다.
2) FD_SET(int fdnum, *fds) : 파일 지정번호 fdnum을 관리 위해서 fd_set에 추가한다.
select 함수는 이 파일에 데이터 변화가 생기면 플래그를 1로 한다.
3) FD_ISSET(fdnum, *fds) : fds에 포함된 fdnum 파일에 데이터 변화가 있는지 확인한다.
4) FD_CLR(fdnum, *fds) : fds 에서 fdnum을 제외한다.
클라이언트가 연결 종료를 해서 더 이상 관리할 필요가 없는 경우에 주로 사용한다.
3. 소켓 프로그래밍과 입출력 다중화의 결합
프로그램 시나리오
1) socket 함수 호출
2) bind 함수 호출
3) listen 함수 호출
4) socket 함수로 만든 듣기 소켓을 fd_set에 추가한다.
5) select 함수를 호출한다.
6) 듣기 소켓 혹은 연결 소켓에 읽을 데이터가 들어오면 select 함수는 반환한다
7) FD_SET 매크로 함수로 어느 소켓에 읽을 데이터가 있는지 확인한다.
- 듣기 소켓에 읽을 데이터가 있다면, accept 함수를 호출해서 클라이언트와 연결한다.
이때 만들어진 연결 소켓은 fd_set에 추가한다.
- 연결 소켓에 읽을 데이터가 있다면 read 함수로 데이터를 읽어서 처리한다.
8) 5번으로 되돌아간다.
4. 입출력 다중화의 특징과 적용처
입출력 다중화는 프로세스나 스레드를 만들지 않고도 여러 소켓을 처리할 수 있다.
IPC를 사용할 필요도 없고 동기화를 위해 고민할 필요도 없다.
그러나 동시 실행이 아니기 때문에 이에 따르는 제약도 있다.
데이터를 읽어서 처리하고 응답하는데 많은 시간이 걸리는 서비스에는 적당하지 않다.
그 시간 동안 다른 입출력은 대기하기 때문이다.
입출력 다중화를 활용하기에 가장 좋은 서비스는 채팅 서비스 같은 메시지 전달 서비스다.
소스 코드 받기
2016년 5월 23일 월요일
2016년 5월 22일 일요일
뇌자극 TCP_IP 프로그래밍 12강 요약
1. 소켓 입출력 함수의 데이터 입출력 제어
TCP 전용 입출력 함수
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd : 소켓 지시사
buf : 데이터 저장을 위한 버퍼
len : 버퍼의 크기
UDP 전용 입출력 함수
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flage,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, void *buf, size_t len, int flage,
const struct sockaddr *dest_addr, socklen_t *addrlen);
sockfd : 소켓 지시사
buf : 데이터 저장을 위한 버퍼
len : 버퍼의 크기
TCP 전용 입출력 함수
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd : 소켓 지시사
buf : 데이터 저장을 위한 버퍼
len : 버퍼의 크기
UDP 전용 입출력 함수
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flage,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, void *buf, size_t len, int flage,
const struct sockaddr *dest_addr, socklen_t *addrlen);
sockfd : 소켓 지시사
buf : 데이터 저장을 위한 버퍼
len : 버퍼의 크기
src_addr : 데이터를 보낸 측의 주소
dest_addr : 데이터를 받을 측의 주소
소켓 함수는 flags 매개변수를 이용하면 여러 일을 할 수 있다
MSG_PEEK : 데이터를 읽은 후 버퍼의 데이터를 지우지 않는다. 이 값은 여러 서브 루틴이 하나의 소켓으로부터 값을 읽기 위해서 대기하고 있을 때 유용하다
MSG_DONTROUTE : 데이터 전송시 라우팅 테이블을 참조 하지 않을 것을 명시한다.
결국 지역 네트워크에서만 패킷을 전송하겠다는 의미다.
MSG_DONTWAIT : 비봉쇄 통신을 시도한다.
MSG_OOB : out-of-band 데이터로 전송함을 나타낸다.
2. OOB 통신
OOB는 긴급히 전달해야 할 데이터가 있을 때 사용한다.
정보의 우선순위가 아닌 중요한 데이터라는 것을 알려주는 것이다.
버퍼가 비어지길 기다리지 않고 바로 전송한다.
Nagle 알고리즘을 무시한다.
TCP 소켓에서만 사용된다
OOB 통신 방법
보내는 측 : MSG_OOB만 설정하면 된다.
send(sockfd, buf, MAX_LINE-1, MSG_OOB);
받는 측 : 시그널을 발생시켜야 하므로 약간 더 복잡하다.
signal(SIGURG, urg_handler);
fcntl(sockfd, F_SETOWN, getpid());
send(sockfd, "A", 1, MSG_OOB);
3. 표준 입출력 함수
리눅스는 소켓도 파일로 다루기 때문에 표준 입출력 함수를 사용할 수 있다.
스트림을 제어하는 표준 입출력 함수
저수준 함수를 사용하면 데이터를 저장하기 위한 버퍼만 제공할 뿐,
전혀 관리를 하지 않는다. 개발자가 직접 데이터를 관리해야 한다.
개발자가 구분자를 지정해서 활용해야 한다.
이럴 때 표준 입출력 함수를 이용하면 간단하게 처리할 수 있다.
소켓 프로그래밍이라면 read함수 대신 fgets를, write 함수 대신 fputs를 사용하면 된다.
파일 지정 번호로부터 파일 스트림 가져오기
소켓은 int 형의 파일 지정 번호를 사용한다.
하지만 모든 표준 입출력 함수는 파일 스트림 객체임 FILE 자료형을 사용하며,
형 변환이 불가능하다.
fdopen 함수를 이용하면 파일 지정 번호를 파일 스트림으로 열수 있다.
#include <stdio.h>
FILE *fdopen(int fd, const char *mode);
fd : 파일 스트림을 얻어올 소켓 지시자
mode : 파일 스트림의 접근 방식을 지정한다.
r : 읽기 전용으로 연다
w : 쓰기 전용으로 연다
r+ : 읽기/쓰기로 연다
w+ : 읽기/쓰기로 연다. 만약 파일이 존재하지 않으면, 파일을 새로 만든다.
기존에 파일이 존재 한다면, 파일을 크기 0으로 만든다.
사용이 끝난 후에는 fclose 함수로 닫아줘야 한다
#include <stdio.h>
int fclose(FILE *fp);
표준 입출력 함수의 한계
표준 입출력 함수들은 개발자의 수고를 덜어주기 위해 만들어졌다.
이러한 함수들은 대개 범용적으로 만들어져서 어떤 용도로든 무난하게 사용 가능하다
일반적으로 쓰기에는 편하지만 특수한 용도에는 적합하지 않을 수도 있으며
성능에 문제가 있을 수도 있다.
표준 입출력 함수는 단지 읽기 쓰기 용도로 쓸 경우
저수준 입출력 함수보다 20~30% 느리다.
대량의 데이터를 다루고 성능이 중요하다면
표준 입출력 함수를 쓸 것인지 저수준 입출력함수를 쓸 것인지 고민해봐야 한다.
뇌자극 TCP_IP 프로그래밍 11강 요약 2
너무 내용이 길어서 3개씩 끊었다.
4. 공유 메모리
프로세스 간에 정보 교환 외에 정보 자체를 공유하는 경우가 있을 것이다.
공동으로 사용할 공간을 마련하고 이 공간에 정보를 넣어 공유하는 방식이다.
공유 메모리 생성
#include <sys/types.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmfla);
key : 공유 메모리는 커널에서 관리한다. 각 프로세스는 이를 이용해 접근할 공유 메모리를 식별한다.
size : 커널에 요청할 공유 메모리 공간의 크기다.
shmflg : 공유 메모리 접근 방식과 권한을 결정하기 위해 사용한다.
- IPC_CREATE : 공유 메모리를 새로 생성한다.
- IPC_EXCL : IPC_CREAT와 함께 사용한다. key 값을 가지는 공유 메모리 영역이 이미 전조하면 에러를 확인 할 수 있다.
공유 메모리 첨부와 분리하기
공유 메모리를 사용하기 위해서는 커널의 공유 메모리 공간을 프로세스에 첨부해줘야 한다.
1) 공유 메모리 공간을 생성한다
2) 프로세스 A가 공유 메모리 공간을 첨부한다.
3) 프로세스 B가 공유 메모리 공간을 첨부한다.
4) 이제 A와 B는 첨부된 공유 메모리 공간으로 데이터를 공유할 수 있다.
int shmdt(const void *shmaddr);
shmaddr : 분리할 공유 메모리를 가리키는 포인터로 shmat에서 사용했던 shmaddr을 그대로 사용한다.
공유 메모리 관리
shmctl 함수로 공유 메모리를 관리할 수 있다.
관리 요소는 공유 메모리 정보 가져오기, 권한설정, 공유 메모리 삭제 등이 있다.
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid : 공유 메모리 식별자
cmd : 공유 메모리 제어 값이다. 다음과 같은 값들이 있다.
-IPC_STAT : 공유 메모리 정보를 가져온다.
-IPC_SET : 공유 메모리 공간의 권한 변경을 위해 사용
-IPC_SMID : 공유 메모리 공간을 삭제한다.
buf : 공유 메모리 정보를 저장한다.
공유 메모리의 장점
-빠른 수행 속도 : 공유 메모리는 데이터 저장을 위한 공간으로 메모리를 사용한다.
-효율성 : 여러 프로세스 간의 데이터 공유에 효율적이다.
-접근성 : key 이름만 알고 있으면 어떤 프로세스든 접근할 수 있다.
5. 세마포어
메모리를 공유하다 보면 읽고 쓰는데 동기화 문제가 일어날 수 있다.
동시에 여러 프로세스가 접근하지 못하도록 임계 영역을 만들어 간섭을 막을 수 있다.
세마포어는 커널에서 관리하는 자원으로 프로세스가 임계 영역을 진입하게 되고,
그렇지 못한 프로세스는 키가 반환되어 사용할 수 있을 때까지 기다려야 한다.
세마포어의 생성과 관리
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key : 커널 관리 자원과 마찬가지로 key 값으로 생성하고 접근 할 수 있다.
nsems: 세마포어는 배열로 만들어지는데, 세마포어 집합의 크기를 결정하기 위해 사용한다.
semflg : 세마포어의 생성 방식을 결정한다.
-IPC_CREAT : 커널에 key 값의 세마포어가 없다면 새로 생성한다.
-IPC_EXCL : IPC_CREAT와 함께 사용한다. key 값의 세마포어가 이미 존재한다면 에러 값을 반환한다.
세마포어의 임계영역은 semop 함수로 관리한다.
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid : semget 함수로 생성한 세마포어 식별자이다.
sops : 세마포어 제어를 위해 사용하는 세마포어 구조체이다.
nosps : 세마포에 집합의 개수이다. 하나의 프로세스만 진입하려면 1을 사용한다.
6. 시그널
시그널은 프로세스에게 비동기적인 사건을 알려주거나 혹은 사건을 동기화 시키는데 사용한다.
다양한 시그널
SIGHUP : 터미널에서 잃어버렸을 때 발생한다. 콘솔창을 닫을 때 발생한다.
SIGABRT : 프로그램의 비정상 종료시 발생한다
SIGINT : 컨트롤 + c를 입력할 때 발생한다. SIGINT 시그널이 프로세스로 전달되면 프로세스가 종료된다.
SIGIO : 비동기적인 입출력이 발생했을 때 사용한다.
SIGKILL : 프로세스를 강제로 죽이기 위해 사용한다
SIGEGV : 운영체제가 프로세스를 강제로 종료시킬 때 사용한다.
SIGSTOP : 프로세스를 일시 중단 할 때 사용한다.
SIGCONT : 멈춘 프로세스를 다시 움직일 때 사용한다.
시그널의 기본 행동
종료 : 프로세스를 종료한다.
무시 : 기본적으로 무시되는 시그널은 없다고 봐도 된다. 하지만 개발자는 특정 시그널을 무시하도록 할 수 있다.
함수 실행 : 특정 시그널을 받았을 때 이를 처리할 함수를 호출할 수 있다.
시그널 함수를 이용해서 시그널 제어하기
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
signum : 제어하기 원하는 시그널의 번호다.
handler : signum 번호를 가지는 시그널이 발생 했을 때, 실행한 시그널 핸들러다.
-SIG_IGN : 시그널을 무시한다. SIGKILL 과 SIGSTOP는 무시할 수 없다.
-SIG_DFL : 시그널의 기본 행동을 한다.
역시 위의 내용은 각각 소스코드가 있다.
나의 깃허브에서 다운 받아서 해보길...;;;
소스 코드 받기
4. 공유 메모리
프로세스 간에 정보 교환 외에 정보 자체를 공유하는 경우가 있을 것이다.
공동으로 사용할 공간을 마련하고 이 공간에 정보를 넣어 공유하는 방식이다.
공유 메모리 생성
#include <sys/types.h>
#include <sys/shm.h>
int shmget(key_t key, int size, int shmfla);
key : 공유 메모리는 커널에서 관리한다. 각 프로세스는 이를 이용해 접근할 공유 메모리를 식별한다.
size : 커널에 요청할 공유 메모리 공간의 크기다.
shmflg : 공유 메모리 접근 방식과 권한을 결정하기 위해 사용한다.
- IPC_CREATE : 공유 메모리를 새로 생성한다.
- IPC_EXCL : IPC_CREAT와 함께 사용한다. key 값을 가지는 공유 메모리 영역이 이미 전조하면 에러를 확인 할 수 있다.
공유 메모리 첨부와 분리하기
공유 메모리를 사용하기 위해서는 커널의 공유 메모리 공간을 프로세스에 첨부해줘야 한다.
1) 공유 메모리 공간을 생성한다
2) 프로세스 A가 공유 메모리 공간을 첨부한다.
3) 프로세스 B가 공유 메모리 공간을 첨부한다.
4) 이제 A와 B는 첨부된 공유 메모리 공간으로 데이터를 공유할 수 있다.
int shmdt(const void *shmaddr);
shmaddr : 분리할 공유 메모리를 가리키는 포인터로 shmat에서 사용했던 shmaddr을 그대로 사용한다.
공유 메모리 관리
shmctl 함수로 공유 메모리를 관리할 수 있다.
관리 요소는 공유 메모리 정보 가져오기, 권한설정, 공유 메모리 삭제 등이 있다.
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid : 공유 메모리 식별자
cmd : 공유 메모리 제어 값이다. 다음과 같은 값들이 있다.
-IPC_STAT : 공유 메모리 정보를 가져온다.
-IPC_SET : 공유 메모리 공간의 권한 변경을 위해 사용
-IPC_SMID : 공유 메모리 공간을 삭제한다.
buf : 공유 메모리 정보를 저장한다.
공유 메모리의 장점
-빠른 수행 속도 : 공유 메모리는 데이터 저장을 위한 공간으로 메모리를 사용한다.
-효율성 : 여러 프로세스 간의 데이터 공유에 효율적이다.
-접근성 : key 이름만 알고 있으면 어떤 프로세스든 접근할 수 있다.
5. 세마포어
메모리를 공유하다 보면 읽고 쓰는데 동기화 문제가 일어날 수 있다.
동시에 여러 프로세스가 접근하지 못하도록 임계 영역을 만들어 간섭을 막을 수 있다.
세마포어는 커널에서 관리하는 자원으로 프로세스가 임계 영역을 진입하게 되고,
그렇지 못한 프로세스는 키가 반환되어 사용할 수 있을 때까지 기다려야 한다.
세마포어의 생성과 관리
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
key : 커널 관리 자원과 마찬가지로 key 값으로 생성하고 접근 할 수 있다.
nsems: 세마포어는 배열로 만들어지는데, 세마포어 집합의 크기를 결정하기 위해 사용한다.
semflg : 세마포어의 생성 방식을 결정한다.
-IPC_CREAT : 커널에 key 값의 세마포어가 없다면 새로 생성한다.
-IPC_EXCL : IPC_CREAT와 함께 사용한다. key 값의 세마포어가 이미 존재한다면 에러 값을 반환한다.
세마포어의 임계영역은 semop 함수로 관리한다.
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid : semget 함수로 생성한 세마포어 식별자이다.
sops : 세마포어 제어를 위해 사용하는 세마포어 구조체이다.
nosps : 세마포에 집합의 개수이다. 하나의 프로세스만 진입하려면 1을 사용한다.
6. 시그널
시그널은 프로세스에게 비동기적인 사건을 알려주거나 혹은 사건을 동기화 시키는데 사용한다.
다양한 시그널
SIGHUP : 터미널에서 잃어버렸을 때 발생한다. 콘솔창을 닫을 때 발생한다.
SIGABRT : 프로그램의 비정상 종료시 발생한다
SIGINT : 컨트롤 + c를 입력할 때 발생한다. SIGINT 시그널이 프로세스로 전달되면 프로세스가 종료된다.
SIGIO : 비동기적인 입출력이 발생했을 때 사용한다.
SIGKILL : 프로세스를 강제로 죽이기 위해 사용한다
SIGEGV : 운영체제가 프로세스를 강제로 종료시킬 때 사용한다.
SIGSTOP : 프로세스를 일시 중단 할 때 사용한다.
SIGCONT : 멈춘 프로세스를 다시 움직일 때 사용한다.
시그널의 기본 행동
종료 : 프로세스를 종료한다.
무시 : 기본적으로 무시되는 시그널은 없다고 봐도 된다. 하지만 개발자는 특정 시그널을 무시하도록 할 수 있다.
함수 실행 : 특정 시그널을 받았을 때 이를 처리할 함수를 호출할 수 있다.
시그널 함수를 이용해서 시그널 제어하기
#include <signal.h>
sighandler_t signal(int signum, sighandler_t handler);
signum : 제어하기 원하는 시그널의 번호다.
handler : signum 번호를 가지는 시그널이 발생 했을 때, 실행한 시그널 핸들러다.
-SIG_IGN : 시그널을 무시한다. SIGKILL 과 SIGSTOP는 무시할 수 없다.
-SIG_DFL : 시그널의 기본 행동을 한다.
역시 위의 내용은 각각 소스코드가 있다.
나의 깃허브에서 다운 받아서 해보길...;;;
소스 코드 받기
뇌자극 TCP_IP 프로그래밍 11강 요약 1
1. IPC에 대하여
컴퓨터 내부의 프로세스들이 서로 데이터를 주고 받을 수 있는 것
IPC는 그 자체가 도구가 아닌 '내부 통신을 위한 여러 도구'의 총칭이다
IPC 설비에서 제공하는 도구는 다음과 같다
파이프, 메시지 큐, 네임드 파이프, 공유 메모리,
메모리 맵, 시그널, 세마포어, 유닉스 도메인 소켓
2. 파이프 - 익명 파이프와 네임드 파이프
IPC 설비가 제공하는 내부통신 도구 중 가장 널리 사용되는 도구는 파이프다.
파이프는 프로세스 간의 파이프 모양의 단반향 통신 선로를 개설한다.
읽기 쓰기를 모두 수행하려면 두 개의 파이프를 만들어야 한다.
단방향 통신 : 한 반향으로만 통신 가능. 파이프
반이중 통신 : 양쪽 방향으로 통신이 가능 하지만, 동신 퉁신은 불가능. 무전기
전이중 통신 : 동시에 양쪽 방향을 통신 가능. 전화기, 소켓
파이프 만들기
#include <unistd.h>
int pipe(int filedes[2]);
fileds : pipe 함수를 실행하면 매개변수로 파이프를 가리키는 파일 지정 번호를 돌려준다.
filedes[0] 은 읽기 전용, filedes[1] 는 쓰기 전용 파이프다.
이 두 파이프로 전이중 통신을 구현한다.
파이프는 부모 프로세스 <-> 자식 프로세스 통신을 위해 사용된다.
파이프 예제
//pip 함수로 파이프를 만든다.
if (pipe(fd) < 0)
{
perror("pipe error : ");
return 1;
}
//fork 함수로 자식 프로세스를 생성한다.
if ((pid = fork()) < 0)
{
return 1;
}
// 만약 자식프로세스라면 파이프에 자신의 PID 정보를 쓴다.
else if (pid == 0)
{
close(fd[0]); //읽기 전용 파이프 닫기
while(1)
{
i++;
write(fd[1], (void *)&i, sizeof(i)); //쓰기 전용 파이프
sleep(1);
}
}
// 만약 부모프로세스라면 파이프에서 데이터를 읽어 들인다.
else
{
close(fd[1]); //쓰기 전용 파이프 닫기
while(1)
{
read(fd[0], (void *)&buf, sizeof(buf)); //읽기 전용 파이프
printf("> %d\n", buf);
}
}
익명 파이프와 네임드 파이프
내부 통신을 위해서 사용할 수 있는 가장 간단한 도구는 파일이다.
파이프와 파일의 가장 큰 차이는 '이름'을 가지고 있는지 여부다.
pipe 함수로 만든 파이프는 이름을 가지고 있지 않기에
외부의 프로세스는 이 파이프를 사용할 수 없다.
파이프는 부모/자식 프로세스 간에만 사용할 수 있다.
외부 프로세스와 파이프 통신을 하기 위해서는 파이프에 이름을 지어주면 된다.
네임드 파이프의 생성
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname : 네임드 파이프의 이름이다. 네임드 파이프는 파일 형태다. 파일이므로 이름만으로도 어디에서나 파이프에 접근할 수 있다.
mode : 파일이므로 권한을 설정해야 한다.
네임드 파이프의 예제
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define MAXLINE 1024
int main(int argc, char **argv)
{
int rfd, wfd;
char buf[MAXLINE];
//mkfifo 함수로 네임드 파이프를 만든다.
mkfifo("/tmp/myfifo_r", S_IRUSR|S_IWUSR); //읽기용 네임드 파이프
mkfifo("/tmp/myfifo_w", S_IRUSR|S_IWUSR); //쓰기용 네임드 파이프
//네임드 파이프를 연다
if( (rfd = open("/tmp/myfifo_r", O_RDWR)) == -1)
{
perror("rfd error");
return 0;
}
if ( (wfd = open("/tmp/myfifo_w", O_RDWR)) == -1)
{
perror("wfd error");
return 0;
}
//읽기 네임드 파이프에서 데이터를 읽어서 쓰기 네임드 파이프에 쓴다.
while(1)
{
memset(buf, 0x00, MAXLINE);
if(read(rfd,buf,MAXLINE) < 0)
{
perror("Read Error");
return 1;
}
printf("Read : %s", buf);
write(wfd, buf, MAXLINE);
//쓰기 버퍼를 비워서 즉시 파일에 쓰도록 한다. 파일 내에서 위치를 재정의 하는 함수다.
lseek(wfd, 0, SEEK_SET);
}
}
3. 유닉스 도메인 소켓
IPC와 네트워크 통신은 통신 영역만 다를 뿐 본질적으로 차이가 없다.
소켓으로도 내부 프로세스 간 통신을 할 수 있다.
소켓 함수에서 첫번째 매개 변수를 바꾸면 가능하다
int socket(int domain, int type, int protocol);
struct sockaddr_un sockaddr;
socket (AF_UNIX, SOCK_STREAM, 0);
strncpy(serveraddr.sun_path, "filename", 8);
유닉스 도메인 소켓의 용도
유닉스 도메인 소켓 방식은 소켓 함수 뿐만 아니라 소켓 프로그래밍 모델까지 그대로 사용할 수 있다. 간단히 서버/클라이언트 모델을 구현할 수 있다.
대신, 파이프에 비해서 성능이 약간 떨어진다.
데이터를 가공하기 위한 여러 프로세스로 구성된 미들웨어와 같은 큰 규모의 프로그램에서 내부 데이터 통신을 원한다면 유닉스 도메인 소켓을 사용하길 권장한다.
소스 코드 보기
컴퓨터 내부의 프로세스들이 서로 데이터를 주고 받을 수 있는 것
IPC는 그 자체가 도구가 아닌 '내부 통신을 위한 여러 도구'의 총칭이다
IPC 설비에서 제공하는 도구는 다음과 같다
파이프, 메시지 큐, 네임드 파이프, 공유 메모리,
메모리 맵, 시그널, 세마포어, 유닉스 도메인 소켓
2. 파이프 - 익명 파이프와 네임드 파이프
IPC 설비가 제공하는 내부통신 도구 중 가장 널리 사용되는 도구는 파이프다.
파이프는 프로세스 간의 파이프 모양의 단반향 통신 선로를 개설한다.
읽기 쓰기를 모두 수행하려면 두 개의 파이프를 만들어야 한다.
단방향 통신 : 한 반향으로만 통신 가능. 파이프
반이중 통신 : 양쪽 방향으로 통신이 가능 하지만, 동신 퉁신은 불가능. 무전기
전이중 통신 : 동시에 양쪽 방향을 통신 가능. 전화기, 소켓
파이프 만들기
#include <unistd.h>
int pipe(int filedes[2]);
fileds : pipe 함수를 실행하면 매개변수로 파이프를 가리키는 파일 지정 번호를 돌려준다.
filedes[0] 은 읽기 전용, filedes[1] 는 쓰기 전용 파이프다.
이 두 파이프로 전이중 통신을 구현한다.
파이프는 부모 프로세스 <-> 자식 프로세스 통신을 위해 사용된다.
파이프 예제
//pip 함수로 파이프를 만든다.
if (pipe(fd) < 0)
{
perror("pipe error : ");
return 1;
}
//fork 함수로 자식 프로세스를 생성한다.
if ((pid = fork()) < 0)
{
return 1;
}
// 만약 자식프로세스라면 파이프에 자신의 PID 정보를 쓴다.
else if (pid == 0)
{
close(fd[0]); //읽기 전용 파이프 닫기
while(1)
{
i++;
write(fd[1], (void *)&i, sizeof(i)); //쓰기 전용 파이프
sleep(1);
}
}
// 만약 부모프로세스라면 파이프에서 데이터를 읽어 들인다.
else
{
close(fd[1]); //쓰기 전용 파이프 닫기
while(1)
{
read(fd[0], (void *)&buf, sizeof(buf)); //읽기 전용 파이프
printf("> %d\n", buf);
}
}
익명 파이프와 네임드 파이프
내부 통신을 위해서 사용할 수 있는 가장 간단한 도구는 파일이다.
파이프와 파일의 가장 큰 차이는 '이름'을 가지고 있는지 여부다.
pipe 함수로 만든 파이프는 이름을 가지고 있지 않기에
외부의 프로세스는 이 파이프를 사용할 수 없다.
파이프는 부모/자식 프로세스 간에만 사용할 수 있다.
외부 프로세스와 파이프 통신을 하기 위해서는 파이프에 이름을 지어주면 된다.
네임드 파이프의 생성
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname : 네임드 파이프의 이름이다. 네임드 파이프는 파일 형태다. 파일이므로 이름만으로도 어디에서나 파이프에 접근할 수 있다.
mode : 파일이므로 권한을 설정해야 한다.
네임드 파이프의 예제
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#define MAXLINE 1024
int main(int argc, char **argv)
{
int rfd, wfd;
char buf[MAXLINE];
//mkfifo 함수로 네임드 파이프를 만든다.
mkfifo("/tmp/myfifo_r", S_IRUSR|S_IWUSR); //읽기용 네임드 파이프
mkfifo("/tmp/myfifo_w", S_IRUSR|S_IWUSR); //쓰기용 네임드 파이프
//네임드 파이프를 연다
if( (rfd = open("/tmp/myfifo_r", O_RDWR)) == -1)
{
perror("rfd error");
return 0;
}
if ( (wfd = open("/tmp/myfifo_w", O_RDWR)) == -1)
{
perror("wfd error");
return 0;
}
//읽기 네임드 파이프에서 데이터를 읽어서 쓰기 네임드 파이프에 쓴다.
while(1)
{
memset(buf, 0x00, MAXLINE);
if(read(rfd,buf,MAXLINE) < 0)
{
perror("Read Error");
return 1;
}
printf("Read : %s", buf);
write(wfd, buf, MAXLINE);
//쓰기 버퍼를 비워서 즉시 파일에 쓰도록 한다. 파일 내에서 위치를 재정의 하는 함수다.
lseek(wfd, 0, SEEK_SET);
}
}
3. 유닉스 도메인 소켓
IPC와 네트워크 통신은 통신 영역만 다를 뿐 본질적으로 차이가 없다.
소켓으로도 내부 프로세스 간 통신을 할 수 있다.
소켓 함수에서 첫번째 매개 변수를 바꾸면 가능하다
int socket(int domain, int type, int protocol);
struct sockaddr_un sockaddr;
socket (AF_UNIX, SOCK_STREAM, 0);
strncpy(serveraddr.sun_path, "filename", 8);
유닉스 도메인 소켓의 용도
유닉스 도메인 소켓 방식은 소켓 함수 뿐만 아니라 소켓 프로그래밍 모델까지 그대로 사용할 수 있다. 간단히 서버/클라이언트 모델을 구현할 수 있다.
대신, 파이프에 비해서 성능이 약간 떨어진다.
데이터를 가공하기 위한 여러 프로세스로 구성된 미들웨어와 같은 큰 규모의 프로그램에서 내부 데이터 통신을 원한다면 유닉스 도메인 소켓을 사용하길 권장한다.
소스 코드 보기
뇌자극 TCP_IP 프로그래밍 10강 요약
멀티 프로세스 소켓 프로그래밍
1. 프로세스란?
프로그램을 실행할 때 만들어지는 것이 프로세스라고 한다.
프로그램을 실행하면 운영체제는 하드디스크에 저장된 프로그램을
그대로 실행하지 않고 프로그램을 메모리에 복사하고
복사한 프로그램의 이미지를 실행한다.
멀티 프로세스 운영체제는 동시에 여러 프로세스를 관리해야 하기에
각 프로세스에 유일한 일련번호(PID)를 부여한다.
2. 멀티 프로세스
멀티 프로세싱이 동시에 여러 프로세스를 운용한다는 의미를 가지고 있지만
실제로는 같은 시간에 여러 프로세스가 동시에 실행되지는 않는다.
아주 짧은 시간 동안 프로세스를 번갈아 가면서 실행해서 동시성을 제공한다.
3. 멀티 프로세스 프로그래밍
fork 함수로 프로세스 복사하기
리눅스는 fork 함수로 프로세스를 복사할 수 있다.
fork 함수는 새로운 프로세스를 만드는 함수가 아니라 복사하는 함수이기에 실행까지는 되지 않는다.
fork 함수
#include <unistd.h>
pid_t fork(void);
fork 함수는 매개변수가 없다. fork함수를 호출하면 다음과 같은 값을 반환한다.
pid_t < 0 : fork 함수 실행 실패
pid_t == 0 : 자식 프로세스 생성 성공(자식 프로세스에는 0을 반환한다)
pid_t > 0 : 자식 프로세스 성공이이면 자식 프로세스의 PID 값이다.
fork 함수 들여다 보기
fork 함수를 호출하면 자식 프로세스를 만든다.
이때 명령과 데이터를 부모 프로세스로부터 상속 받는다.
지역 변수 값 및 전역 변수 값 : 복사된다.
열린 파일: 소켓을 포함한 모든 열린 파일의 파일 지정번호가 복사된다
시그널 : 상태를 알려주기 위해 사용한다.
execl 함수로 프로그램 실행하기
fork 함수를 자신을 복사한 프로세스를 만든다.
하지만 다른 프로그램을 실행 시킬 수는 없다.
외부 프로그램을 실행 시킬 수 있는 함수가 execl 함수다.
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
-path : 경로를 포함한 실행 프로그램의 이름
-arg : 프로그램의 실행 인자
fork 함수와 execl 함수로 새로운 프로세스 실행하기
fork 함수로 자식 프로세스를 만들고,
자식 프로세스에서 execl 함수를 실행하면 새로운 프로세스를 실행할 수 있다.
프로그램 작동 시나리오는 다음과 같다.
1. 사용자 명령을 읽기 위한 프롬프트를 출력한다.
2. 키보드로 명령을 받는다.
3. fork 함수로 자식 프로세스를 만든다. 부모 프로세스는 자식 프로세스가 종료되는걸 기다린다.
4. execl 함수로 외부 프로그램(입력받은 명령)을 실행한다.
5. 외부 프로그램이 종료된다. 즉, 자식 프로세스가 종료된다.
6. 부모 프로세스는 자식 프로세스가 종료된 걸 확인하고, 다시 프롬프트를 띄운다.
자식 프로세스 기다리기
자식 프로세스의 생명이 부모 프로세스의 생명보다 대부분 짧다.
그러므로 부모 프로세스는 자식 프로세스의 종료를 기다리는 일이 중요하다.
프로세스는 종료 상태를 가진다.
프로세스가 종료가 되어야 자신의 모든 자원을 운영체제에게 되돌려 준다.
자식 프로세스의 종료 값은 wait 함수와 waitpid 함수로 얻을 수 있다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
부모 프로세스는 자식 프로세스가 종료되면 그 값을 얻어온다.
waitpid 함수는 기다리기 원하는 프로세스의 pid를 지정할 수 있다.
4. 프로세스 관계
PID는 프로세스를 구분할 수 있는 유일한 번호로 시스템에서 유일하다.
프로세스는 독립된 프로세스이면서 동시에 집단에 포함된다.
프로세스 간의 관계가 형성된다.
부모와 자식 프로세스 간의 관계를 그룹으로 묶인다.
그룹은 다시 세션으로 묶인다.
만들어진 프로세스는 자신의 PID로 전체 시스템에서
유일한 프로세스임을 확인하는 동시에
부모/자식간의 관계와 그룹과의 관계를 맺게 된다.
5. 멀티 프로세스 네트워크 프로그램 개발
1) socket() -> bind() -> listen()까지는 기존 서버와 동일하다.
2) accept 함수를 호출한다.
3) accept 함수가 반환하면
4) fork 함수를 호출한다.
5) 부모 프로세스는 2번으로 간다.
6) 자식 프로세스는 7번으로 간다.
7) 클라이언트로부터 데이터를 읽는다.
8) 클라이언트에 데이터를 쓴다.
소스코드
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#define MAXLINE 1024
#define PORTNUM 3600
int main(int argc, char **argv)
{
int listen_fd, client_fd;
pid_t pid;
socklen_t addrlen;
int readn;
char buf[MAXLINE];
struct sockaddr_in client_addr, server_addr;
if( (listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return 1;
}
memset((void *)&server_addr, 0x00, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORTNUM);
if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==-1)
{
perror("bind error");
return 1;
}
if(listen(listen_fd, 5) == -1)
{
perror("listen error");
return 1;
}
//자식 프로새스의 시그널을 무시한다.
signal(SIGCHLD, SIG_IGN);
//부모 프로세스는 루프의 처음으로 되돌아가서
//새로운 클라이언트가 연결되어 있는지 확인한다.
while(1)
{
addrlen = sizeof(client_addr);
//accept 함수로 클라이언트 연결을 가져온다.
client_fd = accept(listen_fd,
(struct sockaddr *)&client_addr, &addrlen);
if(client_fd == -1)
{
printf("accept error\n");
break;
}
//fork함수로 자식 프로세스를 만든다.
pid = fork();
//자식 프로세스가 실행할 코드다.
//accept 함수로 만든 연결 소켓은 자식 프로세스에 그대로 상속된다.
//자식 프로세스는 상속 받은 연결 소켓으로 클라이언트와 통신 하게 된다.
if(pid == 0)
{
memset(buf, 0x00, MAXLINE);
while((readn = read(client_fd, buf, MAXLINE)) > 0)
{
printf("Read Data %s(%d) : %s",
inet_ntoa(client_addr.sin_addr),
client_addr.sin_port,
buf);
write(client_fd, buf, strlen(buf));
memset(buf, 0x00, MAXLINE);
}
close(client_fd);
return 0;
}
//부모 프로세스가 실행하는 코드다.
//accept 함수로 생성된 연결 소켓은 자식 프로세스만 필요할 뿐,
//부모 프로세스가 필요 없다. 그러므로 연결 소켓을 닫아준다.
else if(pid>0)
close(client_fd);
}
return 0;
}
1. 프로세스란?
프로그램을 실행할 때 만들어지는 것이 프로세스라고 한다.
프로그램을 실행하면 운영체제는 하드디스크에 저장된 프로그램을
그대로 실행하지 않고 프로그램을 메모리에 복사하고
복사한 프로그램의 이미지를 실행한다.
멀티 프로세스 운영체제는 동시에 여러 프로세스를 관리해야 하기에
각 프로세스에 유일한 일련번호(PID)를 부여한다.
2. 멀티 프로세스
멀티 프로세싱이 동시에 여러 프로세스를 운용한다는 의미를 가지고 있지만
실제로는 같은 시간에 여러 프로세스가 동시에 실행되지는 않는다.
아주 짧은 시간 동안 프로세스를 번갈아 가면서 실행해서 동시성을 제공한다.
3. 멀티 프로세스 프로그래밍
fork 함수로 프로세스 복사하기
리눅스는 fork 함수로 프로세스를 복사할 수 있다.
fork 함수는 새로운 프로세스를 만드는 함수가 아니라 복사하는 함수이기에 실행까지는 되지 않는다.
fork 함수
#include <unistd.h>
pid_t fork(void);
fork 함수는 매개변수가 없다. fork함수를 호출하면 다음과 같은 값을 반환한다.
pid_t < 0 : fork 함수 실행 실패
pid_t == 0 : 자식 프로세스 생성 성공(자식 프로세스에는 0을 반환한다)
pid_t > 0 : 자식 프로세스 성공이이면 자식 프로세스의 PID 값이다.
fork 함수 들여다 보기
fork 함수를 호출하면 자식 프로세스를 만든다.
이때 명령과 데이터를 부모 프로세스로부터 상속 받는다.
지역 변수 값 및 전역 변수 값 : 복사된다.
열린 파일: 소켓을 포함한 모든 열린 파일의 파일 지정번호가 복사된다
시그널 : 상태를 알려주기 위해 사용한다.
execl 함수로 프로그램 실행하기
fork 함수를 자신을 복사한 프로세스를 만든다.
하지만 다른 프로그램을 실행 시킬 수는 없다.
외부 프로그램을 실행 시킬 수 있는 함수가 execl 함수다.
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
-path : 경로를 포함한 실행 프로그램의 이름
-arg : 프로그램의 실행 인자
fork 함수와 execl 함수로 새로운 프로세스 실행하기
fork 함수로 자식 프로세스를 만들고,
자식 프로세스에서 execl 함수를 실행하면 새로운 프로세스를 실행할 수 있다.
프로그램 작동 시나리오는 다음과 같다.
1. 사용자 명령을 읽기 위한 프롬프트를 출력한다.
2. 키보드로 명령을 받는다.
3. fork 함수로 자식 프로세스를 만든다. 부모 프로세스는 자식 프로세스가 종료되는걸 기다린다.
4. execl 함수로 외부 프로그램(입력받은 명령)을 실행한다.
5. 외부 프로그램이 종료된다. 즉, 자식 프로세스가 종료된다.
6. 부모 프로세스는 자식 프로세스가 종료된 걸 확인하고, 다시 프롬프트를 띄운다.
자식 프로세스 기다리기
자식 프로세스의 생명이 부모 프로세스의 생명보다 대부분 짧다.
그러므로 부모 프로세스는 자식 프로세스의 종료를 기다리는 일이 중요하다.
프로세스는 종료 상태를 가진다.
프로세스가 종료가 되어야 자신의 모든 자원을 운영체제에게 되돌려 준다.
자식 프로세스의 종료 값은 wait 함수와 waitpid 함수로 얻을 수 있다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
부모 프로세스는 자식 프로세스가 종료되면 그 값을 얻어온다.
waitpid 함수는 기다리기 원하는 프로세스의 pid를 지정할 수 있다.
4. 프로세스 관계
PID는 프로세스를 구분할 수 있는 유일한 번호로 시스템에서 유일하다.
프로세스는 독립된 프로세스이면서 동시에 집단에 포함된다.
프로세스 간의 관계가 형성된다.
부모와 자식 프로세스 간의 관계를 그룹으로 묶인다.
그룹은 다시 세션으로 묶인다.
만들어진 프로세스는 자신의 PID로 전체 시스템에서
유일한 프로세스임을 확인하는 동시에
부모/자식간의 관계와 그룹과의 관계를 맺게 된다.
5. 멀티 프로세스 네트워크 프로그램 개발
1) socket() -> bind() -> listen()까지는 기존 서버와 동일하다.
2) accept 함수를 호출한다.
3) accept 함수가 반환하면
4) fork 함수를 호출한다.
5) 부모 프로세스는 2번으로 간다.
6) 자식 프로세스는 7번으로 간다.
7) 클라이언트로부터 데이터를 읽는다.
8) 클라이언트에 데이터를 쓴다.
소스코드
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#define MAXLINE 1024
#define PORTNUM 3600
int main(int argc, char **argv)
{
int listen_fd, client_fd;
pid_t pid;
socklen_t addrlen;
int readn;
char buf[MAXLINE];
struct sockaddr_in client_addr, server_addr;
if( (listen_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
return 1;
}
memset((void *)&server_addr, 0x00, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORTNUM);
if(bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) ==-1)
{
perror("bind error");
return 1;
}
if(listen(listen_fd, 5) == -1)
{
perror("listen error");
return 1;
}
//자식 프로새스의 시그널을 무시한다.
signal(SIGCHLD, SIG_IGN);
//부모 프로세스는 루프의 처음으로 되돌아가서
//새로운 클라이언트가 연결되어 있는지 확인한다.
while(1)
{
addrlen = sizeof(client_addr);
//accept 함수로 클라이언트 연결을 가져온다.
client_fd = accept(listen_fd,
(struct sockaddr *)&client_addr, &addrlen);
if(client_fd == -1)
{
printf("accept error\n");
break;
}
//fork함수로 자식 프로세스를 만든다.
pid = fork();
//자식 프로세스가 실행할 코드다.
//accept 함수로 만든 연결 소켓은 자식 프로세스에 그대로 상속된다.
//자식 프로세스는 상속 받은 연결 소켓으로 클라이언트와 통신 하게 된다.
if(pid == 0)
{
memset(buf, 0x00, MAXLINE);
while((readn = read(client_fd, buf, MAXLINE)) > 0)
{
printf("Read Data %s(%d) : %s",
inet_ntoa(client_addr.sin_addr),
client_addr.sin_port,
buf);
write(client_fd, buf, strlen(buf));
memset(buf, 0x00, MAXLINE);
}
close(client_fd);
return 0;
}
//부모 프로세스가 실행하는 코드다.
//accept 함수로 생성된 연결 소켓은 자식 프로세스만 필요할 뿐,
//부모 프로세스가 필요 없다. 그러므로 연결 소켓을 닫아준다.
else if(pid>0)
close(client_fd);
}
return 0;
}
6. 멀티 프로세스 서버의 장점과 단점
장점
1) 단순함 : 프로그램의 흐름에서 적절한 위치에 fork함수를 호출해주면 끝이다.
2) 안전성 : 부모 프로세스와 자식 프로세스는 완전히 독립된다. 어떤 프로세스가 잘못된 동작을 하더라도 다른 프로세스로의 영향을 최소화 할 수 있다.
단점
1) 성능 : fork 함수를 호출하면 부모 프로세스를 그대로 복사한 프로세스가 만들어진다.
이는 성능 저하를 일으킨다.
2) 프로세스간의 통신 문제 : 부모 프로세스와 자식 프로세스가 모두 독립되어 있다 보니 데이터 교환이 쉽지 않다.
역시 이 부분도 깃 허브에 소스코드를 올렸다.
뇌자극 TCP_IP 프로그래밍 9강 요약
UDP - 품질보다 연속성을 중시하는 서비스
UDP 특징
- 신뢰할 수 없음 : UDP 는 메세지를 보내고 잊어 버린다.
- 정돈 되지 않음 : UDP는 일련번호가 없다. 2개 이상의 패킷이 전달되면 정돈 할 수 없다.
- 가벼움 : 신뢰성을 포기한 대신 가볍게 움직일 수 있다.
소켓 생성
socket(AF_INET, SOCK_DGRAM, 0);
데이터 쓰기
#include <sys/type.h>
#include <sys/socket.h>
int sendto(int sockfd, const void *msg, size_t len, int flags,
const struct sockaddr *to, socklen_t tolen);
-sockfd : 소켓 함수로 생성한 소켓 지정번호
-msg : 전송할 데이터
-len : 전송할 데이터의 크기
-flags : 데이터 전송방식, 일반적으로 0을 사용한다.
-to : 보낼 인터넷 주소와 포트 번호를 포함하는 sockaddr 구조체
-tolen : 인터넷 주소 구조체 sockaddr의 크기
데이터 읽기
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, size_t len, int flags,
const struct sockaddr *from, socklen_t fromlen);
-sockfd : 소켓 지정 번호
-buf : 읽은 데이터를 저장할 변수
-len : 읽을 데이터의 크기
-flags : 데이터 전송 방식, 일반적으로 0을 사용한다.
-from : 데이터를 보낸 인터넷 주소와 포트 번호를 포함
-fromlen : sockaddr 구조체의 크기
소스코드는 내 깃허브에서 다운 받을 수 있다.
깃허브 방문하기
UDP 특징
- 신뢰할 수 없음 : UDP 는 메세지를 보내고 잊어 버린다.
- 정돈 되지 않음 : UDP는 일련번호가 없다. 2개 이상의 패킷이 전달되면 정돈 할 수 없다.
- 가벼움 : 신뢰성을 포기한 대신 가볍게 움직일 수 있다.
소켓 생성
socket(AF_INET, SOCK_DGRAM, 0);
데이터 쓰기
#include <sys/type.h>
#include <sys/socket.h>
int sendto(int sockfd, const void *msg, size_t len, int flags,
const struct sockaddr *to, socklen_t tolen);
-sockfd : 소켓 함수로 생성한 소켓 지정번호
-msg : 전송할 데이터
-len : 전송할 데이터의 크기
-flags : 데이터 전송방식, 일반적으로 0을 사용한다.
-to : 보낼 인터넷 주소와 포트 번호를 포함하는 sockaddr 구조체
-tolen : 인터넷 주소 구조체 sockaddr의 크기
데이터 읽기
#include <sys/types.h>
#include <sys/socket.h>
int recvfrom(int sockfd, void *buf, size_t len, int flags,
const struct sockaddr *from, socklen_t fromlen);
-sockfd : 소켓 지정 번호
-buf : 읽은 데이터를 저장할 변수
-len : 읽을 데이터의 크기
-flags : 데이터 전송 방식, 일반적으로 0을 사용한다.
-from : 데이터를 보낸 인터넷 주소와 포트 번호를 포함
-fromlen : sockaddr 구조체의 크기
소스코드는 내 깃허브에서 다운 받을 수 있다.
깃허브 방문하기
2016년 5월 14일 토요일
뇌자극 TCP_IP 프로그래밍 8강 요약
-TCP의 특징-
연결 지향 - 두 컴퓨터를 연결하는 전용의 연결회선을 만든다.(세션을 생성한다)
신뢰성 있는 데이터 교환 - 전송한 데이터를 제대로 받았는지 확인 한다.
만약 실패하면 재전송 한다.
전 이중 통신 - 읽고 쓰기를 모두 할 수 있다.
-TCP의 과정-
1. 클라이언트가 패킷을 전송한다.
2. 패킷을 받은 서버는 일련 번호에 1을 더한 값을 ACK 값을 설정해서 전송한다.
3. 클라이언트는 서버의 ACK 값을 읽어서 패킷이 제대로 전달된 것을 알게 된다
4. 1~3을 반복하면서 데이터를 주고 받는다.
-소스코드-
아래 코드는 "뇌를 자극하는 TCP/IP 소켓 프로그래밍" 코드다.
꼭 책을 사서 공부하자~ 이 블로그는 공부한 내용을 기억하기 위해 요약 한 것이다.
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define PORT 3600
//주고 받을 데이터를 구조화 하기 위해 cal_data 구조체를 정의 한다.
struct cal_data
{
int left_num;
int right_num;
char op;
int result;
short int error;
};
int main(int argc, char **argv)
{
struct sockaddr_in client_addr, sock_addr;
int listen_sockfd, client_sockfd;
int addr_len;
struct cal_data rdata;
int left_num, right_num;
short int cal_result;
//IP와 연결지향 TCP 기반 통신을 위한 듣기 소켓을 생성한다.
if( (listen_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Error ");
return 1;
}
//듣기 소켓을 위한 주소 영역, 기다릴 주소와 포트 번호를 생성한다.
memset((void *)&sock_addr, 0x00, sizeof(sock_addr));
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sock_addr.sin_port = htons(PORT);
//bind 함수로 소켓 특성을 묶어준다.
if( bind(listen_sockfd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1)
{
perror("Error ");
return 1;
}
//수신 대기열을 생성한다.
if(listen(listen_sockfd, 5) == -1)
{
perror("Error ");
return 1;
}
for(;;)
{
addr_len = sizeof(client_addr);
//accept 함수로 수신 대기열에 클라이언트 연결이 있는지 확인한다.
client_sockfd = accept(listen_sockfd,
(struct sockaddr *)&client_addr, &addr_len);
if(client_sockfd == -1)
{
perror("Error ");
return 1;
}
//read 함수로 소켓에서 데이터를 읽어온다.
read(client_sockfd, (void *)&rdata, sizeof(rdata));
rdata.error = 0;
//읽어온 데이터는 네이퉈크 바이트 순서를 따른다.
//ntohs 함수로 호스트 바이트 순서로 변환한다.
left_num = ntohs(rdata.left_num);
right_num = ntohs(rdata.right_num);
//op 값을 확인해서 필요한 사칙연산 루틴을 실행한다.
//나누기(/)에서 분모가 되는 오른쪽 피연산자가 0이면 error 값을 설정한다.
switch(rdata.op)
{
case '+':
cal_result = left_num + right_num;
break;
case '-':
cal_result = left_num - right_num;
break;
case '*':
cal_result = left_num * right_num;
break;
case '/':
if(right_num == 0)
{
rdata.error = 2;
break;
}
cal_result = left_num / right_num;
break;
default:
rdata.error = 1;
}
//계산 값과 에러 값을 네트워크 바이트 순서에 맞게 변환한다.
rdata.result = htonl(cal_result);
rdata.error = htons(cal_result);
//write 함수로 계산 결과를 클라이언트에 전송한다.
write(client_sockfd, (void *)&rdata, sizeof(rdata));
close(client_sockfd);
}
close(listen_sockfd);
return 0;
}
연결 지향 - 두 컴퓨터를 연결하는 전용의 연결회선을 만든다.(세션을 생성한다)
신뢰성 있는 데이터 교환 - 전송한 데이터를 제대로 받았는지 확인 한다.
만약 실패하면 재전송 한다.
전 이중 통신 - 읽고 쓰기를 모두 할 수 있다.
-TCP의 과정-
1. 클라이언트가 패킷을 전송한다.
2. 패킷을 받은 서버는 일련 번호에 1을 더한 값을 ACK 값을 설정해서 전송한다.
3. 클라이언트는 서버의 ACK 값을 읽어서 패킷이 제대로 전달된 것을 알게 된다
4. 1~3을 반복하면서 데이터를 주고 받는다.
-소스코드-
아래 코드는 "뇌를 자극하는 TCP/IP 소켓 프로그래밍" 코드다.
꼭 책을 사서 공부하자~ 이 블로그는 공부한 내용을 기억하기 위해 요약 한 것이다.
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/time.h>
#define PORT 3600
//주고 받을 데이터를 구조화 하기 위해 cal_data 구조체를 정의 한다.
struct cal_data
{
int left_num;
int right_num;
char op;
int result;
short int error;
};
int main(int argc, char **argv)
{
struct sockaddr_in client_addr, sock_addr;
int listen_sockfd, client_sockfd;
int addr_len;
struct cal_data rdata;
int left_num, right_num;
short int cal_result;
//IP와 연결지향 TCP 기반 통신을 위한 듣기 소켓을 생성한다.
if( (listen_sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Error ");
return 1;
}
//듣기 소켓을 위한 주소 영역, 기다릴 주소와 포트 번호를 생성한다.
memset((void *)&sock_addr, 0x00, sizeof(sock_addr));
sock_addr.sin_family = AF_INET;
sock_addr.sin_addr.s_addr = htonl(INADDR_ANY);
sock_addr.sin_port = htons(PORT);
//bind 함수로 소켓 특성을 묶어준다.
if( bind(listen_sockfd, (struct sockaddr *)&sock_addr, sizeof(sock_addr)) == -1)
{
perror("Error ");
return 1;
}
//수신 대기열을 생성한다.
if(listen(listen_sockfd, 5) == -1)
{
perror("Error ");
return 1;
}
for(;;)
{
addr_len = sizeof(client_addr);
//accept 함수로 수신 대기열에 클라이언트 연결이 있는지 확인한다.
client_sockfd = accept(listen_sockfd,
(struct sockaddr *)&client_addr, &addr_len);
if(client_sockfd == -1)
{
perror("Error ");
return 1;
}
//read 함수로 소켓에서 데이터를 읽어온다.
read(client_sockfd, (void *)&rdata, sizeof(rdata));
rdata.error = 0;
//읽어온 데이터는 네이퉈크 바이트 순서를 따른다.
//ntohs 함수로 호스트 바이트 순서로 변환한다.
left_num = ntohs(rdata.left_num);
right_num = ntohs(rdata.right_num);
//op 값을 확인해서 필요한 사칙연산 루틴을 실행한다.
//나누기(/)에서 분모가 되는 오른쪽 피연산자가 0이면 error 값을 설정한다.
switch(rdata.op)
{
case '+':
cal_result = left_num + right_num;
break;
case '-':
cal_result = left_num - right_num;
break;
case '*':
cal_result = left_num * right_num;
break;
case '/':
if(right_num == 0)
{
rdata.error = 2;
break;
}
cal_result = left_num / right_num;
break;
default:
rdata.error = 1;
}
//계산 값과 에러 값을 네트워크 바이트 순서에 맞게 변환한다.
rdata.result = htonl(cal_result);
rdata.error = htons(cal_result);
//write 함수로 계산 결과를 클라이언트에 전송한다.
write(client_sockfd, (void *)&rdata, sizeof(rdata));
close(client_sockfd);
}
close(listen_sockfd);
return 0;
}
피드 구독하기:
글 (Atom)