소켓 통신의 이해를 위해 Python으로 간단한 소켓 통신을 구현해본다.

 

1. 서버 측

from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread

BUF_SIZ = 1024
user_list = dict()


# 수신 핸들러
def receive_handler(client_socket, user_name):
    # 클라이언트 소켓으로부터 메시지 수신대기
    while True:
        try:
            data = client_socket.recv(BUF_SIZ).decode()

        # 클라이언트와 연결이 끊어진 경우 반복 종료
        except:
            print(user_name, '와의 연결이 끊어졌습니다.')
            break

        # 클라이언트가 종료 명령어를 입력한 경우 반복 종료
        if data == '/종료':
            break

        # 유저명: 메시지 형태로 다이얼로그 생성
        dialog = f'{user_name}: {data}'

        # 자신과 연결된 다른 클라이언트들에게 메시지 출력
        print(dialog)
        for user in user_list:
            # 메시지를 보낸 유저에겐 출력하지 않음
            if user == user_name:
                continue
            user_list[user].sendall(dialog.encode())

    # 클라이언트 연결 종료
    # 유저 리스트에서 해당 유저 제거
    del user_list[user_name]

    # 자신과 연결된 다른 클라이언트들에게 해당 유저의 퇴장알림 메시지 출력
    msg = user_name+'님이 퇴장하셨습니다.'
    print(msg)
    for user in user_list:
        user_list[user].sendall(msg.encode())

    # 클라이언트 소켓 close
    client_socket.close()


if __name__ == '__main__':
    # 서버측 소켓 생성(socket)
    # 주소 체계와 소켓 타입을 입력하여 생성한다.
    # 인터넷 IPv4 주소체계를 사용하기 위해 AF_INET 을 사용
    # TCP 소켓을 사용하기 위해 SOCK_STREAM 을 사용
    server_socket = socket(AF_INET, SOCK_STREAM)

    # 바인딩에 성공할 때 까지 입력을 다시받음
    while True:
        try:
            print('HOST IP: ', end='')
            HOST = input()
            print('PORT: ', end='')
            PORT = int(input())

            # 연결할 서버의 주소, 포트를 입력하여 연결 요청
            server_socket.bind(('localhost', PORT))
            break
        except:
            continue

    # 수신 대기열 생성
    # 입력한 숫자만큼 대기열을 생성
    server_socket.listen(5)
    print('>> 채팅방 개설')

    # 무한루프
    while True:
        try:
            # 클라이언트로부터 로그인 요청을 대기
            connection_socket, addr = server_socket.accept()

            # 유저 등록
            user_name = connection_socket.recv(BUF_SIZ).decode()

            # 이미 존재하는 이름으로 로그인 시도시 재입력 요구
            while user_name in user_list:
                connection_socket.send('exist username'.encode())
                user_name = connection_socket.recv(BUF_SIZ).decode()

            # 로그인 성공시 성공메시지를 송신하고 유저리스트에 추가
            user_list[user_name] = connection_socket
            connection_socket.send('ok'.encode())

            # 클라이언트로부터 메시지를 수신하기 위한 스레드 실행
            receive_thread = Thread(target=receive_handler, args=(connection_socket, user_name))
            receive_thread.daemon = True
            receive_thread.start()

            # 해당 유저의 입장알림 메시지를 자신과 다른 연결된 클라이언트들에게 출력
            msg = user_name+'님이 입장하셨습니다.'
            print(msg)
            for user in user_list:
                user_list[user].sendall(msg.encode())

        # 서버 종료시 모든 클라이언트 소켓 close
        except KeyboardInterrupt:
            for user in user_list:
                user_list[user].close()

 

  • host 주소와 port 번호를 입력받아 채팅방 개설
     
  • 수신 대기열을 만들고 클라이언트로부터 연결요청을 대기

  • 연결요청이 들어오고 user_name이 전송되면 중복검사

  • 중복 user_name인 경우 재입력 요구

  • 중복되지 않는 user_name을 입력받을 경우 user_list에 user_name : connection_socket 의 형태로 저장

  • 해당 클라이언트로부터 메시지를 전송받기 위한 스레드를 daemon으로 생성
    => daemon 스레드는 메인 프로세스가 종료시 자신의 실행여부에 관계없이 종료된다.

  • 메시지 수신 핸들러는 클라이언트로부터 메시지를 전송받아 일반 메시지라면 유저명: 메시지 의 형태로
    포매팅하여 연결된 전 클라이언트에게 전송.  종료 명령어라면 해당 클라이언트의 소켓을 close 하고
    해당 클라이언트의 퇴장을 연결중인 전 클라이언트에게 알림

  • 해당 유저의 입장을 연결중인 전 클라이언트에게 알림

  • 서버 종료시 모든 클라이언트 소켓 close

 

 

