상세 컨텐츠

본문 제목

정보보안_파이썬_Y2K 레트로 채팅 프로그램 제작

2026년도 1학기/정보 보안

by 멈뭉밈 2026. 5. 13. 16:45

본문

 



💬 기본 채팅 기능

✅ 여러 명 접속 가능
✅ 실시간 채팅
✅ 닉네임 표시
✅ 시간 표시

예시:

[CHAT] [16:40] 감자
안녕
 

👥 접속자 기능

✅ 입장 알림

[SYSTEM] 감자님이 입장했습니다.
 

✅ 퇴장 알림

[SYSTEM] 감자님이 퇴장했습니다.
 

✅ 현재 접속자 목록 자동 표시

[USERS]
감자 / 멍멍이 / 아르
 

새 유저 들어오면 자동 갱신 😼


🕵️ 귓속말 기능

명령어:

/w 닉네임 메시지
 

예시:

/w 감자 안녕
 

출력:

[WHISPER] [16:41] 멍멍이 → 감자
안녕
 

📖 명령어 기능

명령어:

/?
 

출력:

[COMMAND]

/? : 명령어 보기
/w 닉네임 메시지 : 귓속말
end : 채팅 종료
IMAGE 버튼 : 이미지 전송
 

🖼️ 이미지 전송 기능

✅ IMAGE 버튼
✅ 이미지 선택
✅ 채팅창 내부 출력
✅ 이미지 미리보기 표시

😼📷


🎨 디자인 기능

✅ 레트로 블루 감성
✅ BLUE WEB CHAT 타이틀
✅ 파란 CRT 느낌
✅ 채팅창 일체형 디자인

거의 2000년대 웹메신저 감성 🌐✨


🖥️ 서버 기능

✅ 멀티 클라이언트 지원
✅ 스레드 기반 처리
✅ 서버 로그 출력

터미널에서:

[SYSTEM] 감자님 입장했습니다.

[CHAT] [16:52] 감자
안녕
 

전부 볼 수 있음 😼


🔌 네트워크 기능

✅ TCP 소켓 통신
✅ localhost 연결
✅ 포트 7777 사용
✅ socket reuse 처리


📦 사용 라이브러리

 
socket
threading
tkinter
PIL (pillow)
datetime

 

 


 

 

 

thread_client2.py

import socket
import threading

def thread_recv(client_socket, addr):
    while True:
        recv_data = client_socket.recv(1024)

        print(f"{addr}에서 보낸 메시지 : {recv_data.decode()}")

        # 에코 전송
        client_socket.sendall(recv_data)

        # 종료 처리
        if recv_data.decode().strip() == "end":
            print(f"{addr}에서 종료하고 싶어합니다.")

            client_socket.close()
            break


# 1. 소켓 생성
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2. 바인딩
sock.bind(("", 7777)) #고침

# 3. 접속 대기
sock.listen()

print("The Server Start.....")

# 4. 접속 수락
conn, addr = sock.accept()

# 쓰레드 생성
recv_handler = threading.Thread(
    target=thread_recv,
    args=(conn, addr)
)

recv_handler.start()

thread_server2.py

import socket
import threading
from datetime import datetime

clients = {}  # 닉네임 : 소켓


# 전체 전송
def broadcast(message):

    for client_socket in list(clients.values()):

        try:

            client_socket.send(
                message.encode("utf-8")
            )

        except:

            pass


# 현재 접속자 전송
def send_user_list():

    user_list = " / ".join(clients.keys())

    broadcast(f"[USERS]\n{user_list}")


# 클라이언트 처리
def thread_recv(client_socket, addr):

    nickname = client_socket.recv(1024).decode("utf-8").strip()

    if nickname == "":
        nickname = "guest"

    clients[nickname] = client_socket

    # 입장 알림
    broadcast(
        f"[SYSTEM] {nickname}님이 입장했습니다."
    )

    # 명령어 안내
    broadcast(
        "[SYSTEM] /? 를 입력하면 명령어를 볼 수 있습니다."
    )

    # 현재 접속자
    send_user_list()

    while True:

        try:

            recv_data = client_socket.recv(4096)

            if not recv_data:
                break

            msg = recv_data.decode("utf-8").strip()

            now = datetime.now().strftime("%H:%M")

            # 종료
            if msg == "end":

                break

            # 명령어
            elif msg == "/?":

                help_msg = (
                    "[COMMAND]\n"
                    "──────── COMMAND ────────\n"
                    "/? : 명령어 보기\n"
                    "/w 닉네임 메시지 : 귓속말\n"
                    "end : 채팅 종료\n"
                    "IMAGE 버튼 : 이미지 전송\n"
                    "────────────────────────"
                )

                client_socket.send(
                    help_msg.encode("utf-8")
                )

            # 귓속말
            elif msg.startswith("/w"):

                parts = msg.split(" ", 2)

                if len(parts) >= 3:

                    target = parts[1]

                    whisper_msg = parts[2]

                    if target in clients:

                        send_msg = (
                            f"[WHISPER] "
                            f"[{now}] "
                            f"{nickname} → {target}\n"
                            f"{whisper_msg}"
                        )

                        clients[target].send(
                            send_msg.encode("utf-8")
                        )

                        client_socket.send(
                            send_msg.encode("utf-8")
                        )

                    else:

                        client_socket.send(
                            "[SYSTEM] 유저를 찾을 수 없습니다.".encode("utf-8")
                        )

            # 이미지
            elif msg.startswith("[IMAGE]"):

                image_path = msg.replace(
                    "[IMAGE]",
                    ""
                )

                broadcast(
                    f"[IMAGE]{nickname}|{image_path}"
                )

            # 일반 채팅
            else:

                send_msg = (
                    f"[CHAT] "
                    f"[{now}] "
                    f"{nickname}\n"
                    f"{msg}"
                )

                broadcast(send_msg)

        except:

            break

    # 퇴장 처리
    if nickname in clients:

        del clients[nickname]

    broadcast(
        f"[SYSTEM] {nickname}님이 퇴장했습니다."
    )

    send_user_list()

    client_socket.close()


