IRC 정리

......·2024년 2월 14일

42서울

목록 보기
7/7

사용된 개념 공부하기
소켓 통신 및 IRC에 대해서 이해하기

공유 포인터(Shared_Ptr)

  • Shared_Ptr : C++11부터 나온 개념으로, 어떠한 객체를 여러 포인터에서 접근하고 있는 구조이다. 일반 포인터와 다른 이유는 해당 객체를 가리키고 있는 포인터의 개수를 가지고 있다는 것이다.
  • 즉 구조는 아래와 같다.
struct Count {
	std::size_t	strong;
    std::size_t	weak;
}
template<typename T>
class Shared_ptr {
private:
	Count *count;
    T	*type;
public:
	~Shared_ptr() {
    	if (Count != NULL) {
        	Count->strong--;
          if (Count->strong == 0) {
              delete type;
              if (Count->weak == 0) {
                  delete Count;
              }
          }    
      }
      else
      	return ;
    }
}
  • Count라는 구조체 안에 shared_ptr(strong의 갯수), weak_ptr의 갯수를 가지고 있으며, 이를 멤버변수로 들고 있는 형태이다.
  • 소멸자에서 볼 수 있듯이, shared_ptr(strong의 갯수)가 있으면 해당 객체를 삭제하지 않고, weak_ptr이 있으면 Count라는 구조체를 해제하지 않아서 메모리 누수를 조심해야 한다.
  • Weak_ptr : 메모리를 할당 받지 않는 포인터이며, 순환 참조를 막기 위해서 사용된다. shared_ptr을 weak_ptr로 참조 했을 경우, Count구조체 안의 weak의 개수가 증가하며, 단순 shared_ptr의 개수가 증가했을 때, weak의 개수가 증가하지 않는다.
  • 이 공유포인터(shared_ptr)을 통해서 , 해당 객체가 어디에서 참조하고 있으며, 사용되고 있는 개수를 파악할 수 있다. 대략적인 그림도는 아래와 같다.

IRC

  • IRC : Internet Relay Chat의 약자로, 널리 사용되던 서버/클라이언트 통신을 이용한 채팅 프로토콜

  • 서버 - 클라이언트의 관계를 그림으로 나타내면 위와 같이 그릴 수 있다.

  • 프로토콜 래퍼런스

  • 보통 TCP/IP통신 프로토콜을 통해서 구현을 한다.

  • TCP/IP통신 프로토콜 중, IPv4 프로토콜은 아래의 그림과 같이 소켓이 구성되어 있다.

    (출처 : 위키피디아)

  • 총 32비트로 구성되어있으며, 4개의 계층으로 구성된 것이 TCP/IP의 IPv4 프로토콜이다.

  • 4개의 계층 : 네트워크 접근 계층 -> 인터넷 계층 -> 전송 계층 -> 응용 계층

  • 간단하게 말하면, IRC는 TCP/IP 프로토콜을 통해서 데이터를 송수신하여 통신을 할 수 있는 프로토콜

IRC Protocols 확인하기(MacOS)

  • docker desktop을 설치
  • docker image로 ubuntu로 컨테이너 생성
  • docker run --d ubuntu container_name으로 종료되지 않도록 설정
  • 해당 컨테이너의 터미널에 접속해서 apt update, apt upgrade실행
  • apt install tcpflow(서버<->클라이언트의 메세지를 확인하기 위함)
  • apt install inspircd(서버의 역할)
  • https://irssi.org/download/ 해당 페이지를 따라서 irssi설치(apt install cmake,apt install meson을 해야함)
  • 터미널을 최소 3개를 열어준 후, 아래의 명령어들을 각 터미널에서 실행
    • inspircd --runasroot --nofork(서버 실행)
    • irssi -c 127.0.0.1 -n nickname -w password(클라이언트 실행)
    • tcpflow -i lo port 6667 -c(메세지를 보기 위한 터미널)
  • 확인하고 싶은 명령어들을 입력하면, 서로 메시지를 주고 받는 것을 확인할 수 있음

