5. ft_irc

zinnnn37·2023년 11월 1일
0

🪐 42 SEOUL

목록 보기
8/9

0. Background

0.0. IRC?

📌 Internet Relay Chat의 약자로 텍스트 기반 실시간 채팅 시스템이다. 채널이라 불리는 그룹 통신을 위해 설계되었지만 1:1 채팅 및 파일 공유 또한 지원한다.

0.1. Block vs Non-Block

현 작업이 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


0.2. Synchronous vs Asynchronous

요청한 작업의 완료 여부를 판단, 작업을 순차적으로 수행할 지에 대한 관점

1. 동기(Synchronous)
: 요청한 작업에 대해 완료 여부를 확인하여 순서대로 실행한다.
   → 요청한 작업의 순서가 지켜짐
: Block의 경우 함수의 결과 여부를 확인하지 않는 반면, 동기의 경우 확인을 함.
: 호출된 함수의 수행 결과 및 종료를 호출된 함수 뿐만 아니라 호출한 함수도 확인하는 경우 동기

2. 비동기(Asynchronous)
: 요청한 작업에 대해 완료 여부를 따지지 않고 실행한다.
   → 요청한 작업의 순서가 지켜지지 않을 수 있음
   → I/O 작업과 같이 느린 작업이 발생하는 경우, 이를 기다리지 않고 동시에 다른 작업을 처리할 수 있음
   → 전반적인 시스템 성능 향상에 도움된다.
: 동시성 문제를 해결해야 함
: 호출된 함수의 수행 결과 및 종료를 호출된 함수에서만 처리하면 비동기


→ 따로 도식화


0.3. 전송 계층

📌 인터넷 7계층 중 4계층
📌 전송 계층은 송신자와 수신자를 연결하는 통신서비스를 제공하는 계층으로 쉽게 말하자면 데이터의 전달을 담당한다.
     → 데이터를 보내기 위해 사용하는 프로토콜에는 TCPUDP가 있다.

0.3.0. TCP

📌 인터넷상에서 데이터를 메시지의 형태로 보내기 위해 IP와 함께 사용하는 프로토콜
📌 IP가 데이터의 배달을 처리한다면 TCP는 패킷¹을 추적하고 관리한다.

1. 패킷(Packet): 라우팅을 효율적으로 하기 위해 데이터를 여러 개의 조각들로 나누어 전송하는데, 이 조각을 패킷이라고 한다.

  • TCP의 특징
    - 연결 지향 방식으로 패킷 교환 방식을 사용한다.
        → 연결 지향 방식: 패킷을 전송하기 위한 논리적 경로를 배정한다
    - 3-way handshaking의 과정을 통해 연결을 설정하고 4-way handshaking을 통해 해제한다.
        → 3-way handshaking: 목적지와 수신지를 확실히 하기 위해 세션을 수립하는 과정
        → 높은 신뢰도를 가짐
    - 흐름 제어 및 혼잡 제어
        → 따라서 UDP에 비해 속도가 느림

  • TCP는 패킷에 번호를 부여하여 추적 및 관리함. 이를 통해 분실 여부 등의 처리가 가능하며, 목적지에 도착 시 패킷을 재조립한다.

연속성보다 신뢰성 있는 전송이 중요한 경우 사용되는 프로토콜

※ 4-way handshaking
📌 TCP 통신 관계를 끊는 것

  • A와 B가 있으며 A가 먼저 close한다고 가정
    : A가 close를 하고 B의 close를 기다린다
    : B가 어떠한 수단으로 A의 close를 알아차리고 B도 close를 한다
    : A가 B의 close를 받고 B에게 자신의 종료를 알리고 종료한다.
    : B는 A의 종료를 확인하고 자신도 종료한다.
    → 마지막 두 단계는 socket library가 알아서 처리

0.3.1. UDP

📌 데이터를 데이터그램² 단위로 처리하는 프로토콜

2. 데이터그램(Datagram): 독립적인 관계를 지니는 패킷

  • UDP의 특징
    - 비연결형 서비스로 데이터그램 방식을 제공한다.
        → 즉, 연결을 위해 할당되는 논리적인 경로가 없다.
        → 각각의 패킷은 다른 경로로 전송, 독립적으로 처리된다.
    - 정보를 주고 받을 때 송·수신한다는 신호를 보내거나 받지 않는다
    - UDP 헤더의 CheckSum 필드를 통해 최소한의 오류만 검출한다
    - TCP보다 빠르다
        → 패킷에 순서를 부여하여 재조립을 하거나 흐름 제어, 혼잡 제어 등의 기능을 수행하지 않음
        → 신뢰성 있는 데이터 전송을 보장하지 못 함

신뢰성보다 연속성있는 전송이 중요한 경우 사용되는 프로토콜
(e.g. 실시간 스트리밍 등)

0.4. Socket


1. Subject

1.0. details

📌 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

📌 패킷을 전송 받아 재조립 해야함


1.1. functions

1.1.0. socket()

	#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, 소켓 생성 실패

