ㅇ 일 자 : 2026년 7월 2일(목)
ㅇ 주 제 : Team Project Chatting app "CodeLike" (사내 채팅앱)
ㅇ 프로젝트 간단 소개

| 개발 목적 |
1. 팀 단위 실시간 소통 : 대형 앱 대신 팀 규모에 최적화된 소형 앱을 통해 프로젝트별 방 개설, 소스코드 및 파일 전송, 실시간 알림 기능으로 신속한 의견 교환 환경을 구축하기 위함입니다. 2. 보안 및 기밀 유지 : 외부망 노출 위험이 있는 소스코드와 핵심 개발 자료를 사내 메신저 내에서 안전하게 공유하고 실시간으로 문제를 해결하고자 합니다. 3. 업무 효율 및 역량 강화: AI 활용으로 회의 시간을 단축해 개발 집중 시간을 확보하며, 신입 개발자는 채팅 기반의 업무 흐름을 모니터링하며 자연스럽게 역량을 강화하고자 합니다. 4. 개발 및 UI 환경 : Linux와 Windows를 모두 지원하는 Python3 및 PySide6 기반으로 개발하며, 직관적인 Windows 응용프로그램 스타일의 인터페이스를 제공하기 위함입니다. |
| 추진배경 | 국가 주요 사업인 인공지능 응용시스템 개발자 양성과정 중, 개발자들의 소통 채널이 외부망에만 의존하고 있다는 점에 착안하여 전용 ‘사내 채팅앱’의 필요성을 느꼈습니다. 개발자들이 업무나 학습에 몰입하면서도 채팅을 통해 실시간으로 의문점을 해결하고, 간접 지식을 공유할 수 있도록 지원하며, 나아가 채팅앱을 확장하여 개발자들만의 긴밀한 의견 공유와 상호 교류가 가능한 독자적인 커뮤니티 공간을 구축하고자 개발을 추진하게 되었습니다. |
| 개발 환경 | 1. OS / ver : Ubuntu Linux / Windows10,11 / Python 3.12 2. 서버 : 내부망 전용, Linux Internal Server, Windows Internal Server 3. 구조 : Client + Server + Protocol, JSON 4. 패턴 : MVC + Service Layer |
1. 개발 산출물 : 별도 첨부 유지
[개발 완료 단계] 프로젝트 완료 보고서
- 프로젝트 소개
- 프로그램 구현 영상 및 스크린샷
- 요구사항 명세서
- 유스케이스
- 순 서 도
- 메뉴 구조도
- 개발 일정표
- 프로그램 구현 파일 : 패키지 파일 목록, 소스 코드
- 프로그램 개발 후기 및 개발일지
2. API 명세서를 작성하는 이유
- API명세서를 작성하지 않으면 사용자는 어떤 정보를 어떤 방식으로 요청해야하는지, 어떤 정보를 전달받을 수 있을지, 어떤 형식으로 데이터가 넘어오는지 알 수 없다.
2.1. API 명세서 작성법 (예시)

