개인 프로젝트/간단한 프로젝트

[개인프로젝트] tkinter로 GUI 입힌 tcp/ip 소켓 통신 프로그램 만들기

에버듀 2020. 8. 7. 18:13
반응형

개인적으로 네트워크 관련 내용을 배울 기회를 갖게 되어, 배운 내용을 기반으로 채팅프로그램을 만들어보았습니다.

 

서버 소스 코드

''' 서버 예제
각 클라이어언트가 보낸 메세지를 서버에서 클라이언트 정보와 조합하여 각 클라이언트 채팅창에 띄워 보내 주는 역할 '''
import socket
from _thread import *
from tkinter import *

def threaded(client_socket, addr):
    global chat_log
    chat_log['state'] = 'normal'
    chat_log.insert("end", 'Connected by :'+ addr[0] + ':' + str(addr[1]) + '\n')
    chat_log['state'] = 'disabled'
    for c in c_list:
        c.sendall(('[System] ' + str(addr[1]) + ' 님이 접속하였습니다.').encode())
    while 1:
        try:
            data = client_socket.recv(1024)
            chat_log['state'] = 'normal'
            chat_log.insert("end", 'Received from ' + addr[0] + ' : ' + str(addr[1]) + ' :: ' + str(data.decode()) + '\n')
            chat_log['state'] = 'disabled'
            for c in c_list:
                c.sendall((str(addr[1]) + ' : ' + data.decode()).encode())
        except ConnectionResetError as e:
            c_list.remove(client_socket)
            for c in c_list:
                c.sendall(('[System] '+ str(addr[1]) + ' 님이 나갔습니다.').encode())
            chat_log['state'] = 'normal'
            chat_log.insert("end", 'Disconnected by ' + addr[0] + ':' + str(addr[1]) + '\n')
            chat_log['state'] = 'disabled'
            break
    client_socket.close()

def server_open():
    HOST = ip_entry.get(); PORT = int(port_entry.get())
    start_new_thread(make_server,(HOST,PORT))
    open_button['state'] = 'disabled'
    ip_entry['state'] = 'readonly'
    port_entry['state'] = 'readonly'

def server_close():
    exit()

def make_server(HOST, PORT):
    global server_socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 포트사용중이라 연결할 수 없다는 WinError 10048 에러를 해결하기 위해 필요합니다.
    # 서버 소켓의 SOL_SOCKET의 SO_REUSEADDR(이미 사용중인 포트에 대해서도 바인드 허용) 를 1(True)로 설정하는 것으로 이해
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    server_socket.bind((HOST, PORT))
    server_socket.listen()
    chat_log['state'] = 'normal'
    chat_log.insert("end", 'Server Start\n')
    chat_log['state'] = 'disabled'

    while 1:
        client_socket, addr = server_socket.accept()
        c_list.append(client_socket)
        start_new_thread(threaded, (c_list[-1], addr))

c_list = []
close = False
server_socket = None

s_root = Tk()
s_root.geometry('500x500')
s_root.title('Server')
s_root.resizable(False, False)

''' Top Menu '''
Label(s_root, text = 'Server IP : ').place(x=20, y=20)
Label(s_root, text = 'Port : ').place(x=250, y=20)
ip_entry = Entry(s_root, width=14, text = '127.0.0.1'); ip_entry.place(x=83, y=21)
ip_entry.insert(0,'127.0.0.1')
port_entry = Entry(s_root, width=5, text = '9999'); port_entry.place(x = 290, y=21)
port_entry.insert(0,'9999')
open_button = Button(s_root,text='Server Open', command=server_open); open_button.place(x=380, y=18)

''' Middle Menu '''
chat_log = Text(s_root, width = 65, height = 29, state = 'disabled', spacing2 = 2) ; chat_log.place(x=20, y=60)

''' Bottom Menu '''
close_button = Button(s_root,text='Server Close',command=server_close); close_button.place(x=200, y = 460)
s_root.mainloop()

클라이언트 소스코드

import socket
from _thread import *
import threading
from tkinter import *
from time import sleep

def send(socket):
    global go_send
    while True:
        if go_send:
            message = (message_input.get(1.0,"end")).rstrip()
            socket.send(message.encode())
            message_input.delete(1.0, "end")
            go_send = False
        else:
            if go_out:
                socket.close()
                exit()
            sleep(0.1)

def receive(socket):
    first = True
    while True:
        try:
            data = socket.recv(1024)
            chat_log['state'] = 'normal'
            if first: # 이걸 처음 체크 이후 의미없이 매번 체크하므로 이렇게 하는 건 효율적이지 않음.
                chat_log.insert("end",str(data.decode( )))
                first = False
            else:
                chat_log.insert("end",'\n' + str(data.decode()))
                chat_log.see('end')
            chat_log['state'] = 'disabled'
        except ConnectionAbortedError as e:
            chat_log['state'] = 'normal'
            chat_log.insert("end", '\n[System] 접속을 종료합니다.\n')
            chat_log['state'] = 'disabled'
            exit()

def login():
    # 서버의 ip주소 및 포트
    HOST = ip_entry.get(); PORT = int(port_entry.get())
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client_socket.connect((HOST, PORT))

    threading.Thread(target=send, args= (client_socket,)).start()
    threading.Thread(target=receive, args= (client_socket,)).start()
    exit()

def try_login():
    global go_out
    start_new_thread(login,())
    login_button['state'] = 'disabled'
    logout_button['state'] = 'active'
    ip_entry['state'] = 'readonly'
    port_entry['state'] = 'readonly'
    go_out = False

def try_logout():
    global go_out
    login_button['state'] = 'active'
    logout_button['state'] = 'disabled'
    ip_entry['state'] = 'normal'
    port_entry['state'] = 'normal'
    go_out = True

def set_go_send(event):
    global go_send
    go_send = True

go_out, go_send = False, False
c_root = Tk()
c_root.geometry('500x500')
c_root.title('Client')
c_root.resizable(False, False)

''' Top Menu '''
Label(c_root, text = 'Server IP : ').place(x=20, y=20)
Label(c_root, text = 'Port : ').place(x=250, y=20)
ip_entry = Entry(c_root, width=14); ip_entry.place(x=83, y=21)
ip_entry.insert(0,'127.0.0.1')
port_entry = Entry(c_root, width=5); port_entry.place(x = 290, y=21)
port_entry.insert(0,'9999')
login_button = Button(c_root,text='Log In', command=try_login); login_button.place(x=350, y=18)
logout_button = Button(c_root,text='Log Out',state = 'disabled', command = try_logout); logout_button.place(x=420, y=18)

''' Middle Menu '''
chat_frame = Frame(c_root)
scrollbar = Scrollbar(chat_frame) ; scrollbar.pack(side='right',fill='y')
chat_log = Text(chat_frame, width = 62, height = 24, state = 'disabled', yscrollcommand = scrollbar.set) ; chat_log.pack(side='left')#place(x=20, y=60)
scrollbar['command'] = chat_log.yview
chat_frame.place(x=20, y=60)
message_input = Text(c_root, width = 55, height = 4) ; message_input.place(x=20,y = 390)
send_button = Button(c_root, text = 'Send', command = lambda: set_go_send(None)); send_button.place(x=430, y=405)
message_input.bind("<Return>",set_go_send)

''' Bottom Menu '''
close_button = Button(c_root,text='Close',command=exit); close_button.place(x=200, y = 460)

c_root.mainloop()
반응형