2. 클라이언트 측

from socket import socket, AF_INET, SOCK_STREAM
from threading import Thread

BUF_SIZ = 1024


# 수신 핸들러
def receive_handler(client_socket):
    while True:
        try:
            data = client_socket.recv(BUF_SIZ)
            data = data.decode()
            print(data)
        except:
            print('연결 종료')
            break


# 송신 핸들러
def send_handler(client_socket):
    # 사용자가 '/종료' 를 입력하기 전까지 입력을 받아 서버에 전송
    while True:
        data = input()
        client_socket.sendall(data.encode())
        if data == '/종료':
            break
        
    # 소켓 close
    client_socket.close()


if __name__ == '__main__':
    # 클라이언트측 소켓 생성(socket)
    # 주소 체계와 소켓 타입을 입력하여 생성한다.
    # 인터넷 IPv4 주소체계를 사용하기 위해 AF_INET 을 사용
    # TCP 소켓을 사용하기 위해 SOCK_STREAM 을 사용
    client_socket = socket(AF_INET, SOCK_STREAM)

    # 연결에 성공할 때 까지 입력을 다시받음
    while True:
        try:
            print('HOST IP: ', end='')
            HOST = input()
            print('PORT: ', end='')
            PORT = int(input())

            # 연결할 서버의 주소, 포트를 입력하여 연결 요청
            client_socket.connect(('localhost', PORT))
            break
        except:
            continue

    # 로그인
    while True:
        print('USER_ID: ', end='')
        user = input()
        client_socket.send(user.encode())
        result = client_socket.recv(BUF_SIZ).decode()
        if result == 'ok':
            break
        else:
            print('이미 존재하는 유저명입니다.')

    # 로그인 성공시
    # 수신 대기 스레드
    receive_thread = Thread(target=receive_handler, args=(client_socket,))
    receive_thread.daemon = True
    receive_thread.start()

    # 송신 대기 스레드
    send_thread = Thread(target=send_handler, args=(client_socket,))
    send_thread.daemon = True
    send_thread.start()

    # 수신 대기 스레드의 종료를 대기
    # 송신 대기 스레드가 종료시 소켓이 닫혀 수신 대기 스레드도 자동종료
    # 수신 대기 스레드가 종료시 메인 프로세스가 종료되며 송신 대기 스레드도 자동종료
    receive_thread.join()
  • 서버의 host 주소와 port 번호를 입력받아 연결요청

  • 연결 성공시 유저명을 입력하여 채팅방 입장 시도

  • 중복되는 유저명일 경우 재입력

  • 입장 성공시 수신 대기 스레드와 송신 대기 스레드를 daemon으로 생성

  • 수신 대기 스레드는 서버와의 연결이 끊어지기 전까지 서버로부터 전송되는 메시지를 받아 출력

  • 송신 대기 스레드는 입력된 메시지를 서버에 전송 종료 명령어가 입력될 경우 반복문을 빠져나와 소켓을 close

 

'CS > 네트워크' 카테고리의 다른 글

#6 Transport Layer  (0) 2021.11.16
#5 Application Layer 2 - HTTP  (0) 2021.11.01
#3 Application Layer 1 - 소켓(Socket)  (0) 2021.10.28
#2 OSI 7계층  (0) 2021.10.28
#1 네트워크 구성 2 - Network Core  (0) 2021.10.28

+ Recent posts