client.py 는 python3 script 로 되어있습니다.
client.py 는 다음 실행 인자들을 사용할 수 있습니다.
--help: 사용 가능한 실행 인자 목록과 간단한 설명을 출력합니다.--verbosity: 기본 로그 외에 상세 로그를 출력하게 됩니다.--verbosity=0또는--verbosity=1또는--verbosity=2처럼 쓸 수 있으며, 0 은 아무것도 쓰지 않은 경우입니다. 1은 클라이언트-서버간 주고 받는 메시지를 출력하고, 2는 소켓 작업 내용까지 출력합니다.--ip: 채팅 서버의 IP 주소를 입력합니다. 실습 서버에서 사용함을 가정해서 기본값은127.0.0.1로 지정되어 있습니다.--port: 채탕 서버의 port 를 지정합니다. LMS 를 통해 고지된 각 개인에게 할당된 port 를 사용하세요.--format:--format=json이나--format=protobuf처럼 쓸 수 있습니다. 클라이언트-서버 간 메시지의 포맷을 지정합니다.
$ python3 -m venv .venv
$ source .venv/bin/activate
$ python3 -m pip install -r requirements.txt
$ source .venv/bin/activate
$ python3 ./client.py --format=json --port=10101
$ source .venv/bin/activate
$ python3 ./client.py --format=protobuf --port=10101
클라이언트 측에서 서버와 주고 받는 메시지의 상세 정보를 보고 싶은 경우 --verbosity 옵션을 추가할 수 있습니다.
기본 값은 0 인데, 1 은 좀 더 많은 로그 메시지, 2는 아주 많은 로그 메시지를 출력합니다.
예시 1
$ source .venv/bin/activate
$ python3 ./client.py --format=protobuf --port=10101 --verbosity=1
예시 2
$ source .venv/bin/activate
$ python3 ./client.py --format=json --port=10101 --verbosity=2
교수가 개발한 (1) JSON 사용 데모 서버가 실습 서버의 9000 번 포트에, (2) Protobuf 사용 데모 서버가 실습 서버의 9100 번 포트에 동작하고 있습니다. 따라서 아래 명령어에 대한 설명은 이 서버에 접속 후 테스트 해볼 수 있습니다.
데모 서버를 직접 실행해 보고 싶은 경우 다음 명령을 실행합니다. (해당 파일은 Linux 에서만 동작하며 Windows 나 macOS 에서는 실행이 되지 않습니다.)
$ python3 dist/server.py --port=내포트번호
(보다 자세한 옵션은 --help 인자를 주고 실행해 보면 알 수 있음)
client.py 를 실행해서 서버와 접속한 경우 다음과 같은 명령어들을 쓸 수 있습니다.
/help: 사용 가능 명령어를 나열한다./name: 채팅 이름을 지정한다./rooms: 채팅 방 목록을 출력한다./create: 채팅 방을 만든다./join: 채팅 방에 들어간다./leave: 채팅 방을 나간다./shutdown: 채팅 서버를 종료한다.
앞에 나열한 client.py 명령어 중 /help 를 제외한 나머지는 모두 서버가 있어야 동작합니다.
각 명령어의 기대되는 동작은 다음과 같습니다.
- 클라이언트가 처음 연결되면 서버는 이 클라이언트에게
(127.0.0.1, 9001)처럼 클라이언트 측 IP 주소와 port 번호를 이름으로 부여 합니다. /name명령은 연결한 클라이언트의 닉네임을 변경합니다.- 해당 클라이언트에게는 시스템 메시지로
[시스템 메시지] 이름이 test 으로 변경되었습니다.와 같은 알림이 가야됩니다. - 만일 이 클라이언트가 대화방에 들어가 있는 경우, 대화방에 있는 모든 멤버들에게
[시스템 메시지] 이름이 test 으로 변경되었습니다.와 같은 메시지가 추가로 가야됩니다.
- 현재 개설된 대화방 목록이 출력됩니다.
- 대화방 목록은 (1) 방 번호, (2) 방 제목, (3) 참여중인 멤버들의 이름 이 나열되어야 됩니다.
- 위 정보가 포함되는한 출력 형식은 자유롭게 하면 됩니다.
- 개설된 대화방이 없는 경우 이를 알아볼 수 있게 자유롭게 표시하면 됩니다.
- 대화방을 개설하는 명령어로서, 뒤에 방제목을 입력해야 됩니다.
예시:
/create hello world - 대화방 개설은 현재 참여 중인 대화방이 없는 경우만 가능해야됩니다. 만일 현재 다른 방에 들어가 있는 경우 서버는
[시스템 메시지] 대화 방에 있을 때는 방을 개설 할 수 없습니다.메시지를 해당 유저에게 전송해야 됩니다. - 대화방 개설과 동시에 해당 유저는 그 방에 자동으로 입장하게 됩니다.
예시:
/create hello world [시스템 메시지] 방제[hello world] 방에 입장했습니다.
- 이미 개설된 방에 참여할 수 있는 명령어로서, 뒤에 방 번호를 입력해야 됩니다.
예시:
/join 2 - 대화방 참여는 현재 참여중인 대화방이 없는 경우만 가능해야 됩니다. 만일 현재 다른 방에 들어가 있는 경우 서버는
[시스템 메시지] 대화 방에 있을 때는 다른 방에 들어갈 수 없습니다.메시지를 해당 유저에게 전송해야 됩니다. - 없는 방 번호의 방으로 들어가려고 할 때 서버는
[시스템 메시지] 대화방이 존재하지 않습니다.메시지를 해당 유저에게 전송해야 됩니다. - 방에 들어가면 서버는 해당 유저에게
[시스템 메시지] 방제[hello world] 방에 입장했습니다.와 같은 메시지를 전송해야 됩니다. - 방에 들어간 경우 모든 기존 멤버들에게는
[시스템 메시지] [test2] 님이 입장했습니다.와 같은 메시지를 전송해야 됩니다.
- 대화방을 나가는 명령어입니다.
- 대화방에 참여 중이지 않을 때 서버는
[시스템 메시지] 현재 대화방에 들어가 있지 않습니다.메시지를 해당 유저에게 전송해야 됩니다. - 남아있는 멤버들에게는
[시스템 메시지] [test2] 님이 퇴장했습니다.와 같은 메시지가 전송 되어야 됩니다. - 나간 유저 본인에게는
[시스템 메시지] 방제[hello world] 대화 방에서 퇴장했습니다.와 같은 메시지를 전송해야 됩니다.
- 채팅 서버를 종료하는 명령어입니다.
- 서버는 이 명령어를 수신하면 생성한 쓰레드들을 정리하고 프로그램을 종료해야 됩니다.
/ 로 시작하지 않는 문자열은 채팅 메시지로 인식합니다.
- 만일 대화 방에 없는 경우 서버는
[시스템 메시지] 현재 대화방에 들어가 있지 않습니다.를 해당 유저에게 전송해야 됩니다. - 만일 대화 방에 있는 경우 해당 채팅 메시지는 본인을 제외한 나머지 모든 멤버들에게 전송되어야 합니다.
JSON 과 Protobuf 두 경우를 모두 구현하는 것이 목적입니다.
아래 설명에서 CS 가 붙는 메시지 포맷은 클라이언트 -> 서버 를 의미합니다. SC 는 서버 -> 클라이언트 를 의미합니다.
- 클라이언트 -> 서버 메시지: client.py 의
on_cs_name(),on_cs_rooms(),on_cs_create(),on_cs_join(),on_cs_leave(),on_cs_chat()함수들을 보면 각 명령어에 대해서 서버 전송시 JSON 메시지 포맷을 알 수 있습니다. 이를 참고하길 바랍니다. - 서버 -> 클라이언트 메시지:
/rooms명령어에 대한 응답은on_sc_rooms_result()함수를 참고하길 바랍니다. 다른 유저가 입력한 채팅 메시지를 받는 경우는on_sc_chat()함수를 참고 바랍니다. 그 외에/로 시작하는 명령어들에 대한 처리는 모두on_sc_system_message()에 의해서 처리 됩니다.
메시지 형태는 message.proto 파일에 정의되어 있습니다.
이를 사용하기 위해서는 다음과 같은 명령어를 실행해서 message.proto 파일의 내용을 포함하는 python module 을 생성해야 됩니다. (본 repo 에 이미 포함하고 있지만, 만일 수정을 하는 경우 아래 명령어를 다시 실행해야 됩니다.)
$ protoc --python_out=. -I. message.proto
JSON 의 경우와 마찬가지로 대응되는 on_cs_XXX() 함수와 on_sc_YYY() 함수를 참고해서 메시지를 만들면 됩니다.
- 앞의 client.py 명령어들이 동작하게끔 메시지 포맷 (= 프로토콜)이 호환되는 서버를 작성하시오.
- 서버는 Python, C++, Java 어떤 것을 써도 상관 없지만, framework 은 사용할 수 없으며 직접 I/O multiplexing 과 producer-consumer 문제를 구현해야 함
- 서버는 worker thread 들의 개수를 수정할 수 있어야 합니다. (프로그램 실행 인자로 할 수 있다면 가장 좋겠지만, 이 부분이 익숙치 않다면 코드 상에서 대응되는 변수 값을 고치면 쓰레드 개수가 수정되게 구현해야 됩니다.)
- 서버가 클라이언트에 메시지를 전송하고 그게 클라이언트에서 출력되는 것이 중요합니다. 샘플 서버를 흉내낸다면 이 부분을 흉내내고, 서버 자체에서 출력되는 실행 로그 메시지는 똑같을 필요가 없습니다.
- worker thread 를 2개 이상으로 지정할 수 있도록 프로그래밍했는지 여부
- worker thread 가 2개 이상일 때 위의 명령어들이 제대로 동작하는지 여부 (= synchronization 이 제대로 구현되었는지)
- 둘 이상의 채팅 방에 유저들이 나눠 들어가 있는 경우 대화방 간 간섭 없이 제대로 채팅이 되는지 여부
- I/O multiplexing 적용 여부
- producer-consumer 적용 여부 (= queue, mutex, condition variable 사용 여부)
- Message handler map 적용 여부
- JSON 과 Protobuf 둘 다 지원하는지 여부
구현 내용 중에 어느 부분이 됐든 I/O multiplexing 과 proder-consumer 문제를 구현하면 됩니다.
그런데 구현 해야되는 내용이 너무 뻔해서 아마도 I/O multiplexing 은 여러 클라이언트 소켓과 새로운 연결을 처리하는데 써야될 것 이고, producer-consumer 문제는 각 클라이언트에서 보내온 메시지를 처리하는 형태가 될 것입니다.
이 때 producer-consumer 의 queue 에 각 클라이언트의 메시지를 집어 넣는 경우는 주의가 필요합니다. 일반적인 경우에서는 경험하기 어렵지만, 메시지가 매우 빠른 속도로 들어오는 경우 한 클라이언트로부터 온 메시지가 2개 이상 queue 에 들어갈 수 있게 구현 할 경우, 이 두 메시지가 서로 다른 쓰레드에 의해 처리 된다면 메시지의 처리 순서가 어긋나는 문제가 생길 수 있습니다.
- 예) client A 가 매우 빠르게 메시지1, 2 두개를 전송했는데, 둘을 큐에 넣는 경우 쓰레드1번은 메시지1 을 쓰레드2번은 메시지2를 처리하게 될 수 있습니다. 이 경우 처리 순서가 메시지1 -> 메시지2 여야 함에도 쓰레드에 의한 race condition 이 발생할 수 있습니다.
따라서 이 경우는 메시지들을 각각 큐에 넣어서는 안되고, 클라이언트 자체를 큐에 넣거나, 아니면 이미 해당 클라이언트로부터 받은 메시지가 큐에 있는 경우 기존 메시지를 처리 한 뒤에 다시 큐에 넣는 방식으로 구현해야 됩니다.