이번엔 소켓 프로그래밍의 확장버전인 실시간 채팅 프로그램을 만들어 보도록 하겠습니다.

구현할 기능을 대략적으로 살펴보면 아래와 같이 정리 할 수 있습니다.

 

[핵심 기능]

1. 서버는 클라이언트의 모든 메시지를 받아서 채팅에 참여한 각 클라이언트에게 모두 동일한 메시지를 전달해 주어야 한다.

2. 클라이언트와 서버는 비동기 통신을 해야한다. 즉, 클라이언트와 서버는 메시지를 보내는 동시에 받는 기능도 구현되어야 한다.

3. 클라이언트는 언제든지 채팅서버에 접속하여 다른 클라이언트와 메시지를 주고 받을 수 있어야 한다.

 

 

[Server]

import socket
import threading
from queue import Queue


def Send(group, send_queue):
    print('Thread Send Start')
    while True:
        try:
            #새롭게 추가된 클라이언트가 있을 경우 Send 쓰레드를 새롭게 만들기 위해 루프를 빠져나감
            recv = send_queue.get()
            if recv == 'Group Changed':
                print('Group Changed')
                break


            #for 문을 돌면서 모든 클라이언트에게 동일한 메시지를 보냄
            for conn in group:
                msg = 'Client' + str(recv[2]) + ' >> ' + str(recv[0])
                if recv[1] != conn: #client 본인이 보낸 메시지는 받을 필요가 없기 때문에 제외시킴
                    conn.send(bytes(msg.encode()))
                else:
                    pass
        except:
            pass


def Recv(conn, count, send_queue):
    print('Thread Recv' + str(count) + ' Start')
    while True:
        data = conn.recv(1024).decode()
        send_queue.put([data, conn, count]) #각각의 클라이언트의 메시지, 소켓정보, 쓰레드 번호를 send로 보냄


# TCP Echo Server
if __name__ == '__main__':
    send_queue = Queue()
    HOST = ''  # 수신 받을 모든 IP를 의미
    PORT = 9000  # 수신받을 Port
    server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # TCP Socket
    server_sock.bind((HOST, PORT))  # 소켓에 수신받을 IP주소와 PORT를 설정
    server_sock.listen(10)  # 소켓 연결, 여기서 파라미터는 접속수를 의미
    count = 0
    group = [] #연결된 클라이언트의 소켓정보를 리스트로 묶기 위함
    while True:
        count = count + 1
        conn, addr = server_sock.accept()  # 해당 소켓을 열고 대기
        group.append(conn) #연결된 클라이언트의 소켓정보
        print('Connected ' + str(addr))


        #소켓에 연결된 모든 클라이언트에게 동일한 메시지를 보내기 위한 쓰레드(브로드캐스트)
        #연결된 클라이언트가 1명 이상일 경우 변경된 group 리스트로 반영

        if count > 1:
            send_queue.put('Group Changed')
            thread1 = threading.Thread(target=Send, args=(group, send_queue,))
            thread1.start()
            pass
        else:
            thread1 = threading.Thread(target=Send, args=(group, send_queue,))
            thread1.start()

        #소켓에 연결된 각각의 클라이언트의 메시지를 받을 쓰레드
        thread2 = threading.Thread(target=Recv, args=(conn, count, send_queue,))
        thread2.start()

서버기능을 순서대로 대략적으로 살펴보면 아래와 같습니다.

[알고리즘]

1. 9000번 포트를 열고 while 문을 무한으로 돌며 클라이언트를 기다림

2. 클라이언트가 접속하면 count 를 1 증가시키고 group 이라는 리스트에 클라이언트 커넥션(conn) 정보를 담는다.

3. 만약 접속자가 1명 이상일 경우 'Group Change'라는 메시지를 queue를 이용해 send쪽으로 보내 Send 쓰레드를 종료시키고 새로 접속한 사용가 포함된 group 리스트를 인자로하는 쓰레드를 재생성한다.

4. Send 쓰레드가 생성되면 while 문을 돌며 Recv 쓰레드에서 queue를 통해 받은 메시지들 모든 클라이언트에게 보낸다. 단, 클라이언트 자신이 보낸 메시지는 받을 필요가 없으므로 conn정보를 비교하여 자신에게는 보내지 않도록 한다.

5. Recv쓰레드가 생성되면 while문을 돌며 메시지 받고 메시지를 보낸 상대의 conn정보와 메시지를 queue를 이용해 send로 보낸다.

6. 이후  4번와 같은 작업을 반복한다.

 

[Client]

import socket
import threading

def Send(client_sock):
    while True:
        send_data = bytes(input().encode()) # 사용자 입력
        client_sock.send(send_data)  # Client -> Server 데이터 송신

def Recv(client_sock):
    while True:
        recv_data = client_sock.recv(1024).decode()  # Server -> Client 데이터 수신
        print(recv_data)

#TCP Client
if __name__ == '__main__':
    client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #TCP Socket
    Host = 'localhost' #통신할 대상의 IP 주소
    Port = 9000  #통신할 대상의 Port 주소
    client_sock.connect((Host, Port)) #서버로 연결시도
    print('Connecting to ', Host, Port)


    #Client의 메시지를 보낼 쓰레드
    thread1 = threading.Thread(target=Send, args=(client_sock, ))
    thread1.start()

    #Server로 부터 다른 클라이언트의 메시지를 받을 쓰레드
    thread2 = threading.Thread(target=Recv, args=(client_sock, ))
    thread2.start()

클라이언트의 기능을 순서대로 대략적으로 살펴보면 아래와 같습니다.

 

[알고리즘]

1. 소켓을 이용하여 9000번 포트 접속한다.

2. Send 쓰레드와 Recv 쓰레드를 생성하여 동시에 메시지를 주고 받을 수 있도록 만들어준다.

3. while 문을 돌며 input()함수를 이용하여 메시지를 작성하여 서버로 메시지를 보낸다.

4. while 문을 돌며 서버로부터 메시지를 지속적으로 받아 출력한다.

 

[실행]

위 처럼 3개의 클라이언트가 서버에 접속하여 서로 메시지를 공유할 수 있습니다.

유용하게 사용하세요 ~

+ Recent posts