# 서버 생성
sock = socket.socket(
    socket.AF_INET,
    socket.SOCK_STREAM
)

sock.setsockopt(
    socket.SOL_SOCKET,
    socket.SO_REUSEADDR,
    1
)

sock.bind(("", 7777))

sock.listen()

print("BLUE WEB CHAT SERVER START")

while True:

    conn, addr = sock.accept()

    recv_handler = threading.Thread(
        target=thread_recv,
        args=(conn, addr)
    )

    recv_handler.start()

gui_client.py

import tkinter
import socket

from threading import Thread

from tkinter import simpledialog
from tkinter import filedialog

from PIL import Image, ImageTk


image_refs = []


# 텍스트 추가
def add_text(message, tag="normal"):

    chat_text.config(state="normal")

    chat_text.insert(
        tkinter.END,
        message + "\n\n",
        tag
    )

    chat_text.see(tkinter.END)

    chat_text.config(state="disabled")


# 이미지 추가
def add_image(nickname, image_path):

    chat_text.config(state="normal")

    chat_text.insert(
        tkinter.END,
        f"[ IMAGE ] {nickname}\n",
        "image_title"
    )

    try:

        img = Image.open(image_path)

        img.thumbnail((220, 220))

        photo = ImageTk.PhotoImage(img)

        image_refs.append(photo)

        chat_text.image_create(
            tkinter.END,
            image=photo
        )

        chat_text.insert(
            tkinter.END,
            "\n\n"
        )

    except:

        chat_text.insert(
            tkinter.END,
            "[이미지 출력 실패]\n\n",
            "system"
        )

    chat_text.see(tkinter.END)

    chat_text.config(state="disabled")


# 메시지 전송
def send(event=None):

    msg = input_msg.get().strip()

    if msg == "":
        return

    sock.send(msg.encode("utf-8"))

    input_msg.set("")

    if msg == "end":

        sock.close()

        win.quit()


# 이미지 전송
def send_image():

    filepath = filedialog.askopenfilename(
        filetypes=[
            (
                "Image Files",
                "*.png;*.jpg;*.jpeg;*.gif"
            )
        ]
    )

    if filepath:

        sock.send(
            f"[IMAGE]{filepath}".encode("utf-8")
        )


# 메시지 수신
def recvMessage():

    try:

        while True:

            msg = sock.recv(4096).decode("utf-8")

            # 시스템
            if msg.startswith("[SYSTEM]"):

                add_text(msg, "system")

            # 접속자
            elif msg.startswith("[USERS]"):

                users = msg.replace(
                    "[USERS]\n",
                    ""
                )

                add_text(
                    "──────── ONLINE ────────\n"
                    + users +
                    "\n────────────────────────",
                    "users"
                )

            # 명령어
            elif msg.startswith("[COMMAND]"):

                add_text(msg, "command")

            # 귓속말
            elif msg.startswith("[WHISPER]"):

                add_text(msg, "whisper")

            # 이미지
            elif msg.startswith("[IMAGE]"):

                data = msg.replace(
                    "[IMAGE]",
                    ""
                )

                nickname, image_path = data.split("|", 1)

                add_image(
                    nickname,
                    image_path
                )

            # 일반 채팅
            elif msg.startswith("[CHAT]"):

                add_text(
                    msg.replace("[CHAT] ", ""),
                    "chat"
                )

            else:

                add_text(msg)

    except:

        add_text(
            "[SYSTEM] 서버 종료",
            "system"
        )


# 종료
def on_delete():

    try:

        sock.send("end".encode("utf-8"))

        sock.close()

    except:

        pass

    win.destroy()


# GUI 생성
win = tkinter.Tk()

win.geometry("700x700")

win.configure(bg="#f5f5f5")


