Irc #1 개념 정리

2024. 1. 20. 17:0842seoul

1. IRC란?

인터넷 릴레이 챗(Internet Relay Chat, 약칭: IRC)은 실시간 채팅 프로토콜이다.

채널이라 불리는 토론 포럼에서 그룹 대화를 하기 위해 설계되었으나 개인 메시지를 통한 1:1 소통, 그리고 파일 공유를 포함한 채팅 및 대화 전송도 가능하다. 그리고 IRC는 전통적인 채팅 프로토콜로, 이를 지원하는 수많은 서버 네트워크와 클라이언트가 존재한다.

그렇다면 실시간 채팅 프로토콜이라는 말에서 프로토콜은 뭘까? 내가아는 지식으로는 인터넷에서 쓰이는 규칙정도로만 알고있다.

 

1) 프로토콜 이란?

통신 프로토콜 또는 통신 규약은 컴퓨터나 원거리 통신 장비 사이에서 메시지를 주고 받는 양식과 규칙의 체계이다. 통신 프로토콜은 신호 체계, 인증, 그리고 오류 감지 및 수정 기능을 포함할 수 있다. 프로토콜은 형식, 의미론, 그리고 통신의 동기 과정 등을 정의하기는 하지만 구현되는 방법은 독립적이다. 이때문에 프로토콜은 하드웨어 또는 소프트웨어 그리고 때로는 모두를 사용하여 구현되기도 한다.

이 중에 우리는 RFC 1459 규칙을 참고하여 작성하였다. 그렇다면 RFC는 또 뭔가..?

 

2) RFC란?

RFC(Request for Comments) 문서는 비평을 기다리는 문서라는 의미로, 컴퓨터 네트워크 공학 등에서 인터넷 기술에 적용 가능한 새로운 연구, 혁신, 기법 등을 아우르는 메모를 나타낸다. 이중에 RFC 1459: Internet Relay Chat을 참고했다.

 

정리해보자면 IRC 인터넷 채팅 프로토콜을 구현하는데 서버와 클라이언트 중에 '서버'를 구현하는 것이다.

클라이언트는 이미 알려진 클라이언트중 하나를 선택해서 평가때 활용한다. 우리는 irssi를 선택했다.

 

3) IRSSI란?

Irssi는 리눅스, FreeBSD, macOS, 마이크로소프트 윈도우용 IRC 클라이언트 프로그램이다. Timo Sirainen이 처음 개발했으며 1999년 1월 GNU GPL 2.0 이상 라이선스로 출시되었다.

Irssi는 따로 약자가 있는것 같지는 않고 그냥 이름인것 같다. 어쨋든 클라이언트는 이렇게 구현되어있는걸 활용하면 되고

우리는 '서버'를 개발하면 된다.

 

2. 개발 순서

1) 초기 개발 디자인 (피그마 이용)

팀원들과 상의하여 처음에 객체를 다음과같이 5가지로 구성하기로 했다.

서버, 클라이언트, 채널, 유틸, 커맨드

다섯가지 객체를 구성하고, 각자 객체의 특성에 맞게 디자인패턴을 적용해보기로 했다.

챗gpt를 이용하여 아래 그림처럼 각 객체마다 어떤 패턴을 적용할것인지도 적어보았다.

sudo code 1.0

 

2) 개발 전개

VS코드의 라이브쉐어를 이용하여 초기에 객체 지정을 함께 했다.

서버객체부터 코드를 작성하였다. 근데 막상 코드를 작성하다보니 서버의 옵저버패턴, 클라이언트의 빌더패턴 등등이

왜 써야하는가 의문이 들었다.. 코드를 효율적으로 작성하기 위해 디자인패턴을 쓰는 느낌이 아닌

디자인패턴을 쓰기위해 코드를 작성하는 느낌..?이 들어서 팀원들과 많은 상의 끝에 디자인패턴에 너무 초점을 두지 말고

객체지향적으로 객체별로 서로 의존성을 줄이고 유지보수가 쉽게끔 작성하기로 했다.

sudo code 2.0

그래서 결론은 위 수도코드처럼 코드를 작성하게 되었다.

메인함수에서 서버 객체를 만들고, 크게 서버실행, 서버루프 함수를 실행한다. 서버객체는

단일객체생성이고, 서버루프안에서 클라이언트 접속을 POLL함수로 감지하여 클라이언트 객체를 생성하고,

클라이언트의 메세지를 POLLIN이 들어오면 recv함수로 버퍼를 받아주고 메세지 객체를 만들어준다.

그리고 서버 커맨드를 실행하고 커맨드에 따라 채널객체도 생성, 삭제등을 할수있다.

이때 메인함수와 서버객체에서 유틸객체의 네임스페이스 RPL / Check / Parsing을 활용한다.

 

3) 서버 실행(runServer)

