Socket 을 이용한 chatting app 만들기

Yoongja·2021년 11월 30일
1

kivy

목록 보기
2/2
post-thumbnail

풀스택서비스 네트워킹 프로젝트로 chatting app을 만들게 되었고 중간 과정을 적어보려 한다.

<본 포스팅에 사용된 모든 자료는 이성원교수님 풀스택서비스네트워킹 강의자료 내용이다>

일단 socket api 호출 순서에 대하여 알아보자

이제,간단한 1:1 socket programming부터 만들어보자

본격 코드가 나오기전 내가 구성할 화면의 layout을 먼저 보자

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView


class Scroll(ScrollView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        self.layout = GridLayout(cols=1, size_hint_y=None)
        self.add_widget(self.layout)
        self.chatHistory = Label(size_hint_y=None, markup=True)
        self.scroll = Label()
        self.layout.add_widget(self.chatHistory)
        self.layout.add_widget(self.scroll)
        
    def updateChat(self, message):

        self.chatHistory.text += '\n' + message    
        self.layout.height = self.chatHistory.texture_size[1] + 15
        self.chatHistory.height = self.chatHistory.texture_size[1]
        self.chatHistory.text_size = (self.chatHistory.width * 0.98, None)
        self.scroll_to(self.scroll)
        
        
class chatPage(GridLayout):

    def __init__(self, **kwargs):
        super(chatPage, self).__init__(**kwargs)
        self.rows = 2
        self.cols = 1
        
        self.history = Scroll(height=Window.size[1]*0.9,size_hint_y = None)
        self.add_widget(self.history)
        self.new_message = TextInput(width=Window.size[0]*0.8, size_hint_x=None, multiline=False)
        self.send = Button(text="Send")
        self.send.bind(on_press=self.sendMessage)
        
        bottom_line = GridLayout(cols=2)
        bottom_line.add_widget(self.new_message)
        bottom_line.add_widget(self.send)
        self.add_widget(bottom_line)
        
    def sendMessage(self,_):
        message = self.new_message.text
        self.new_message.text=""
        if message:
            self.history.updateChat(f"[color=20dddd]>:[/color] {message}")
            
                
    def historyMessage(self, username, message):
        self.history.updateChat(
            f"[color=20dd20]{username}[/color] [color=20dddd]>:[/color] {message}")


class MyApp(App):

    def build(self):
        return chatPage()


if __name__ == '__main__':
    MyApp().run()


아래 흰색이 입력창이고 send버튼을 누르면 history에 update되게 되어있다.

personServer.py

import socket

HOST = '127.0.0.1'  
PORT = 65456  

def main():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as serverSocket:
        try:
            if serverSocket.bind((HOST, PORT)) == -1:
                print('> bind() failed and program terminated')
                serverSocket.close()
                return
        except Exception as exceptionObj:
            print('> bind() failed by exception:', exceptionObj)
            serverSocket.close()
            return
    
        if serverSocket.listen() == -1:
            print('> listen() failed and program terminated')
            serverSocket.close()
            return
            
        clientSocket, clientAddress = serverSocket.accept()
        
        with clientSocket:
            print('> client connected by IP address {0} with Port number {1}'.format(clientAddress[0], clientAddress[1]))
            while True:
                # [=start=]
                RecvData = clientSocket.recv(1024)
                print('> echoed:', RecvData.decode('utf-8'))
                clientSocket.sendall(RecvData)
                if RecvData.decode('utf-8') == 'quit':
                    break
                # [==end==]

if __name__ == "__main__":
    print('> echo-server is activated')
    main()
    print('> echo-server is de-activated')

personClient.py

import socket

HOST = '127.0.0.1'
PORT = 65456

def connect():
    global clientSocket
    clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clientSocket.connect((HOST, PORT))
        
def send(message):
    message = message.encode('utf-8')
    clientSocket.send(message)

1:1 통신에서는 본 main 함수에서 listen api를 사용하지 않아 지워버렸다.

main.py

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView

import personClient



class Scroll(ScrollView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        self.layout = GridLayout(cols=1, size_hint_y=None)
        self.add_widget(self.layout)
        self.chatHistory = Label(size_hint_y=None, markup=True)
        self.scroll = Label()
        self.layout.add_widget(self.chatHistory)
        self.layout.add_widget(self.scroll)
        
    def updateChat(self, message):

        self.chatHistory.text += '\n' + message    
        self.layout.height = self.chatHistory.texture_size[1] + 15
        self.chatHistory.height = self.chatHistory.texture_size[1]
        self.chatHistory.text_size = (self.chatHistory.width * 0.98, None)
        self.scroll_to(self.scroll)
        
        
class chatPage(GridLayout):

    def __init__(self, **kwargs):
        super(chatPage, self).__init__(**kwargs)
        self.rows = 2
        self.cols = 1
        
        self.history = Scroll(height=Window.size[1]*0.9,size_hint_y = None)
        self.add_widget(self.history)
        self.new_message = TextInput(width=Window.size[0]*0.8, size_hint_x=None, multiline=False)
        self.send = Button(text="Send")
        self.send.bind(on_press=self.sendMessage)
        
        bottom_line = GridLayout(cols=2)
        bottom_line.add_widget(self.new_message)
        bottom_line.add_widget(self.send)
        self.add_widget(bottom_line)
        personClient.connect()
        personClient
        
    def sendMessage(self,_):
        message = self.new_message.text
        self.new_message.text=""
        if message:
            self.history.updateChat(f"[color=20dddd]>:[/color] {message}")
        personClient.send(message)    
                
    


class MyApp(App):

    def build(self):
        return chatPage()


if __name__ == '__main__':
    MyApp().run()

실행화면

multithread를 이용한 N:M통신

server.py

import socketserver
import threading

# {CHAT#1} Create a DB to register all client's socket information
group_queue = []

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # Show a client connection information
        print('> client connected by IP address {0} with Port number {1}'.format(self.client_address[0], self.client_address[1]))

        # {CHAT#2} Import a client DB into Request Handler
        global group_queue
        # {CHAT#3} Register a new client connection information into a client DB
        group_queue.append(self.request)

        while True:
            # [=start=]
            RecvData = self.request.recv(1024)
            if RecvData.decode('utf-8') == 'quit':
                # {CHAT#4} Deregister a disconnected client from a client DB
                group_queue.remove(self.request)
                break
            else:
                # {CHAT#5} Forward a client message to whole clients (currently a broadcast)
                print('> received (', RecvData.decode('utf-8'), ') and echoed to ', len(group_queue), 'clients')
                for conn in group_queue:
                    conn.sendall(RecvData)
            # [==end==]

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = "localhost", 65456
    print('> echo-server is activated')

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)

        # Set to exit the server thread when the main thread terminates, then execute the main thread
        server_thread.daemon = True
        server_thread.start()
        print("> server loop running in thread (main thread):", server_thread.name)
        
        # Server termination by input "quit" when all client connections are disconnected
        baseThreadNumber = threading.active_count()
        while True:
            msg = input('> ')
            if msg == 'quit':
                if baseThreadNumber == threading.active_count():
                    print("> stop procedure started")
                    break
                else:
                    print("> active threads are remained :", threading.active_count() - baseThreadNumber, "threads")

        print('> echo-server is de-activated')
        server.shutdown()
        import socketserver
import threading

# {CHAT#1} Create a DB to register all client's socket information
group_queue = []

class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # Show a client connection information
        print('> client connected by IP address {0} with Port number {1}'.format(self.client_address[0], self.client_address[1]))

        # {CHAT#2} Import a client DB into Request Handler
        global group_queue
        # {CHAT#3} Register a new client connection information into a client DB
        group_queue.append(self.request)

        while True:
            # [=start=]
            RecvData = self.request.recv(1024)
            if RecvData.decode('utf-8') == 'quit':
                # {CHAT#4} Deregister a disconnected client from a client DB
                group_queue.remove(self.request)
                break
            else:
                # {CHAT#5} Forward a client message to whole clients (currently a broadcast)
                print('> received (', RecvData.decode('utf-8'), ') and echoed to ', len(group_queue), 'clients')
                for conn in group_queue:
                    conn.sendall(RecvData)
            # [==end==]

class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = "localhost", 65456
    print('> echo-server is activated')

    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
    with server:
        ip, port = server.server_address

        # Start a thread with the server -- that thread will then start one
        # more thread for each request
        server_thread = threading.Thread(target=server.serve_forever)

        # Set to exit the server thread when the main thread terminates, then execute the main thread
        server_thread.daemon = True
        server_thread.start()
        print("> server loop running in thread (main thread):", server_thread.name)
        
        # Server termination by input "quit" when all client connections are disconnected
        baseThreadNumber = threading.active_count()
        while True:
            msg = input('> ')
            if msg == 'quit':
                if baseThreadNumber == threading.active_count():
                    print("> stop procedure started")
                    break
                else:
                    print("> active threads are remained :", threading.active_count() - baseThreadNumber, "threads")

        print('> echo-server is de-activated')
        server.shutdown()

client.py

import socket
import threading

HOST = '127.0.0.1'
PORT = 65456

# {CHAT#1} Create a separate receive handler


def connect():
    global clientSocket
    clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    clientSocket.connect((HOST, PORT))
    return clientSocket

def listen(comming):
    while True:
        message = clientSocket.recv(1024)
        if message.decode('utf-8') == "quit":
            break 
        comming(message)
    
def startlistening(comming):
    clientThread = threading.Thread(target=listen, args=(comming,))
    clientThread.daemon = True
    clientThread.start()
    
def send(message):
    message = message.encode('utf-8')
    clientSocket.send(message)
        # [==end==]

main.py

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.core.window import Window
from kivy.uix.button import Button
from kivy.uix.scrollview import ScrollView

import client



class Scroll(ScrollView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        
        self.layout = GridLayout(cols=1, size_hint_y=None)
        self.add_widget(self.layout)
        self.chatHistory = Label(size_hint_y=None, markup=True)
        self.scroll = Label()
        self.layout.add_widget(self.chatHistory)
        self.layout.add_widget(self.scroll)
        
    def updateChat(self, message):

        self.chatHistory.text += '\n' + message    
        self.layout.height = self.chatHistory.texture_size[1] + 15
        self.chatHistory.height = self.chatHistory.texture_size[1]
        self.chatHistory.text_size = (self.chatHistory.width * 0.98, None)
        self.scroll_to(self.scroll)
        
        
class chatPage(GridLayout):

    def __init__(self, **kwargs):
        super(chatPage, self).__init__(**kwargs)
        self.rows = 2
        self.cols = 1
        
        self.history = Scroll(height=Window.size[1]*0.9,size_hint_y = None)
        self.add_widget(self.history)
        self.new_message = TextInput(width=Window.size[0]*0.8, size_hint_x=None, multiline=False)
        self.send = Button(text="Send")
        self.send.bind(on_press=self.sendMessage)
        
        bottom_line = GridLayout(cols=2)
        bottom_line.add_widget(self.new_message)
        bottom_line.add_widget(self.send)
        self.add_widget(bottom_line)
        client.connect()
        client.startlistening(self.commingMessage)
        
    def sendMessage(self,_):
        message = self.new_message.text
        self.new_message.text=""
        if message:
            self.history.updateChat(f"[color=7e4a8f]send:[/color] {message}")
        client.send(message)    
                
    def commingMessage(self,message):
        self.history.updateChat(f"[color=20dddd]recieve:[/color] {message}")


class MyApp(App):

    def build(self):
        return chatPage()


if __name__ == '__main__':
    MyApp().run()    

실행화면

profile
Belief in the possibility

0개의 댓글