📌 Internet Relay Chat
의 약자로 텍스트 기반 실시간 채팅 시스템이다. 채널이라 불리는 그룹 통신을 위해 설계되었지만 1:1 채팅 및 파일 공유 또한 지원한다.
현 작업이 block(차단, 대기) 되느냐 아니야에 따라 다른 작업을 수행할 수 있는지에 대한 관점
1. 블로킹(Block)
: 함수 A와 B가 있고, 실행 순서가 A → B라고 가정할 때, B는 A가 끝날 때까지 호출되지 못 함
: 호출된 함수가 자신이 할 일을 모두 마칠 때까지 제어권을 계속 가지고 있고 자신을 호출한 함수에게 바로 return하지 않으면 Block
2. 논블로킹(Non-Block)
: 함수 A, B가 있고, 순서는 A → B라고 가정한다.
: 메인 함수가 A를 호출하는 경우 A는 실행되며, 바로 return하며 B가 실행된다.
→ A의 실행이 모두 끝난 후가 아닌 시작하자마자 return한다는 의미
: 호출된 함수가 자신이 할 일을 마치지 않았음에도 바로 제어권을 return하여 자신을 호출한 함수가 다른 일을 진행할 수 있도록 하면 Non-Block
요청한 작업의 완료 여부를 판단, 작업을 순차적으로 수행할 지에 대한 관점
1. 동기(Synchronous)
: 요청한 작업에 대해 완료 여부를 확인하여 순서대로 실행한다.
→ 요청한 작업의 순서가 지켜짐
: Block의 경우 함수의 결과 여부를 확인하지 않는 반면, 동기의 경우 확인을 함.
: 호출된 함수의 수행 결과 및 종료를 호출된 함수 뿐만 아니라 호출한 함수도 확인하는 경우 동기
2. 비동기(Asynchronous)
: 요청한 작업에 대해 완료 여부를 따지지 않고 실행한다.
→ 요청한 작업의 순서가 지켜지지 않을 수 있음
→ I/O 작업과 같이 느린 작업이 발생하는 경우, 이를 기다리지 않고 동시에 다른 작업을 처리할 수 있음
→ 전반적인 시스템 성능 향상에 도움된다.
: 동시성 문제를 해결해야 함
: 호출된 함수의 수행 결과 및 종료를 호출된 함수에서만 처리하면 비동기
→ 따로 도식화
📌 인터넷 7계층 중 4계층
📌 전송 계층은 송신자와 수신자를 연결하는 통신서비스를 제공하는 계층으로 쉽게 말하자면 데이터의 전달을 담당한다.
→ 데이터를 보내기 위해 사용하는 프로토콜에는 TCP와 UDP가 있다.
📌 인터넷상에서 데이터를 메시지의 형태로 보내기 위해 IP와 함께 사용하는 프로토콜
📌 IP가 데이터의 배달을 처리한다면 TCP는 패킷¹을 추적하고 관리한다.
1. 패킷(Packet): 라우팅을 효율적으로 하기 위해 데이터를 여러 개의 조각들로 나누어 전송하는데, 이 조각을 패킷이라고 한다.
TCP의 특징
- 연결 지향 방식으로 패킷 교환 방식을 사용한다.
→ 연결 지향 방식: 패킷을 전송하기 위한 논리적 경로를 배정한다
- 3-way handshaking의 과정을 통해 연결을 설정하고 4-way handshaking을 통해 해제한다.
→ 3-way handshaking: 목적지와 수신지를 확실히 하기 위해 세션을 수립하는 과정
→ 높은 신뢰도를 가짐
- 흐름 제어 및 혼잡 제어
→ 따라서 UDP에 비해 속도가 느림
TCP는 패킷에 번호를 부여하여 추적 및 관리함. 이를 통해 분실 여부 등의 처리가 가능하며, 목적지에 도착 시 패킷을 재조립한다.
연속성보다 신뢰성 있는 전송이 중요한 경우 사용되는 프로토콜
※ 4-way handshaking
📌 TCP 통신 관계를 끊는 것
📌 데이터를 데이터그램² 단위로 처리하는 프로토콜
2. 데이터그램(Datagram): 독립적인 관계를 지니는 패킷
CheckSum
필드를 통해 최소한의 오류만 검출한다신뢰성보다 연속성있는 전송이 중요한 경우 사용되는 프로토콜
(e.g. 실시간 스트리밍 등)
📌 client를 만들 필요는 없다
📌 server끼리 통신할 필요 없다
./ircserv <port> <password>
#port: IRC 서버가 연결을 수락할 포트 번호
#password: 연결을 위한 비밀번호. 서버에 연결하려는 IRC 클라이언트에 필요함
📌 서버는 여러 클라이언트를 동시에 처리할 수 있어야 한다
📌 포크 불가. 모든 입출력은 Non-blocking이어야 한다
📌 오직 하나의 poll()(혹은 대체 가능 함수)만 read, write, listen, ...할 때 사용되어야 한다. 각 operation 당 하나가 아니라 통틀어서 하나
poll()을 사용하지 않고 file descripter를 read/recv하거나 write/send하는 경우 Fail
📌 IRC client 레퍼런스를 채택하여 이를 평가 중에 사용한다
→ 이 클라이언트는 에러 없이 서버와 연결되어야 한다
→ irssi 채택
📌 TCP/IP로 통신해야 함(IPv4, IPv6 둘 중 하나 채택)
📌 공식 IRC 서버와 유사하게 동작해야 함. 단, 다음 기능만 수행하면 됨
→ 클라이언트를 이용해 인증, 닉네임 및 사용자 이름 설정, 채널 참여, 비공개 메시지 송수신이 가능해야 함
→ 하나의 클라이언트에서 보낸 메시지는 채널에 참여 중인 모든 클라이언트에게 출력되어야 함
→ 명령들과 일반 사용자가 있어야 함
→ 운영자 전용 명령
1. KICK: 채널에서 클라이언트 추방
2. INVITE: 채널에 클라이언트 초대
3. TOPIC: 채널 토픽 변경 혹은 출력
4. MODE: 채널 모드 변경
- i: 초대 전용 채널 설정/삭제
- t: 채널 운영자에 대한 TOPIC 명령어 제한 설정/삭제
- k: 채널 비밀번호 설정/삭제
- o: 채널 운영자 특권 설정/삭제
- l: 채널 참여자 수 제한 설정/삭제
📌 MacOS는 Unix 운영체제와 같은 방식의 write()를 제공하지 않으므로 fcntl()을 사용할 수 있다.
⚠️ fcntl(fd, F_SETFL, O_NONBLOCK)의 형태로만 사용 가능⚠️
📌 Unix 운영체제와 비슷하게 동작하도록 하기 위해 non-blocking으로 file descriptor를 이용해야 한다
📌 ctrl+D를 이용해 명령어를 나눠서 전송할 수 있어야 함
\$> nc 127.0.0.1 6667
com^Dman^Dd
\$>
# com
# man
# d\n
📌 패킷을 전송 받아 재조립 해야함
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
📌 통신을 위한 endpoint를 생성하고 소켓 디스크립터를 반환
domain: 어떤 영역에서 통신할 것인지에 대한 영역 지정(protocol family)
→ AF_UNIX: 프로토콜 내부
→ AF_INET: IPv4
→ AF_INET6: IPv6
type: 소켓 타입
→ SOCK_STREAM: TCP
→ SOCK_DGRAM: UDP
protocol: 사용할 프로토콜
→ IPPROTO_TCP: TCP 프로토콜, SOCK_STREAM일 때 사용
→ IPPROTO_UDP: UDP 프로토콜, SOCK_DGRAM일 때 사용
→ 0: 프로토콜 지정하지 않음
반환 값
1. 0 이상의 값: socket descripter
2. INVALID_SOCKET: -1, 소켓 생성 실패
#include <unistd.h>
int close(int fd);
📌 열린 파일을 닫을 때 사용한다.
#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int socket, int level, int optname, const void *optval, socklen_t *optlen);
📌 소켓 옵션을 설정한다.
s: 소켓 식별 번호
level: 옵션을 해석하고 처리하는 코드
→ SOL_SOCKET: socket 코드가 처리
→ IPPROTO_IP: IP Protocol 코드가 처리
→ IPPROTO_TCP: TCP Protocol 코드가 처리
optname: 값을 설정할 소켓 옵션. level의 종류에 따라 다른 설정이름을 가짐.
optval: 설정할 옵션 값을 담고 있는 버퍼의 주소
optlen: optval이 가리키는 버퍼 크기
반환 값
1. 0: 정상적으로 옵션이 설정 됨
2. -1: 설정 실패. errno에 상세 내용 저장
#include <sys/socket.h>
int getsockname(int s, struct sockaddr *name, socklen_t *namelen);
📌 지정된 로컬 소켓 정보를 가져온다.
s: 소켓 식별 번호
name: 소켓의 이름을 받는 sockaddr 구조체의 포인터
namelen: name 버퍼의 크기
반환 값
1. 0: 성공
2. -1: 실패. errno에 상세 내용 저장
#include <netdb.h>
struct protoent *getprotobyname(const char *name);
📌 name에 해당하는 프로토콜 정보를 검색한다.
name: null로 종료된 프로토콜 이름에 대한 포인터.
반환 값
1. 성공: protoent 구조체 포인터
2. 실패: null
※ protoent 구조체
#include <netdb.h>
typedef struct protoent {
char *p_name;
char **p_aliases;
int p_proto;
} PROTOENT, *PPROTOENT, *LPPROTOENT;
: 지정된 프로토콜에 해당하는 이름, 별명 및 프로토콜 번호를 포함하는 구조체
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
📌 name에 해당하는 호스트 정보를 검색한다.
name: 호스트 이름
반환 값
1. 성공: hostent 구조체 포인터 반환
2. 실패: null
※ hostent 구조체
#include <netdb.h>
struct hostent{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
}
: 도메인 이름, 별명, 주소 유형, 주소 길이, 이진 IP 주소를 포함하는 구조체
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
📌 도메인 주소를 받아 네트워크 주소 정보(IP 주소)를 찾아서 result에 동적으로 할당.
domain: 도메인 이름(e.g. http://www.google.com) 혹은 IP 주소(e.g. 216.58.200.14)
→ service가 null이 아닌 경우 null이 올 수 있다.
service: http
, ftp
와 같은 서비스 이름 혹은 80
등의 포트 번호.
→ domain이 null이 아닌 경우 null이 올 수 있다.
hints: addrinfo 구조체 포인터
result: DNS 서버로부터 받은 네트워크 주소 정보(IP 주소)를 반환한다.
반환 값
1. 성공: 0
2. 실패: error code
※ addrinfo 구조체
#include <netdb.h>
typedef struct addrinfo {
int ai_flags; // 옵션 정의
int ai_family; // address family(e.g. AF_INET, AF_INET6 etc.)
int ai_socktype; // 소켓 유형(e.g. SOCK_STREAM, SOCK_DGRAM etc.)
int ai_protocol; // 프로토콜 유형
size_t ai_addrlen; // ai_addr가 가리키는 버퍼의 길이
char *ai_canonname; // 호스트의 정식 이름
struct sockaddr *ai_addr; // sockaddr 구조체에 대한 포인터. 소켓 주소를 나타냄
struct addrinfo *ai_next; // 다음 addrinfo 노드(연결 리스트의 형태를 가짐)
} ADDRINFOA, *PADDRINFOA;
📌 호스트 주소 정보를 저장
#include <sys/socket.h>
#include <netdb.h>
int freeaddrinfo(struct addrinfo *result);
📌 addrinfo 구조체의 메모리 해제
#include <sys/socket.h>
int bind(int socket, const struct sockaddr *addr, socklen_t addr_len);
📌 로컬 주소를 소켓과 연결한다.
#include <sys/socket.h>
int listen(int socket, int backlog);
📌 소켓에서 들어오는 연결을 수신 대기
#include <netinet/in.h>
unsigned long htons(unsigned long hostShort);
// Host short to network
unsigned long htonl(unsigned long hostLong);
// Host long to network
unsigned long ntohs(unsigned long netShort);
// Network to host short
unsigned long ntohl(unsigned long netLong);
// Network to host long
📌 소켓을 통해 다른 기종 간 데이터를 송수신 할 때 호스트 시스템의 byte order
에 맞게 데이터를 변환시킨다.
📌 htons: 호스트 바이트 순서의 IP 포트 번호를 네트워크 바이트 순서의 IP 포트 번호로 변환
📌 htonl: 호스트 바이트 순서의 IPv4 주소를 네트워크 바이트 순서의 IPv4 주소로 변환
📌 ntohs: 네트워크 바이트 순서의 IP 포트 번호를 호스트 바이트 순서의 IP 포트 번호로 변환
📌 ntohl: 네트워크 바이트 순서의 IPv4 주소를 호스트 바이트 순서의 IPv4 주소로 변환/\
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
📌 신호를 처리하는 함수
sig
SIGABRT
: 비정상적 종료SIGFPE
: 부동 소수점 오류SIGILL
: 잘못된 명령SIGINT
: 터미널 인터럽트 신호(ctrl + C)SIGSEGV
: 잘못된 메모리 참조SIGTERM
: 종료 신호함수 포인터
1. SIG_DFL
: 신호에 대한 기본 작업으로 처리
2. SIG_IGN
: 신호 무시
3. 함수 이름 : 지정 함수 호출
반환 값
1. 성공: 이전 핸들러를 반환
2. 실패: SIG_ERR
를 반환
select(), poll(), epoll()로 대체 가능
#include <sys/event.h>
int kqueue(void);
📌 커널에 새로운 이벤트 대기열을 만들고 fd를 반환한다.
📌 반환된 fd는 kevent()에서 이벤트를 등록하고 모니터링하는 데 사용된다.
#include <sys/event.h>
int kevent(
int kq,
const struct kevent *changeList,
inc nchanges,
struct kevent *eventList,
int nevents,
const struct timespec *timeout
);
📌 입력받은 kqueue에 새로 모니터링할 이벤트를 등록하고, 아직 처리되지 않은 이벤트의 개수를 반환한다.
NULL
을 전달하는 경우 이벤트가 발생될 때까지 block(무한정 대기)struct kevent {
uintptr_t ident; // 이벤트 식별자
int16_t filter; // 이벤트 필터
uint16_t flags; // 일반 플래그
uint32_t fflags; // 필터 플래그
intptr_t data; // 필터 데이터
void *udata; // 사용자 데이터
}
EV_SET(&kev, ident, filter, flags, fflags, data, udata);
📌 kevent 구조체 초기화
RFC
RFC 1459 | 정리
RFC 2810
RFC 2811
RFC 2812
RFC 2813
RFC 7194