void Server::runServer()
{
    _serverFd = socket(AF_INET, SOCK_STREAM, 0);
    if (_serverFd == -1)
        throw(std::logic_error("serverFd"));

    struct sockaddr_in server_addr;

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(_portNumber);

    int option = 1;
    setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));

    if (bind(_serverFd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
        throw(std::logic_error("bind"));
    if (listen(_serverFd, 5) == -1)
        throw(std::logic_error("listen"));
}

서버를 실행하는 건 사실 runServer함수가 전부다 짧고 별거없다. 위 함수들 실행후 멤버변수로 _serverFd를 가져가서 
계속 이용하는거다.

* runServer 분석

더보기

#chat GPT 3.5
1. `_serverFd` 소켓 생성:
   - `socket` 함수를 사용하여 IPv4(AF_INET), TCP(SOCK_STREAM) 소켓을 생성합니다.
   - 생성된 소켓의 파일 디스크립터는 `_serverFd` 변수에 저장됩니다.

```cpp
_serverFd = socket(AF_INET, SOCK_STREAM, 0);
if (_serverFd == -1)
    throw(std::logic_error("serverFd"));
```

2. 서버 주소 설정:
   - `struct sockaddr_in` 구조체를 사용하여 서버의 주소 정보를 설정합니다.
   - `INADDR_ANY`를 사용하여 어떤 네트워크 인터페이스에서든 접속을 허용하도록 합니다.
   - `_portNumber` 변수에 저장된 포트 번호를 사용하여 포트를 설정합니다.

```cpp
struct sockaddr_in server_addr;

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(_portNumber);
```

3. 소켓 옵션 설정:
   - `setsockopt` 함수를 사용하여 소켓 옵션을 설정합니다. 여기서는 `SO_REUSEADDR` 옵션을 사용하여 소켓 주소를 재사용할 수 있도록 설정합니다.

```cpp
int option = 1;
setsockopt(_serverFd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
```

4. 소켓에 주소 바인딩:
   - `bind` 함수를 사용하여 소켓에 서버 주소를 바인딩합니다.

```cpp
if (bind(_serverFd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
    throw(std::logic_error("bind"));
```

5. 리스닝 상태로 변경:
   - `listen` 함수를 사용하여 클라이언트의 연결을 대기할 수 있도록 소켓을 리스닝 상태로 변경합니다.

```cpp
if (listen(_serverFd, 5) == -1)
    throw(std::logic_error("listen"));
```

 

4) 서버 루프(serverLoop)

runServer후에는 아래 서버루프가 반복문으로 계속 반복되는 것이다.

되게 길어보이지만 사실 간단하다 pollfd라는 구조체에 서버정보를 넣고 

멤버변수 <vector>pollFds에 넣어준다. (이때 첫번째라서 인덱스가 0이다)

그리고 반복문이 돌면서

poll함수실행 -> 서버이벤트 확인(POLLIN이면 makeClient) -> 클라이언트 이벤트확인(반복문으로 인덱스 1번부터)
-> POLLIN에 걸리면 -> receiveData로 버퍼 읽어주기  / 메세지 객체만들기 / 명령어 실행(exeCommand) / 클라이언트 등록
-> POLLOUT에 걸리면 -> 해당클라이언트의 outBuffer확인해서 내용이있으면 sendMessage
void Server::serverLoop()
{
    struct pollfd poll_serv;

    poll_serv.fd = _serverFd;
    poll_serv.events = POLLIN;
    pollFds.push_back(poll_serv);

    while (1)
    {
        if (poll(pollFds.data(), pollFds.size(), -1) == -1)
            throw(std::logic_error(strerror(errno)));
        if (pollFds[0].revents & POLLIN)
            makeClient();
        for (size_t i = 1; i < pollFds.size(); ++i)
        {
            Client &client = _clientBook[pollFds[i].fd];
            if (pollFds[i].revents & POLLIN)
            {
                std::cout << "[fd:" <<pollFds[i].fd << "/nick:" << client.getNick() << "] ";
                try
                {
                    receiveData(pollFds[i].fd);
                }
                catch(const std::exception& e)
                {
                    const std::string buffer = RPL::QUIT::DIS_CONNECTION(client);
                    sendQuitMessage(client, buffer);
                    std::cerr << e.what() << '\n';
                    quitClient(client);
                    close(pollFds[i].fd);
                    pollFds.erase(pollFds.begin() + i);
                    continue ;
                }
                makeMessages(client.getInBuffer());
                try
                {
                    exeCommand(pollFds[i].fd);
                    _messages.clear();
                }
                catch (const std::exception &e)
                {
                    std::cerr << e.what() << std::endl;
                    quitClient(client);
                    close(pollFds[i].fd);
                    pollFds.erase(pollFds.begin() + i);

                    _messages.clear();
                    continue;
                }
                if (!client.getIsRegister())
                {
                    if (client.checkInfoSet())
                    {
                        welcomeMessage(client);
                        client.setIsRegister(true);
                    }
                }
            }
            else if (pollFds[i].revents & POLLOUT)
            {
                if (client.getOutBuffer() != "")
                {
                    sendMessage(pollFds[i].fd, client.getOutBuffer());
                    client.clearOutBuffer();
                }
            }
        }
    }
}

 