# 닉네임
name = simpledialog.askstring(
    "nickname",
    "닉네임 입력"
)

if name is None or name.strip() == "":
    name = "guest"


# 창 제목
win.title(f"BLUE WEB CHAT :: {name}")


# 상단 제목
title = tkinter.Label(
    win,
    text=f"BLUE WEB CHAT :: {name}",
    font=("Courier New", 24, "bold"),
    fg="#0033ff",
    bg="#f5f5f5"
)

title.pack(pady=15)


# 부제목
sub_title = tkinter.Label(
    win,
    text="RETRO STYLE NETWORK MESSENGER",
    font=("Courier New", 11),
    fg="#0033ff",
    bg="#f5f5f5"
)

sub_title.pack()


# 장식선
line = tkinter.Label(
    win,
    text="******************************************************",
    font=("Courier New", 10),
    fg="#0033ff",
    bg="#f5f5f5"
)

line.pack(pady=10)


# 채팅 프레임
chat_frame = tkinter.Frame(
    win,
    bg="#ffffff",
    highlightbackground="#0033ff",
    highlightthickness=1
)

chat_frame.pack(
    padx=20,
    pady=10,
    fill=tkinter.BOTH,
    expand=True
)


# 스크롤
scroll = tkinter.Scrollbar(chat_frame)

scroll.pack(
    side=tkinter.RIGHT,
    fill=tkinter.Y
)


# 채팅창
chat_text = tkinter.Text(
    chat_frame,
    font=("Courier New", 12),
    fg="#0033ff",
    bg="#ffffff",
    yscrollcommand=scroll.set,
    wrap="word",
    borderwidth=0
)

chat_text.pack(
    side=tkinter.LEFT,
    fill=tkinter.BOTH,
    expand=True
)

scroll.config(command=chat_text.yview)


# 태그 스타일
chat_text.tag_config(
    "system",
    foreground="#0033ff",
    font=("Courier New", 11, "bold")
)

chat_text.tag_config(
    "users",
    foreground="#0033ff",
    font=("Courier New", 11, "bold")
)

chat_text.tag_config(
    "command",
    foreground="#0033ff",
    font=("Courier New", 11)
)

chat_text.tag_config(
    "whisper",
    foreground="#0033ff",
    font=("Courier New", 11, "italic")
)

chat_text.tag_config(
    "chat",
    foreground="#0033ff",
    font=("Courier New", 12)
)

chat_text.tag_config(
    "image_title",
    foreground="#0033ff",
    font=("Courier New", 11, "bold")
)

chat_text.config(state="disabled")


# 하단 프레임
bottom_frame = tkinter.Frame(
    win,
    bg="#f5f5f5"
)

bottom_frame.pack(
    fill=tkinter.X,
    padx=20,
    pady=15
)


# 입력 변수
input_msg = tkinter.StringVar()


# 입력창
inputbox = tkinter.Entry(
    bottom_frame,
    textvariable=input_msg,
    font=("Courier New", 14),
    fg="#0033ff",
    bg="#ffffff",
    insertbackground="#0033ff",
    highlightbackground="#0033ff",
    highlightthickness=1
)

inputbox.pack(
    side=tkinter.LEFT,
    fill=tkinter.X,
    expand=True,
    ipady=12
)

inputbox.bind("<Return>", send)


# 이미지 버튼
image_button = tkinter.Button(
    bottom_frame,
    text="IMAGE",
    command=send_image,
    font=("Courier New", 10, "bold"),
    fg="#0033ff",
    bg="#ffffff",
    activeforeground="#ffffff",
    activebackground="#0033ff",
    relief="solid",
    borderwidth=1
)

image_button.pack(
    side=tkinter.RIGHT,
    padx=5,
    ipadx=10,
    ipady=10
)


# 전송 버튼
send_button = tkinter.Button(
    bottom_frame,
    text="SEND",
    command=send,
    font=("Courier New", 10, "bold"),
    fg="#0033ff",
    bg="#ffffff",
    activeforeground="#ffffff",
    activebackground="#0033ff",
    relief="solid",
    borderwidth=1
)

send_button.pack(
    side=tkinter.RIGHT,
    padx=5,
    ipadx=10,
    ipady=10
)


# 종료 이벤트
win.protocol(
    "WM_DELETE_WINDOW",
    on_delete
)


# 서버 연결
IP = "localhost"

PORT = 7777

sock = socket.socket(
    socket.AF_INET,
    socket.SOCK_STREAM
)

sock.connect((IP, PORT))


# 닉네임 전송
sock.send(name.encode("utf-8"))


# 수신 쓰레드
receive_thread = Thread(
    target=recvMessage
)

receive_thread.daemon = True

receive_thread.start()


# 포커스
inputbox.focus()


# 실행
win.mainloop()

 

 

 


 

 

 

 

Network Programming.zip
0.02MB

'2026년도 1학기 > 정보 보안' 카테고리의 다른 글

정보보안  (0) 2026.04.15

관련글 더보기