2.2. 핵 심
- Method, URI, Request body, Response body, Description 속성은 되도록이면 포함되어야 한다.
- Method: HTTP Method을 의미한다. (GET, POST, PUT, DELETE 등)
- URI: 인터넷의 자원을 식별할 수 있는 문자열 또는 주소
- Request Body: API에 제공할 데이터 (요청할 데이터 )
- 주로 POST, PUT일 때 함께 작성됨
- Response Body: API가 생성하는 응답 데이터
- 일반적으로 JSON 및 XML 형식
- Description: 상세 설명
** 용어 설명
Status: API 응답에서 반환되는 상태 코드와 설명 (예: 200 - OK, 400 - Bad Request 등).
params: Query params, Path params 등
Headers: Content-Type, Authorization 등
3. 구현 가능한 코드
3.1. protocol.py (기본형)
#=========== 기본형 툴 참고 ===========
# 기본형 (protocol.py) send_message, receive_message / 송수신 메세지 만 단순 활용시
"""
import json
ENCODING = "utf-8"
DELIMITER = "\n"
def send_message(sock, message):
json_text = json.dumps(message, ensure_ascii=False) # 메시지를 JSON 문자열로 변환
data = json_text + DELIMITER # 끝에 줄바꿈을 붙여 메시지 경계 표시
sock.sendall(data.encode(ENCODING)) # 바이트로 바꿔 전부 전송
def receive_messages(sock, buffer): # 25강에서 사용 (이번 강의에서는 호출 안 함)
data = sock.recv(1024)
if not data:
return buffer, [], False
buffer += data.decode(ENCODING)
messages = []
while DELIMITER in buffer:
raw_message, buffer = buffer.split(DELIMITER, 1)
if raw_message.strip():
message = json.loads(raw_message)
messages.append(message)
return buffer, messages, True
"""
3.2. protocol.py (1형)
# 단순응용형 (protocol.py) send_message, receive_message / 송수신 메세지 활용시
# 구현기능 포함요소: 소켓통신 및 파일전송 프로토콜 모듈,JSON 기반 메시지 송수신, Base64 기반 파일 인코딩/디코딩 및 송수신, 예외 오류처리 포함
import json
import base64
import os
import socket
from typing import Tuple, List, Dict, Any, Optional
ENCODING = "utf-8"
DELIMITER = "\n"
def send_message(sock: socket.socket, message: Dict[str, Any]) -> bool:
# 딕셔너리 형태의 메시지를 JSON으로 변환하여 소켓으로 전송한다.
try:
json_text = json.dumps(message, ensure_ascii=False)
data = json_text + DELIMITER
sock.sendall(data.encode(ENCODING))
return True
except socket.error as e:
print(f"[오류] 서버로 데이터를 전송하는데 실패했습니다: {e}")
return False
except Exception as e:
print(f"[오류] 알 수 없는 전송 오류가 발생했습니다: {e}")
return False
def receive_messages(sock: socket.socket, buffer: str) -> Tuple[str, List[Dict[str, Any]], bool]:
# 소켓으로부터 데이터를 수신하여 완전한 JSON 메세지 리스트로 반환한다.
try:
data = sock.recv(4096)
if not data:
return buffer, [], False
buffer += data.decode(ENCODING)
messages = []
while DELIMITER in buffer:
raw_message, buffer = buffer.split(DELIMITER, 1)
if raw_message.strip():
try:
message = json.loads(raw_message)
messages.append(message)
except json.JSONDecodeError:
print("[오류] 수신된 데이터의 JSON 형식이 올바르지 않습니다.")
return buffer, messages, True
except ConnectionResetError:
print("[오류] 상대방과의 연결이 끊어졌습니다.")
return buffer, [], False
except Exception as e:
print(f"[오류] 메시지 수신 중 오류가 발생했습니다: {e}")
return buffer, [], False
def send_file(sock: socket.socket, filepath: str) -> bool:
# 사용자가 선택한 파일을 읽어 Base64로 변환한 뒤 서버로 전송한다.
# 1. 파일 미선택 처리
if not filepath:
print("[안내] 전송할 파일이 선택되지 않았습니다.")
return False
# 파일 존재 여부 확인
if not os.path.exists(filepath):
print(f"[오류] 해당 경로에 파일이 존재하지 않습니다: {filepath}")
return False
# 2. 파일 정보 추출 및 빈 파일 처리
filesize = os.path.getsize(filepath)
if filesize == 0:
print("[오류] 내용이 없는 빈 파일은 전송할 수 없습니다.")
return False
filename = os.path.basename(filepath)
try:
# 3. 파일을 Base64로 변환
with open(filepath, "rb") as f:
file_data = f.read()
encoded_data = base64.b64encode(file_data).decode('utf-8')
# 4. 파일 메시지 구성
message = {
"type": "file",
"filename": filename,
"size": filesize,
"data": encoded_data
}
# 5. 파일 정보 표시 및 서버 전송
print(f"[발신] 파일 전송을 시작합니다... (파일명: {filename}, 크기: {filesize} bytes)")
is_success = send_message(sock, message)
if is_success:
print("[안내] 파일 전송이 성공적으로 완료되었습니다.")
else:
print("[오류] 파일 전송에 실패했습니다.")
return is_success
except Exception as e:
print(f"[오류] 파일 처리 및 전송 과정에서 치명적인 오류가 발생했습니다: {e}")
return False
def receive_file(message: Dict[str, Any], save_dir: str = "./downloads") -> bool:
# 수신된 파일 메세지를 디코딩하여 로컬 디렉토리에 저장
if message.get("type") != "file":
return False
filename = message.get("filename", "unknown_file")
filesize = message.get("size", 0)
encoded_data = message.get("data", "")
# 파일 정보 표시
print(f"[수신] 파일을 수신했습니다. (파일명: {filename}, 크기: {filesize} bytes)")
try:
# 저장 디렉토리 생성
if not os.path.exists(save_dir):
os.makedirs(save_dir)
save_path = os.path.join(save_dir, filename)
# Base64 디코딩 및 파일 저장
file_data = base64.b64decode(encoded_data)
with open(save_path, "wb") as f:
f.write(file_data)
print(f"[안내] 파일이 성공적으로 저장되었습니다: {save_path}")
return True
except base64.binascii.Error:
print("[오류] 수신된 파일의 Base64 디코딩에 실패했습니다. 데이터가 손상되었을 수 있습니다.")
return False
except Exception as e:
print(f"[오류] 파일 저장 중 오류가 발생했습니다: {e}")
return False
3.3. protocol.py (2형)
# protocol.py
# 코드 요약 설명 :
# TCP 소켓 통신 환경에서 딕셔너리 데이터를 주고받는 구조로 TCP는 데이터의 경계가 없는 스트림 프로토콜이기 때문에,
# 메시지를 보낼 때 데이터의 크기(Header)를 먼저 보내고, 그 다음 실제 데이터(Payload)를 보내는 방식이 가장 안전하고 일반적
# 서버 측에서도 메시지를 안전하게 수신할 수 있도록 receive_message 함수도 함께 포함한다.
import json
import socket
def send_message(sock: socket.socket, msg_dict: dict):
# 딕셔너리 데이터를 JSON으로 변환하여 TCP 소켓으로 안전하게 전송한다. 4바이트 헤더(데이터 길이) + 데이터 바디 형태로 전송한다
try:
# 1. 딕셔너리를 JSON 문자열로 변환 후 bytes로 인코딩
json_data = json.dumps(msg_dict, ensure_ascii=False).encode('utf-8')
# 2. 데이터의 총 길이를 구함
data_length = len(json_data)
# 3. 4바이트 고정 길이 헤더 생성 (빅 엔디안 방식)
header = data_length.to_bytes(4, byteorder='big')
# 4. 헤더와 바디를 순서대로 전송
sock.sendall(header + json_data)
except Exception as e:
raise RuntimeError(f"메시지 전송 중 오류 발생: {e}")
def receive_message(sock: socket.socket) -> dict:
# 소켓으로부터 [4바이트 헤더 + 데이터 바디]를 읽어와 딕셔너리로 역직렬화, 서버측 코드나 클라이언트 수신 루프에서 사용하기 좋다.
try:
# 1. 4바이트 헤더(길이 정보) 수신
header = _recv_all(sock, 4)
if not header:
return {"type": "exit", "reason": "연결이 끊어졌습니다."}
# 2. 헤더로부터 데이터 크기 파싱
data_length = int.from_bytes(header, byteorder='big')
# 3. 데이터 크기만큼 바디 수신
body_data = _recv_all(sock, data_length)
if not body_data:
return {"type": "exit", "reason": "데이터를 받는 도중 연결이 끊어졌습니다."}
# 4. JSON 디코딩 후 딕셔너리로 반환
return json.loads(body_data.decode('utf-8'))
except Exception as e:
return {"type": "error", "message": str(e)}
def _recv_all(sock: socket.socket, length: int) -> bytes:
# TCP 소켓 스트림에서 지정한 크기(length)만큼 데이터를 완전히 수신할 때까지 반복한다
data = b''
while len(data) < length:
packet = sock.recv(length - len(data))
if not packet:
return b'' # 소켓이 닫힌 경우
data += packet
return data
끝.
'학습 로그 > Project' 카테고리의 다른 글
| D+68[Project] UI 화면구현(모달,팝업) & 데이터가져오기 에러 조치는? (0) | 2026.07.14 |
|---|---|
| D+55 [Project] 팀프로젝트 채팅앱 프로그램(개발 계획) (0) | 2026.06.28 |
| D+49 [Project] 개인 프로젝트를 팀 통합파일로 만들기 실습 (0) | 2026.06.17 |
| D+46 [Project] 프로젝트에서 WBS 무엇일까? (0) | 2026.06.14 |
| D+44 [Project] 프로그램 개발 산출물 작성 방법 (0) | 2026.06.13 |