IRC (Internet Relay Chat Protocol) 란?
IRC 프로토콜은 TCP/IP 네트워크 프로토콜을 기반으로 발전한 프로토콜이고 클라이언트-서버 모델을 사용하여 분산된 방식으로 채팅 시스템을 관리하는 방법에 대해서 설명합니다.
일반적인 설정은 클라이언트가 연결할 수 있는 중앙 지점을 형성하는 단일 프로세스(서버)를 포함하며 필요한 메세지를 전달하는 기능을 수행합니다.
IRC에서의 서버의 정의
[ Server 15 ] [ Server 13 ] [ Server 14]
/ \ /
/ \ /
[ Server 11 ] ------ [ Server 1 ] [ Server 12]
/ \ /
/ \ /
[ Server 2 ] [ Server 3 ]
/ \ \
/ \ \
[ Server 4 ] [ Server 5 ] [ Server 6 ]
/ | \ /
/ | \ /
/ | \____ /
/ | \ /
[ Server 7 ] [ Server 8 ] [ Server 9 ] [ Server 10 ]
:
[ etc. ]
:
[ Fig. 1. Format of IRC server network ]
IRC의 서버를 이해하기 위해서는 위의 그림을 이해해야 합니다. 우선 위 그림에서
Server 1
노드로 시작해보겠습니다. Server1은 2, 3과 직접 연결되어 있습니다.여기서 중요한 또 다른 개념은 위의 그림에서 확인할 수 있듯이 트리의 구조에서 확인할 수 있는 루트 노드가 존재하지 않는다는 것입니다. 각각의 서버는 다른 서버와 같이 모두 동등한 권한을 갖고 있고 특정 서버를
제어
한다기 보다는 서로 협력
하여 서비스가 동작할 수 있게 끔 네트워크를 구성하는 역할을 수행합니다.또한 각각의 서버는 각자의 클라이언트들을 소유하고 있습니다. Server1은 다른 서버들이 갖고 있지 않는 클라이언트를 소유하고 있다는 이야기 입니다.
여기서 만약 Server2에 포함되어 있는 클라이언트가 Server3에 있는 클라이언트에게 문자를 보내려고할 경우 어떻게 해야 할까요? Server2가 Server 1에게 Server3에게 문자를 보내고 싶다는 요청을 보내고 Server1은 해당 요청을 받아들이고 Server3에게 Server2의 요청을 Server3으로 전달하고 Server3은 서버 내에 있는 클라이언트에게 문자를 보내주게 됩니다.
이러한 과정에서 볼 수 있듯이 각각의 서버 노드들은 모두 협력적이어야 하며 연결되어 있고 특정 목적을 수행하기 위해서 반드시 다른 서버들을 거치거나 서버 내에서 작업이 수행되어야 합니다.
IRC에서 Client의 정의
1--\
A D---4
2--/ \ /
B----C
/ \
3 E
Servers: A, B, C, D, E Clients: 1, 2, 3, 4
[ Fig. 2. Sample small IRC network ]
IRC에서 클라이언트는 다른 클라이언트와 중복되지 않는 고유한
nickname
으로 구분이 되어야 합니다. nickname
은 최대 9 글자 이내여야 하며 서버는 단순히 nickname 에 대한 정보를 갖고 있는 것 이외에도, 모든 서버는 모든 클라이언트에 대해 다음 정보를 보유해야 합니다.- 클라이언트가 실행되고 있는 호스트의 실제 이름
- 해당 호스트에서의 클라이언트 사용자 이름
- 클라이언트가 연결된 서버
IRC에서 Operator의 정의
Operator 는 어려워 보이지만 우리말로 할 경우
운영자
에 해당합니다. IRC 프로토콜은 네트워크 내에서 적절한 수준의 질서를 유지하기 위해서 특별한 클라이언트인 운영자가 서버 내에 포함되어 있기를 권장합니다.운영자에게 부여되는 권한은 잘못된 네트워크 라우팅의 장기적인 사용을 방지하기 위해서 서버의 연결을 끊거나 다시 연결하는 등의 기본적인 네트워크 작업을 수행할 수 있어야 합니다. 여기서 중요한 것은 특정 클라이언트의 네트워크 연결을
강제로
끊을 수 있어야 한다는 점입니다. 이에 대한 이야기는 아래에서 더 자세하게 다루겠습니다.IRC 내에서 사용되는 포맷을 표현하는 규칙
[':' <prefix> <SPACE>] <command> [<SPACE> <parameters>] <CR-LF>
::=
는 정의를 시작한다는 의미로 사용이 됩니다. 예를 들어<message> ::=
는 뒤에 나오는 것이 메세지의 정의를 시작한다는 의미를 갖고 있습니다.[':' <prefix> <SPACE> ]
대괄호로 묶여서 표현이 되는 요소들은 선택적인 요소라는 것입니다. 대괄호의 요소들을 선택해도 되고 선택하지 않아도 된다는 의미입니다.:
- 접두사 시작을 나타내는 콜론<prefix>
- 메시지 발신자 정보<SPACE>
- 공백 문자
<command>
이후에 요소들은 필수 요소적인 요소로 IRC 명령어들이 포함이 되어야 한다는 것입니다 .이 명령어들의 종류에 대해서는 추후에 설명하겠습니다.<params>
- 명령어에 따른 파라미터들을 뜻합니다. 앞서 설명한 IRC에 해당하는 command와 접미사로 붙는<crlf>
사이의 모든 값들은 파라미터이며 띄어쓰기로 구분이 됩니다.<crlf>
캐리지 리턴과 라인피드(CR-LF)로 메시지 종료를 의미하는 접미사 입니다.
IRC의 메세지 로그 포맷의 형태
채널 이름의 규칙
여러명의 클라이언트가 채팅을 하기 위해서 모이는 공간을 채널이라고 정의하고 로그에서 채널의 이름을 표기할 때는 규칙이 존재합니다.
- 채널의 이름은
&
또는#
로 시작되어야 한다. - 채널의 이름은 200글자 이내로 작성되어야 한다.
- 채널의 이름에
공백
은 포함되어서는 안된다. - 채널의 이름에
ctrl G
가 포함되어서는 안된다. - 채널의 이름에
,(comma)
가 포함되어서는 안된다.
Chatroom에 참가하였을 경우
JOIN #<channel>,#<channel> <CR-LF>
:<nickname>!<client>@<host address> JOIN #<channel>{, #<channel>} <CR-LF>
JOIN
명령어는 특정 사용자가 서버에서 개설되어 있는 특정 채널에 참가 했을 경우 사용하는 명령어입니다.이 메세지의 접미사에는 사용자의 간략한 정보를 표기해주는 것이 좋습니다. 위에서는 client 의 nickname 과 client 와 주소에 대한 정보를 표기하여 주고 있습니다. 그리고 이 후에는 JOIN 명령어를 통해서 어떤 채널에 포함되어 있는 지에 대한 정보를 포함하고 있습니다.
:foo!foo@foo.tmi.twitch.tv JOIN #bar <CR-LF>
Chatroom에서 나갔을 경우
PART #<channel>,#<channel> <CR-LF>
:<nickname>!<client>@<host address> PART #<channel>{,<channel>} <CR-LF>
PART
명령어는 특정 사용자가 서버에서 개설되어 있는 특정 채널에서 나갔을 경우 사용되는 명령어입니다.PAR의 경우도 JOIN과 동일한 포맷을 따르고 있습니다. 접두사에는 사용자에 대한 정보가 포함되어야 하고 후에 사용자가 어떤 채널을 떠난 것인지에 대한 정보를 명확하게 표기해줄 필요가 있습니다.
Chatroom에서 전송되는 문자 메세지의 규칙
PRIVMSG <receiver>{, <receiver>} <text to be sent> <CR-LF>
ex) PRIVMSG #<channel name> :This is a sample message <CR-LF>
PRIVMSG 는 Private Message 약자로 유저간의 메세지가 오고 갈 경우에 사용이 되거나 특정 채널에 있는 모든 사용자를 대상으로 문자를 보낼 경우 사용이 되는 포맷입니다.
receiver
에 작성된 문자가 #
이나 &
로 시작할 경우 문자 전송 대상을 채널로 간주하고 이외의 경우는 특정 닉네임을 가진 사용자에게 문자를 전송하는 것입니다.:Angel PRIVMSG Wiz :Hello are you receiving this message ?<CR-LF>
여기서 특정 사용자한테 문자 메세지를 보낼 경우의 차이점이 존재합니다. 접두사로 누가 누구한테 보내는지를 표기해주는 것입니다. 위의 예제에서 볼 수 있듯이
:Angel
는 메세지를 전송하는 발신자에 해당하고 command 뒤의 이름인 Wiz
가 수신자가 됩니다.문자 메세지의 경우 메세지의 시작 부분에서
:
전송하는 메세지의 시작 지점을 반드시 표기해주어야 합니다. :
를 발견 했을 경우 이후의 모든 문자는 메세지에 해당하며 마지막에 <cr-lf>
문자가 등장한다면 해당 문자 요청은 끝났다는 것을 알 수 있습니다.# 채널에 메시지 전송
PRIVMSG #programming :안녕하세요! 새로운 멤버입니다.<CR-LF>
# 개인에게 메시지 전송
PRIVMSG nickname :안녕하세요, 개인 메시지입니다.<CR-LF>
# 여러 대상에게 동시 전송
PRIVMSG #room1,#room2 :여러 채널에 보내는 메시지입니다.<CR-LF>
# 이모티콘이나 특수문자 포함
PRIVMSG #programming :코드 리뷰 부탁드립니다! :)<CR-LF>
# 긴 메시지 (최대 510바이트까지)
PRIVMSG #programming :이것은 매우 긴 메시지입니다... (중략) ...끝<CR-LF>
Chatroom에서 특정 사용자를 운영자로 만드는 방법
IRC에서 Chatroom에서 합리적인 수준의 질서를 유지하기 위해서 특별한 클래스의 클라이언트 쉽게 말해서 해당 방의 운영자를
Operators
라고 정의합니다. 운영자들은 잘못된 네트워크 라우팅을 방지하기 위해서 서버의 연결을 해제하고 재 연결하는 것과 같은 기본적인 네트워크 작업을 수행할 수 있어야 합니다.또한 운영자는 강제로 연결되어 있는 사용자를 제거할 수 있는 능력이 있어야 합니다. 운영자들은 모든 클라이언트와 서버 간의 연결을 종료할 수 있는데 이는 운영자가 만든 방을 파괴할 경우 해당 방에 참여하고 있는 모든 사용자가 강제로 쫓겨나는 기능을 이야기 합니다.
MODE #channel +o nickname // 운영자 권한 부여
MODE #channel -o nickname // 운영자 권한 제거
특정 사용자에게
MODE
명령어를 통해서 권한을 부여 합니다. +o
명령어와 같이 MODE
가 사용될 경우 권한을 부여하고 -o
가 작성되었을 경우 권한을 제거하는 역할을 수행합니다.운영자가 특정 사용자를 추방하는 방법
KICK #channel user :reason // 사용자 추방
운영자가 개설한 채널을 닫고 모든 사용자들을 내보낼 경우의 시나리오
- 우선 모든 참여자에게 방 종료 메시지를
WALLOPS
명령어를 활용하여 브로드캐스트로 모든 참여자에게 방이 곧 있으면 종료된다는 메세지를 전송합니다. KICK
명령어를 활용하여 현재 채널에 참여하고 있는 모든 사용자의 연결을 끊고 결과 페이지로 리다이렉션 시킨다.- 마지막으로 운영자가
close
명령어를 이용하여 방장(운영자)의 channel이 차지하고 있던 네트워크 연결을 끊어 개설된 방을 닫아야 한다.
Error Response
401 ERR_NOSUCHNICK
: 채널에 해당 닉네임이 존재하지 않을 경우402 ERR_NOSUCHSERVER
: 특정 이름의 서버가 존재하지 않을 경우의 에러403 ERR_NOSUCHCAHANNEL
: 현재 서버에서 해당 이름의 채널이 사용되고 있지 않은 경우404 ERR_CANNOTSENDTOCHAN
:405 ERR_TOOMANYCCHANNELS
: 너무 많은 채널에 참가할 경우, 프로젝트에서 설정한 개인이 참가할 수 있는 최대 채널 수를 초과할 경우의 에러 코드
Socket을 이용하여 메세지를 전송할 경우 적용할 수 있는 포맷
방 생성 시 사용할 request, response 메세지
{
"type": "CREATE_CHANNEL",
"sender": {
"nickname": "user1",
"role": "user" // 채널 생성 시 자동으로 operator로 승격됨
},
"channel": {
"title": "기아 vs 삼성 승부 예측", // 사용자가 지정한 방 제목
"options": {
"option1": "기아",
"option2": "삼성"
},
"settings": {
"duration": 120, // 진행 시간 (초 단위)
"minBet": 100, // 최소 베팅 금액
"maxParticipants": 300 // 최대 참여 인원
}
},
"timestamp": "2024-11-10T10:00:00Z"
}
{
"status": 200,
"data": {
"channel": {
"id": "12345",
"name": "#betting_room_12345",
"title": "기아 vs 삼성 승부 예측",
"creator": {
"nickname": "host123",
"role": "operator"
},
"options": {
"option1": {
"name": "기아",
"currentBets": 0,
"participants": 0
},
"option2": {
"name": "삼성",
"currentBets": 0,
"participants": 0
}
},
"status": "waiting", // waiting, active, finished
"settings": {
"minBet": 100, // 최소 베팅 금액
"duration": 120, // 초 단위
"maxParticipants": 300 // 최대 참여 인원
},
"metadata": {
"createdAt": "2024-11-10T10:00:00Z",
"startAt": null,
"endAt": null
},
"urls": {
"websocket": "ws://example.com/ws/betting_room_12345",
"invite": "http://example.com/room/12345"
}
},
"message": "OK"
}
}
방에서 채팅을 보냈을 경우의 request, response
{
"type": "PRIVMSG",
"sender": {
"nickname": "user1",
"role": "user"
},
"channel": "#betting_1234", // 단순 채널명으로 수정
"content": "안녕하세요!", // message 대신 content로 통일
"timestamp": "2024-11-10T10:00:00Z"
}
{
"status": 200,
"data": {
"type": "ACK",
"messageId": "msg_123456", // 메시지 추적을 위한 고유 ID
"timestamp": "2024-11-10T10:00:00Z",
"message": "OK"
}
}
방에서 사용자를 내보냈을 경우의 request, response
// Request
{
"type": "KICK",
"sender": {
"nickname": "operator1",
"role": "operator" // KICK 명령어는 operator만 사용 가능
},
"channel": "#betting_1234",
"content": {
"targetUser": "user1", // 추방할 사용자
"reason": "부적절한 채팅" // 추방 사유 (선택적)
},
"timestamp": "2024-11-10T10:00:00Z"
}
// Success Response
{
"status": 200,
"data": {
"type": "KICK_ACK",
"channel": "#betting_1234",
"targetUser": "user1",
"message": "사용자가 성공적으로 추방되었습니다",
"timestamp": "2024-11-10T10:00:00Z"
}
}
Error Response
{
"status": 403,
"data": {
"type": "ERROR",
"code": "INSUFFICIENT_PERMISSIONS",
"message": "해당 작업을 수행할 권한이 없습니다",
"timestamp": "2024-11-10T10:00:00Z"
}
}
{
"status": 404,
"data": {
"type": "ERROR",
"code": "USER_NOT_FOUND",
"message": "해당 사용자를 찾을 수 없습니다",
"timestamp": "2024-11-10T10:00:00Z"
}
}
운영자가 Broadcast 메세지를 전송할 경우
// 1. 채널 종료 알림 (WALLOPS)
{
"type": "WALLOPS",
"sender": {
"nickname": "operator1",
"role": "operator"
},
"channel": "#betting_1234",
"content": "이 채널은 1분 후에 종료됩니다. 모든 베팅이 정산되며 결과 페이지로 이동됩니다.",
"timestamp": "2024-11-10T10:00:00Z"
}
// 2. 전체 참여자 추방 (KICK)
{
"type": "KICK_ALL",
"sender": {
"nickname": "operator1",
"role": "operator"
},
"channel": "#betting_1234",
"content": {
"reason": "채널 종료",
"redirectUrl": "/result/betting_1234" // 결과 페이지 URL
},
"timestamp": "2024-11-10T10:00:00Z"
}
// 3. 채널 종료 (CLOSE)
{
"type": "CLOSE",
"sender": {
"nickname": "operator1",
"role": "operator"
},
"channel": "#betting_1234",
"content": {
"reason": "베팅 종료",
"betResult": {
"winningOption": "option1",
"totalParticipants": 150,
"totalBetAmount": 15000
}
},
"timestamp": "2024-11-10T10:00:00Z"
}
// Success Response
{
"status": 200,
"data": {
"type": "CLOSE_ACK",
"channel": "#betting_1234",
"message": "채널이 성공적으로 종료되었습니다",
"timestamp": "2024-11-10T10:00:00Z"
}
}
// Error Response
{
"status": 403,
"data": {
"type": "ERROR",
"code": "INSUFFICIENT_PERMISSIONS",
"message": "채널을 종료할 권한이 없습니다",
"timestamp": "2024-11-10T10:00:00Z"
}
}
방에서 추방당한 유저들이 보게 되는 메세지
{
"type": "SYSTEM",
"channel": "#betting_1234",
"content": "채널이 종료되었습니다. 결과 페이지로 이동합니다.",
"redirectUrl": "/result/betting_1234",
"timestamp": "2024-11-10T10:00:00Z"
}