그렇다면 여기서 Poll함수가 해주는 기능이 뭘까? 

3. Poll함수와 다중 입출력

poll -- synchronous I/O multiplexing # man poll(2)

man에 poll은 다중입출력 동기화 라고 나와있다.  서버루프 함수에서 처럼 여러개의 파일디스크립터를 한꺼번에 관리해주는 것이고,

event로 필요한 이벤트를 등록하고 revent에서 해당 이벤트를 감시하여

POLLIN인경우 읽기 가능 POLLOUT인경우 쓰기 가능 이런 기능을 활용하여 버퍼를 읽고 쓰고를 하는것이다.

더보기

#chat GPT 3.5
`poll` 함수는 다중 입출력 멀티플렉싱(Multiplexing) I/O를 지원하는 시스템 콜 중 하나입니다. 
멀티플렉싱 I/O는 여러 개의 입출력 소켓 또는 파일 디스크립터를 한 번에 관리하고, 
이벤트가 발생한 소켓에 대해 비동기적으로 처리할 수 있는 메커니즘을 제공합니다. 
이를 통해 효율적으로 여러 소켓을 다룰 수 있고, 블로킹되지 않고도 여러 소켓의 상태를 감시하고 처리할 수 있습니다.

`poll` 함수의 주요 매개변수로는 `struct pollfd` 배열이 사용됩니다. 
이 배열은 관찰하려는 파일 디스크립터에 대한 정보를 포함합니다. 
각 `struct pollfd` 구조체는 다음과 같은 정보를 가지고 있습니다:

- `fd`: 파일 디스크립터
- `events`: 관찰할 이벤트 유형을 지정 (읽기, 쓰기, 오류 등)
- `revents`: 실제로 발생한 이벤트를 나타내는 비트 마스크 (함수 호출 후에 채워짐)

`poll` 함수는 다음과 같은 형식을 갖습니다:

```c
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
```

- `fds`: 관찰할 파일 디스크립터 정보를 담고 있는 배열
- `nfds`: 배열의 크기 (즉, 관찰 대상 파일 디스크립터의 개수)
- `timeout`: `poll` 함수가 블로킹 상태에 머무를 시간 (0은 무한대기, -1은 즉시 반환, 양수는 밀리초 단위의 대기 시간)

`poll` 함수는 호출 시 배열에 등록된 파일 디스크립터 중 하나 이상에서 지정된 이벤트가 발생할 때까지 대기하다가, 
이벤트가 발생하면 해당 이벤트가 발생한 파일 디스크립터의 개수를 반환합니다. 
만약 타임아웃이 발생하면 0을 반환하고, 오류가 발생하면 -1을 반환합니다.

멀티플렉싱 I/O를 사용하면 여러 소켓을 효율적으로 다룰 수 있으며, 
블로킹을 최소화하여 동시에 많은 연결을 처리할 수 있습니다. 이는 서버 프로그래밍에서 특히 중요한 기법 중 하나입니다

 

#참고

https://ko.wikipedia.org/wiki/%EC%9D%B8%ED%84%B0%EB%84%B7_%EB%A6%B4%EB%A0%88%EC%9D%B4_%EC%B1%97

 

인터넷 릴레이 챗 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. IRC는 여기로 연결됩니다. 다른 뜻에 대해서는 IRC (동음이의) 문서를 참고하십시오. 인터넷 릴레이 챗(Internet Relay Chat, 약칭: IRC)은 실시간 채팅 프로토콜이다. 채

ko.wikipedia.org

https://ko.wikipedia.org/wiki/%ED%86%B5%EC%8B%A0_%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C

 

통신 프로토콜 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 통신 프로토콜 또는 통신 규약은 컴퓨터나 원거리 통신 장비 사이에서 메시지를 주고 받는 양식과 규칙의 체계이다. 통신 프로토콜은 신호 체계, 인증, 그리고

ko.wikipedia.org

https://datatracker.ietf.org/doc/html/rfc1459

 

RFC 1459: Internet Relay Chat Protocol

The IRC protocol is a text-based protocol, with the simplest client being any socket program capable of connecting to the server. This memo defines an Experimental Protocol for the Internet community.

datatracker.ietf.org

https://ko.wikipedia.org/wiki/Irssi

 

Irssi - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

https://wariua.github.io/man-pages-ko/poll%282%29/

 

poll(2) - man-pages-ko

From here you can search these documents. Enter your search terms below.

wariua.github.io