1.1.1. close()

#include <unistd.h>

int close(int fd);

📌 열린 파일을 닫을 때 사용한다.

  • fd: 파일 디스크립터
  • 반환 값
    1. 0: 정상적으로 close 됨
    2. -1: close 실패

1.1.2. setsockopt()

#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에 상세 내용 저장

1.1.3. getsockname()

#include <sys/socket.h>

int getsockname(int s, struct sockaddr *name, socklen_t *namelen);

📌 지정된 로컬 소켓 정보를 가져온다.

  • s: 소켓 식별 번호

  • name: 소켓의 이름을 받는 sockaddr 구조체의 포인터

  • namelen: name 버퍼의 크기

  • 반환 값
    1. 0: 성공
    2. -1: 실패. errno에 상세 내용 저장

1.1.4. getprotobyname()

#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;

: 지정된 프로토콜에 해당하는 이름, 별명 및 프로토콜 번호를 포함하는 구조체

1.1.5. gethostbyname()

#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 주소를 포함하는 구조체

1.1.6. getaddrinfo()

#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;

📌 호스트 주소 정보를 저장

1.1.7. freeaddrinfo()

#include <sys/socket.h>
#include <netdb.h>

int freeaddrinfo(struct addrinfo *result);

📌 addrinfo 구조체의 메모리 해제

  • result: addrinfo 구조체
  • 반환 값
    1. 성공: 0
    2. 실패: error code

1.1.8. bind()

#include <sys/socket.h>

int	bind(int socket, const struct sockaddr *addr, socklen_t addr_len);

📌 로컬 주소를 소켓과 연결한다.

  • socket: 소켓 식별자
  • addr: 바인딩된 소켓에 할당할 로컬 주소의 sockaddr 구조체
  • addr_len: addr의 길이
  • 반환값
    1. 성공: 0
    2. 실패: SOCKET_ERROR

1.1.9. listen()

#include <sys/socket.h>
  
int	listen(int socket, int backlog);

📌 소켓에서 들어오는 연결을 수신 대기

  • socket: 소켓 식별자
  • backlog: 대기열에 있을 수 있는 최대 연결 수

1.1.10.

1.1.11. accept()

1.1.12. htons(), htonl(), ntohs(), ntohl()

#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 주소로 변환/\

1.1.14. inet_addr(), inet_ntoa()

1.1.15. send()

1.1.16. recv()

1.1.17. signal(), sigaction()

	#include <signal.h>
    void	(*signal(int sig, void (*func)(int)))(int);

📌 신호를 처리하는 함수

  • sig
    1. SIGABRT : 비정상적 종료
    2. SIGFPE : 부동 소수점 오류
    3. SIGILL : 잘못된 명령
    4. SIGINT : 터미널 인터럽트 신호(ctrl + C)
    5. SIGSEGV : 잘못된 메모리 참조
    6. SIGTERM : 종료 신호
  • 함수 포인터
    1. SIG_DFL : 신호에 대한 기본 작업으로 처리
    2. SIG_IGN : 신호 무시
    3. 함수 이름 : 지정 함수 호출

  • 반환 값
    1. 성공: 이전 핸들러를 반환
    2. 실패: SIG_ERR를 반환

1.1.18. lseek()

1.1.19. fstat()

1.1.20. fcntl()

1.1.21. kqueue()

select(), poll(), epoll()로 대체 가능

#include <sys/event.h>

int	kqueue(void);

📌 커널에 새로운 이벤트 대기열을 만들고 fd를 반환한다.
📌 반환된 fd는 kevent()에서 이벤트를 등록하고 모니터링하는 데 사용된다.

  • 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에 새로 모니터링할 이벤트를 등록하고, 아직 처리되지 않은 이벤트의 개수를 반환한다.

  • kq: kqueue의 fd
  • changeList: kevent 구조체의 배열로, 이 배열에 저장된 kevent 구조체(=이벤트)는 kqueue에 등록됨
  • nchanges: 등록할 이벤트의 개수
  • eventList: 발생한 이벤트가 반환될 배열
  • nevent: eventList 배열 크기
  • timeout: timespec 구조체 포인터. NULL을 전달하는 경우 이벤트가 발생될 때까지 block(무한정 대기)
  • kevent 구조체
struct kevent {
	uintptr_t	ident;	// 이벤트 식별자
  	int16_t		filter;	// 이벤트 필터
  	uint16_t	flags;	// 일반 플래그
  	uint32_t	fflags;	// 필터 플래그
  	intptr_t	data;	// 필터 데이터
  	void		*udata;	// 사용자 데이터
}
  • EV_SET 매크로 함수
EV_SET(&kev, ident, filter, flags, fflags, data, udata);

📌 kevent 구조체 초기화


2. 구현


3. Docker

Docker에서 돌리는 irc


동기/비동기 & 블로킹/논블로킹
TCP/UDP

RFC
RFC 1459 | 정리
RFC 2810
RFC 2811
RFC 2812
RFC 2813
RFC 7194

0개의 댓글