IRC Protocols(Reference : RFC 1459)

  • 클라이언트 -> 서버 통신 시, 명령어 파라미터의 형식으로 메세지가 구성

  • 서버 -> 클라이언트 통신 시, 해당 명령어에 맞는 형식으로 메세지를 전송해야함(해당 패킷이 일치하지 않을 경우, 동작하지 않음)

  • irssi v1.4.5기준

    • 클라이언트가 서버에 처음으로 접속 시, CAP, PASS, NICK, USER, MODE의 명령어가 들어오고, 해당명령어들에 대해 응답 패킷 + 웰컴 메시지(001 ~ 005)를 전송하지 않을 경우, 서버와 연결되지 않음
  • 기본적으로 구현해야할 명령어 목록

    • KICK [paramter] : 해당 채널에서 parameter에 해당하는 유저 추방

    • TOPIC [parameter] : 해당 채널의 주제를 변경

      • 만약, mode +t일 경우, 해당 채널의 운영자 권한이 없을 경우, 주제변경 불가
    • MODE [mode][parameter] : 해당 채널의 주제를 변경

      • i : 초대된 유저만 해당 채널에 접속 가능
      • k [parameter] : 해당 채널에 입장 시, 키를 통해서 입장하도록 제한
      • t : 해당 채널의 주제를 운영자만 변경 가능하도록 설정
      • l [parameter] : 해당 채널의 입장인원 제한 설정
      • o [parameter] : 해당 채널의 운영자 권한을 파라미터에게 부여
    • INVITE [parameter] : 해당 채널에 파라미터에 대한 접근권한 부여

    • PRIVMSG [user][parameter] : 메세지 전송 명령어

      • user가 있을 경우, 해당 메세지를 개인메세지로 전송
      • 없을 경우, 해당 채널에 있는 모두에게 메세지 전송
    • PING - 서버<->클라이언트가 연결이 정상적인지 일정 시간마다 확인하는 명령어, 클라이언트 -> 서버가 PING을 보내며, 서버 -> 클라이언트는 PONG을 보내야함

    • JOIN [parameter][password] : 원하는 채널에 접속

      • 해당 채널에 +k가 되어있을 경우, key를 통해서 접속을 해야함
      • 그렇지 않을 경우, 해당 채널에 입장
    • PASS [parameter] : 처음 서버 접속 시, 해당 채널의 패스워드가 있는지 확인하는 부분

    • NICK [parameter] : 닉네임 설정

      • 자신이 참여중인 모든 채널에 대해, 참여중인 모든 채널의 모든 인원에게 변경되었다는 메세지를 전송해야함
    • USER [parameter][parameter] [parameter] :[parameter] : 처음 서버 접속 시, 발생하는 명령어로 RFC1459를 확인해보면 됨

소켓 통신 프로그래밍

  • IRC의 도식대로 구현하고자 하기 위해서 알아야하는 내용으로 C언어의 소켓 프로그래밍에 대해서 먼저 알아본다.
  • 서버와 클라이언트간에 소켓을 통한 통신의 도식은 아래와 같다.
  • 서버 - 클라이언트 모두 소켓을 통해서 프로토콜이 진행이 되며, 각각의 블록에 있는 내용들이 알아봐야할 함수

소켓 통신 함수

  • Socket함수 : 소켓의 fd값을 얻기 위한 함수
    • 헤더 <sys/socket.h>
    • domain에는 PF_INET를 사용 -> IPv4 프로토콜을 사용
    • type에는 SOCK_STREAM을 사용 -> TCP/IP 프로토콜을 사용
    • protocol에는 특정 프로토콜을 위해서 값을 지정하나, 보통은 0을 사용
    • 리턴값 : socket의 fd의 값을 리턴, 실패 시 -1리턴
int socket(int domain, int type, int protocol)

ex)
int server_s = socket(PF_INET, SOCK_STREAM, 0);
if (server_s == -1)
{
	std::cout << "Fail to create socket\n";
}
  • Bind함수 : 인자로 받은 소켓에 주소를 할당해주는 함수

    • 헤더 <sys/socket.h>
    • socketfd : 소켓의 fd값을 받음
    • myaddr : sockaddr의 주소값을 받음
    • addrlen : myaddr의 구조체의 크기
    • 리턴값 : 0 -> 성공, -1 -> 실패
  • sockaddr 구조체

struct sockaddr_in 
{
   sa_family_t           sin_family;     /* Address family        */
   unsigned short int    sin_port        /* Port number           */
   struct in_addr        sin_addr;       /* Internet address      */
   
   /* Pad to size of ´struct sockaddr'. */
   unsigned char  __pad[__SOCK_SIZE__ - sizeof(short int) -
      sizeof(unsigned short int) - sizeof(struct in_addr)];
};
int bind(int socketfd, struct sockaddr *myaddr, socklen_t addrlen)

ex)
struct sockaddr_in server_a;

memset( &server_addr, 0, sizeof( server_addr);
// IPv4 인터넷 프로토롤
server_a.sin_family      = PF_INET;           

// 사용할 port 번호는 4000 
// sin_port에 인터넷 프로토콜에 맞게 끔 비트를 수정해야하는데
// 그 역할을 해주는 함수 -> htons
server_a.sin_port        = htons(포트번호);

// 32bit IPV4 주소
server_a.sin_addr.s_addr = htonl(주소);

if(bind(server_s, reinterpert_cast<sockaddr*>(server_a), sizeof(server_a)))
{
	std::cout << "Fail to create socket\n";
	exit(1);
}
  • Listen 함수 : 클라이언트의 접속 요청을 허용하도록 대기하는 함수
    • 헤더 <sys/socket.h>
    • sock : 소켓 fd
    • backlog : 연결을 기다리는 공간(백로그큐), 즉 backlog의 개수만큼만 대기상태가 될 수 있음 / 보통 5의 크기로 설정
    • 리턴값 : 0 -> 성공, -1 -> 실패
int listen(int sock, int backlog)

ex)

if (listen(server_s, 5) == -1)
{
	std::cout << "Listen Error";
    exit(1);
}
  • Accept 함수 :
    • 헤더 <sys/socket.h>
    • sockfd : 소켓 fd
    • addr : 서버 주소에 대한 포인터
    • addrlen : 서버주소의 크기
    • 리턴값 : -1 -> 실패, 성공 시, 소켓 fd
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

ex)
sockaddr_in	info;
socklen_t	size = sizeof(info);
int client_s;

socket = ::accept(this->socket, reinterpret_cast<sockaddr *>(&info), &size);
if (socket == -1)
{
	std::cout << "Fail to accept socket";
    exit(1);
}

0개의 댓글