Python 소켓을 사용하여 메시지 송수신 방법

PythonBeginner
지금 연습하기

소개

Python 소켓은 네트워크 통신을 위한 강력한 도구를 제공하여, 서로 다른 시스템 간에 메시지를 송수신할 수 있게 해줍니다. 이 튜토리얼에서는 Python 소켓을 사용하여 연결을 설정하고, 데이터를 전송하며, 응답을 수신하는 과정을 안내합니다. 이 랩을 마치면, 네트워크를 통해 서로 통신할 수 있는 클라이언트와 서버 애플리케이션을 모두 구축하게 될 것입니다.

이러한 실용적인 지식은 채팅 프로그램에서 분산 시스템에 이르기까지, 더 복잡한 네트워크 애플리케이션을 개발하기 위한 기반을 형성합니다.

이것은 가이드 실험입니다. 학습과 실습을 돕기 위한 단계별 지침을 제공합니다.각 단계를 완료하고 실무 경험을 쌓기 위해 지침을 주의 깊게 따르세요. 과거 데이터에 따르면, 이것은 중급 레벨의 실험이며 완료율은 71%입니다.학습자들로부터 100%의 긍정적인 리뷰율을 받았습니다.

소켓 기본 이해 및 첫 번째 소켓 생성

소켓이 무엇인지, Python 에서 어떻게 작동하는지 이해하는 것부터 시작해 보겠습니다. 그런 다음 첫 번째 소켓을 생성하여 초기화 방법을 살펴보겠습니다.

Python 소켓이란 무엇인가요?

소켓은 네트워크를 통해 데이터를 송수신하기 위한 종착점 (endpoint) 입니다. 소켓은 네트워크 통신에 대한 프로그래밍 인터페이스를 제공하여, 애플리케이션이 위치에 관계없이 정보를 교환할 수 있도록 합니다.

네트워크 프로그래밍에서는 일반적으로 클라이언트 - 서버 모델을 사용합니다.

  • 서버는 들어오는 연결을 기다리고 요청을 처리합니다.
  • 클라이언트는 서버에 연결하여 통신을 시작합니다.

Python 의 내장 socket 모듈을 사용하면 네트워크 프로토콜의 복잡한 세부 사항을 모두 이해하지 않고도 소켓을 쉽게 사용할 수 있습니다.

소켓 유형

가장 일반적인 두 가지 소켓 유형은 다음과 같습니다.

  • TCP (Transmission Control Protocol): 신뢰할 수 있고 순서가 보장된 데이터 전송을 제공합니다.
  • UDP (User Datagram Protocol): 더 빠르지만 신뢰할 수 없는 데이터 전송을 제공합니다.

이 랩에서는 신뢰할 수 있는 통신이 필요한 애플리케이션에 가장 일반적으로 사용되는 유형인 TCP 소켓에 중점을 둡니다.

첫 번째 소켓 생성

소켓을 초기화하는 간단한 Python 스크립트를 만들어 보겠습니다. WebIDE 를 열고 다음 단계를 따르세요.

  1. 먼저, 소켓 프로그래밍 프로젝트를 위한 디렉토리를 생성해 보겠습니다.
mkdir -p ~/project/socket_lab
cd ~/project/socket_lab
  1. 이제 socket_basics.py라는 새 Python 파일을 생성합니다.

WebIDE 에서 "New File" 버튼을 클릭하거나 "File" 메뉴를 사용하고 "New File"을 선택한 다음, socket_lab 디렉토리 내에서 socket_basics.py로 이름을 지정합니다.

  1. 소켓을 생성하는 방법을 보여주기 위해 다음 코드를 추가합니다.
import socket

## Creating a socket
print("Creating a new socket...")
my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(f"Socket created: {my_socket}")

## Explaining the parameters:
print("\nSocket parameters explained:")
print("AF_INET: Using IPv4 addressing")
print("SOCK_STREAM: Using TCP protocol for reliable data transmission")

## Getting available socket methods
print("\nSome methods available on socket objects:")
methods = [method for method in dir(my_socket) if not method.startswith('_')]
print(', '.join(methods[:10]) + '...')  ## Showing first 10 methods

## Closing the socket
my_socket.close()
print("\nSocket closed")
  1. 파일을 저장합니다.

  2. 스크립트를 실행하여 출력을 확인합니다.

python3 ~/project/socket_lab/socket_basics.py

다음과 유사한 출력을 볼 수 있습니다.

Creating a new socket...
Socket created: <socket.socket fd=3, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 0)>

Socket parameters explained:
AF_INET: Using IPv4 addressing
SOCK_STREAM: Using TCP protocol for reliable data transmission

Some methods available on socket objects:
accept, bind, close, connect, connect_ex, detach, fileno, getpeername, getsockname, getsockopt...

Socket closed

이 출력은 소켓 객체를 성공적으로 생성하고 일부 속성을 탐색했음을 보여줍니다. 다음 섹션에서는 소켓을 사용하여 서로 통신할 수 있는 서버 및 클라이언트 애플리케이션을 구축합니다.

주요 소켓 함수

다음 단계로 넘어가기 전에, 사용할 주요 소켓 함수를 이해해 보겠습니다.

  • socket() - 새 소켓 객체를 생성합니다.
  • bind() - 소켓을 특정 네트워크 인터페이스 및 포트에 연결합니다.
  • listen() - 서버가 연결을 수락할 수 있도록 합니다.
  • accept() - 클라이언트로부터의 연결을 수락합니다.
  • connect() - 원격 주소에 연결합니다.
  • send() - 연결된 소켓으로 데이터를 보냅니다.
  • recv() - 연결된 소켓으로부터 데이터를 수신합니다.
  • close() - 소켓을 닫습니다.

다음 단계에서는 이러한 함수를 사용하여 서버를 생성합니다.

간단한 소켓 서버 생성

이제 소켓의 기본 사항을 이해했으므로, 연결을 수신하고 클라이언트로부터 메시지를 수신하는 간단한 서버를 만들어 보겠습니다.

소켓 서버의 작동 방식

소켓 서버는 다음과 같은 일반적인 단계를 따릅니다.

  1. 소켓 생성
  2. 주소 및 포트에 바인딩
  3. 들어오는 연결 대기
  4. 클라이언트 연결 수락
  5. 데이터 수신 및 처리
  6. 필요한 경우 응답 전송
  7. 연결 종료

Python 에서 이 패턴을 구현해 보겠습니다.

서버 생성

  1. socket_lab 디렉토리에 server.py라는 새 Python 파일을 생성합니다.

WebIDE 에서 "New File" 버튼을 클릭하거나 "File" 메뉴를 사용하고 "New File"을 선택한 다음, socket_lab 디렉토리 내에서 server.py로 이름을 지정합니다.

  1. 기본 서버를 생성하기 위해 다음 코드를 추가합니다.
import socket

def start_server():
    ## Server configuration
    host = '127.0.0.1'  ## localhost
    port = 12345        ## arbitrary non-privileged port

    ## Create socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    ## Set socket option to reuse address (helps avoid "Address already in use" errors)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    ## Bind socket to address and port
    server_socket.bind((host, port))

    ## Listen for connections (queue up to 5 connection requests)
    server_socket.listen(5)

    print(f"Server started on {host}:{port}")
    print("Waiting for client connection...")

    try:
        ## Accept connection
        client_socket, client_address = server_socket.accept()
        print(f"Connection established with {client_address}")

        ## Receive data from client
        data = client_socket.recv(1024)  ## receive up to 1024 bytes
        print(f"Message received: {data.decode()}")

        ## Send response to client
        response = "Message received by server"
        client_socket.send(response.encode())

        ## Close client connection
        client_socket.close()

    except KeyboardInterrupt:
        print("\nServer shutting down...")
    finally:
        ## Close server socket
        server_socket.close()
        print("Server socket closed")

if __name__ == "__main__":
    start_server()
  1. 파일을 저장합니다.

서버 코드 이해

서버의 주요 구성 요소를 자세히 살펴보겠습니다.

  • 소켓 생성: AF_INET (IPv4) 및 SOCK_STREAM (TCP 프로토콜) 을 사용하여 socket.socket()으로 TCP 소켓을 생성합니다.

  • 소켓 옵션: 주소를 재사용하도록 옵션을 설정합니다. 이는 서버를 종료한 후 빠르게 다시 시작할 때 "Address already in use" 오류를 방지하는 데 도움이 됩니다.

  • 바인딩: 소켓을 주소 127.0.0.1 (localhost) 및 포트 12345에 바인딩합니다. 이는 운영 체제에 이 특정 네트워크 위치에서 연결을 수신하려는 것을 알립니다.

  • 리스닝: listen(5) 호출은 소켓이 새 연결을 거부하기 전에 최대 5 개의 연결 요청을 큐에 넣도록 지시합니다.

  • 연결 수락: accept() 메서드는 클라이언트가 연결될 때까지 블록 (대기) 한 다음, 해당 클라이언트와 통신하기 위한 새 소켓 객체와 클라이언트의 주소를 반환합니다.

  • 데이터 수신: recv(1024)를 사용하여 클라이언트로부터 최대 1024 바이트의 데이터를 수신합니다. 데이터는 바이트로 제공되므로 decode()를 사용하여 문자열로 변환합니다.

  • 데이터 전송: send() 메서드를 사용하여 클라이언트에 응답을 다시 보냅니다. 전송하기 전에 문자열을 encode()하여 바이트로 변환합니다.

  • 종료: 마지막으로, 리소스를 해제하기 위해 클라이언트 소켓과 서버 소켓을 모두 닫습니다.

서버 테스트

서버를 실행하여 실제로 작동하는지 확인해 보겠습니다. 아직 클라이언트를 생성하지 않았으므로 많은 작업을 수행하지 않지만, 올바르게 시작되는지 확인할 수 있습니다.

python3 ~/project/socket_lab/server.py

다음과 같은 출력을 볼 수 있습니다.

Server started on 127.0.0.1:12345
Waiting for client connection...

이제 서버는 클라이언트가 연결되기를 기다리고 있습니다. 아직 클라이언트가 없으므로 Ctrl+C 를 눌러 서버를 중지합니다.

^C
Server shutting down...
Server socket closed

다음 단계에서는 서버에 연결할 클라이언트를 생성합니다.

서버에 연결하는 클라이언트 구축

이제 서버가 있으므로, 서버에 연결하여 메시지를 보낼 수 있는 클라이언트를 생성해야 합니다. 간단한 클라이언트 애플리케이션을 구축해 보겠습니다.

소켓 클라이언트의 작동 방식

소켓 클라이언트는 다음과 같은 일반적인 단계를 따릅니다.

  1. 소켓 생성
  2. 서버의 주소 및 포트에 연결
  3. 데이터 전송
  4. 응답 수신
  5. 연결 종료

클라이언트 생성

  1. socket_lab 디렉토리에 client.py라는 새 Python 파일을 생성합니다.

WebIDE 에서 "New File" 버튼을 클릭하거나 "File" 메뉴를 사용하고 "New File"을 선택한 다음, socket_lab 디렉토리 내에서 client.py로 이름을 지정합니다.

  1. 기본 클라이언트를 생성하기 위해 다음 코드를 추가합니다.
import socket

def start_client():
    ## Server information to connect to
    host = '127.0.0.1'  ## localhost - same as server
    port = 12345        ## same port as server

    try:
        ## Create socket
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        ## Connect to server
        print(f"Connecting to server at {host}:{port}...")
        client_socket.connect((host, port))
        print("Connected to server")

        ## Send a message
        message = "Hello from the client!"
        print(f"Sending message: {message}")
        client_socket.send(message.encode())

        ## Receive response
        response = client_socket.recv(1024)
        print(f"Response from server: {response.decode()}")

    except ConnectionRefusedError:
        print("Connection failed. Make sure the server is running.")
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        ## Close socket
        client_socket.close()
        print("Connection closed")

if __name__ == "__main__":
    start_client()
  1. 파일을 저장합니다.

클라이언트 코드 이해

클라이언트 코드의 주요 부분을 살펴보겠습니다.

  • 소켓 생성: 서버와 마찬가지로 socket.socket()을 사용하여 TCP 소켓을 생성합니다.

  • 연결: 바인딩 및 리스닝 대신, 클라이언트는 connect()를 사용하여 지정된 호스트 및 포트에서 서버에 연결을 설정합니다.

  • 데이터 전송: send() 메서드를 사용하여 메시지를 보내고, 문자열을 바이트로 인코딩해야 합니다.

  • 데이터 수신: recv(1024)를 사용하여 서버의 응답을 수신하고, 다시 문자열로 디코딩합니다.

  • 오류 처리: 서버를 사용할 수 없는 경우 (ConnectionRefusedError) 와 같은 일반적인 문제를 catch 하기 위해 오류 처리를 포함합니다.

  • 종료: 리소스를 해제하기 위해 완료되면 소켓을 닫습니다.

클라이언트와 서버 함께 테스트

이제 클라이언트와 서버를 함께 테스트해 보겠습니다. 별도의 터미널 창에서 실행해야 합니다.

  1. 먼저, 서버를 시작합니다.
python3 ~/project/socket_lab/server.py

다음과 같은 출력을 볼 수 있습니다.

Server started on 127.0.0.1:12345
Waiting for client connection...
  1. WebIDE 에서 새 터미널을 열고 (터미널 패널에서 "+" 버튼을 클릭하여) 클라이언트를 실행합니다.
python3 ~/project/socket_lab/client.py

클라이언트 터미널에서 다음과 같은 출력을 볼 수 있습니다.

Connecting to server at 127.0.0.1:12345...
Connected to server
Sending message: Hello from the client!
Response from server: Message received by server
Connection closed

그리고 서버 터미널에서 다음을 볼 수 있습니다.

Connection established with ('127.0.0.1', 55234)  ## The port number may differ
Message received: Hello from the client!

클라이언트가 연결을 끊은 후, 현재 구현은 단일 연결만 처리하므로 서버가 중지됩니다. 서버 터미널에서 Ctrl+C 를 눌러 서버가 계속 실행 중인 경우 종료합니다.

이것은 Python 소켓을 사용하여 클라이언트와 서버 간의 성공적인 통신을 보여줍니다. 클라이언트는 서버에 메시지를 보낼 수 있으며, 서버는 이를 수신하고 응답을 다시 보낼 수 있습니다.

지속적인 서버 및 대화형 클라이언트 구축

현재 서버 - 클라이언트 구현은 종료되기 전에 단일 메시지 교환만 처리합니다. 대부분의 실제 애플리케이션은 연결을 유지하고 여러 메시지를 처리해야 합니다. 보다 대화형 환경을 만들기 위해 코드를 개선해 보겠습니다.

서버 개선

먼저, 서버를 수정하여 연결을 지속적으로 수락하고 각 클라이언트로부터 여러 메시지를 처리하도록 하겠습니다.

  1. WebIDE 에서 server.py 파일을 열고 코드를 다음으로 바꿉니다.
import socket

def start_server():
    ## Server configuration
    host = '127.0.0.1'
    port = 12345

    ## Create socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        ## Bind and listen
        server_socket.bind((host, port))
        server_socket.listen(5)

        print(f"Server running on {host}:{port}")
        print("Press Ctrl+C to stop the server")

        while True:  ## Continuous server loop
            print("\nWaiting for a connection...")
            client_socket, client_address = server_socket.accept()
            print(f"Connected to client: {client_address}")

            ## Handle client communication
            handle_client(client_socket)

    except KeyboardInterrupt:
        print("\nServer is shutting down...")
    finally:
        server_socket.close()
        print("Server closed")

def handle_client(client_socket):
    try:
        while True:  ## Keep receiving messages until client disconnects
            ## Receive data
            data = client_socket.recv(1024)

            ## If no data, client has disconnected
            if not data:
                break

            received_message = data.decode()
            print(f"Received: {received_message}")

            ## Process the message (in this case, just echo it back with a prefix)
            response = f"Server received: {received_message}"
            client_socket.send(response.encode())

    except Exception as e:
        print(f"Error handling client: {e}")
    finally:
        ## Close client socket
        client_socket.close()
        print("Client connection closed")

if __name__ == "__main__":
    start_server()
  1. 파일을 저장합니다.

클라이언트 개선

이제 사용자가 여러 메시지를 보낼 수 있는 대화형 클라이언트를 만들어 보겠습니다.

  1. WebIDE 에서 client.py 파일을 열고 코드를 다음으로 바꿉니다.
import socket

def start_client():
    ## Server information
    host = '127.0.0.1'
    port = 12345

    try:
        ## Create socket and connect
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        print(f"Connecting to server at {host}:{port}...")
        client_socket.connect((host, port))
        print("Connected to server!")

        ## Interactive message sending
        while True:
            ## Get message from user
            message = input("\nEnter message to send (or 'quit' to exit): ")

            ## Check if user wants to quit
            if message.lower() == 'quit':
                print("Closing connection...")
                break

            ## Send message
            client_socket.send(message.encode())

            ## Receive response
            response = client_socket.recv(1024)
            print(f"Response from server: {response.decode()}")

    except ConnectionRefusedError:
        print("Connection failed. Make sure the server is running.")
    except Exception as e:
        print(f"An error occurred: {e}")
    finally:
        ## Close connection
        try:
            client_socket.close()
        except:
            pass
        print("Disconnected from server")

if __name__ == "__main__":
    start_client()
  1. 파일을 저장합니다.

개선된 코드 이해

서버 개선 사항:

  • 새로운 클라이언트 연결을 지속적으로 수락하기 위해 외부 while True 루프를 추가했습니다.
  • 각 클라이언트와의 통신을 관리하기 위해 별도의 handle_client 함수를 만들었습니다.
  • 클라이언트 처리 함수에는 동일한 클라이언트로부터 여러 메시지를 수신하기 위한 자체 루프가 있습니다.
  • 클라이언트가 연결을 끊었음을 나타내는 빈 데이터 (if not data:) 를 확인합니다.

클라이언트 개선 사항:

  • 여러 메시지를 보낼 수 있도록 while True 루프를 추가했습니다.
  • 사용자에게 입력을 요청하고 서버로 보냅니다.
  • 사용자는 'quit'을 입력하여 루프를 종료하고 연결을 닫을 수 있습니다.
  • 각 메시지를 보낸 후 서버의 응답을 기다리고 표시합니다.

개선된 애플리케이션 테스트

개선된 서버와 클라이언트를 테스트해 보겠습니다.

  1. 터미널에서 개선된 서버를 시작합니다.
python3 ~/project/socket_lab/server.py

다음과 같은 출력을 볼 수 있습니다.

Server running on 127.0.0.1:12345
Press Ctrl+C to stop the server

Waiting for a connection...
  1. 새 터미널에서 개선된 클라이언트를 실행합니다.
python3 ~/project/socket_lab/client.py

다음과 같은 출력을 볼 수 있습니다.

Connecting to server at 127.0.0.1:12345...
Connected to server!

Enter message to send (or 'quit' to exit):
  1. 메시지를 입력하고 Enter 키를 누릅니다.
Enter message to send (or 'quit' to exit): Hello, server!
Response from server: Server received: Hello, server!

Enter message to send (or 'quit' to exit):
  1. 몇 개의 메시지를 더 보내보세요. 서버 터미널에서 각 메시지가 수신되는 것을 볼 수 있습니다.
Connected to client: ('127.0.0.1', 59042)
Received: Hello, server!
Received: This is another message
  1. 완료되면 클라이언트에서 'quit'을 입력합니다.
Enter message to send (or 'quit' to exit): quit
Closing connection...
Disconnected from server
  1. 서버 터미널에서 다음을 볼 수 있습니다.
Client connection closed

Waiting for a connection...
  1. 서버는 계속 실행 중이며 새 연결을 수락할 준비가 되었습니다. 다른 클라이언트를 시작하거나 Ctrl+C 를 눌러 서버를 중지할 수 있습니다.

이러한 개선 사항을 통해 Python 소켓을 사용하여 보다 현실적이고 대화형 클라이언트 - 서버 통신 시스템을 만들었습니다.

다중 클라이언트 및 오류 처리

실제 애플리케이션에서 서버는 일반적으로 여러 클라이언트를 동시에 처리하고 다양한 오류 조건을 적절하게 관리해야 합니다. 이러한 점을 염두에 두고 구현을 개선해 보겠습니다.

동시 클라이언트 처리 이해

여러 클라이언트를 동시적으로 처리하는 방법에는 여러 가지가 있습니다.

  1. 스레딩 (Threading): 각 클라이언트 연결에 대해 새 스레드를 생성합니다.
  2. 프로세스 기반 (Process-based): 각 클라이언트에 대해 새 프로세스를 생성합니다.
  3. 비동기 I/O (Asynchronous I/O): 이벤트 루프와 함께 논블로킹 I/O 를 사용합니다.

이 랩에서는 이해하고 구현하기 비교적 쉬운 스레딩 기반 접근 방식을 구현합니다.

다중 클라이언트를 위한 서버 개선

스레드를 사용하여 여러 클라이언트를 처리하도록 서버를 수정해 보겠습니다.

  1. WebIDE 에서 server.py 파일을 열고 코드를 다음으로 바꿉니다.
import socket
import threading

def handle_client(client_socket, client_address):
    """Handle communication with a single client"""
    try:
        print(f"[NEW CONNECTION] {client_address} connected.")

        while True:
            ## Receive client data
            try:
                data = client_socket.recv(1024)
                if not data:
                    break  ## Client disconnected

                message = data.decode()
                print(f"[{client_address}] {message}")

                ## Send response
                response = f"Message '{message}' received successfully"
                client_socket.send(response.encode())

            except ConnectionResetError:
                print(f"[{client_address}] Connection reset by client")
                break

    except Exception as e:
        print(f"[ERROR] {e}")

    finally:
        ## Clean up when client disconnects
        client_socket.close()
        print(f"[DISCONNECTED] {client_address} disconnected")

def start_server():
    """Start the server and listen for connections"""
    ## Server configuration
    host = '127.0.0.1'
    port = 12345

    ## Create socket
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    ## Set socket option to reuse address
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        ## Bind to host and port
        server_socket.bind((host, port))

        ## Listen for connections
        server_socket.listen(5)
        print(f"[STARTING] Server is listening on {host}:{port}")

        while True:
            ## Accept client connection
            client_socket, client_address = server_socket.accept()

            ## Create a new thread to handle the client
            client_thread = threading.Thread(
                target=handle_client,
                args=(client_socket, client_address)
            )
            client_thread.daemon = True  ## Thread will close when main program exits
            client_thread.start()

            ## Display active connections
            print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")

    except KeyboardInterrupt:
        print("\n[SHUTTING DOWN] Server is shutting down...")
    except Exception as e:
        print(f"[ERROR] {e}")
    finally:
        server_socket.close()
        print("[CLOSED] Server socket closed")

if __name__ == "__main__":
    start_server()
  1. 파일을 저장합니다.

오류 처리를 통한 클라이언트 개선

또한 더 나은 오류 처리를 통해 클라이언트를 개선해 보겠습니다.

  1. WebIDE 에서 client.py 파일을 열고 코드를 다음으로 바꿉니다.
import socket
import sys
import time

def start_client():
    """Start a client that connects to the server"""
    ## Server information
    host = '127.0.0.1'
    port = 12345

    ## Create socket
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    ## Set a timeout for connection attempts (5 seconds)
    client_socket.settimeout(5)

    try:
        ## Connect to server
        print(f"[CONNECTING] Connecting to server at {host}:{port}...")
        client_socket.connect((host, port))

        ## Reset timeout to none for regular communication
        client_socket.settimeout(None)

        print("[CONNECTED] Connected to server")

        ## Communication loop
        while True:
            ## Get user input
            message = input("\nEnter message (or 'quit' to exit): ")

            if message.lower() == 'quit':
                print("[CLOSING] Closing connection by request...")
                break

            try:
                ## Send message
                client_socket.send(message.encode())

                ## Wait for response
                response = client_socket.recv(1024)
                print(f"[RESPONSE] {response.decode()}")

            except ConnectionResetError:
                print("[ERROR] Connection was reset by the server")
                break
            except ConnectionAbortedError:
                print("[ERROR] Connection was aborted")
                break
            except Exception as e:
                print(f"[ERROR] {e}")
                break

    except socket.timeout:
        print("[TIMEOUT] Connection attempt timed out. Is the server running?")
    except ConnectionRefusedError:
        print("[REFUSED] Connection refused. Make sure the server is running.")
    except KeyboardInterrupt:
        print("\n[INTERRUPT] Client shutting down...")
    except Exception as e:
        print(f"[ERROR] {e}")

    finally:
        ## Close socket
        try:
            client_socket.close()
            print("[DISCONNECTED] Disconnected from server")
        except:
            pass

if __name__ == "__main__":
    start_client()
  1. 파일을 저장합니다.

개선된 코드 이해

서버 개선 사항:

  • 여러 클라이언트를 동시적으로 처리하기 위해 threading 모듈을 추가했습니다.
  • 각 클라이언트 연결은 이제 별도의 스레드에서 처리됩니다.
  • 보다 구체적인 예외 처리를 통해 오류 처리를 개선했습니다.
  • 활성 클라이언트 연결 수를 표시합니다.
  • 스레드는 "데몬 (daemon)"으로 설정되어 주 프로그램이 종료되면 자동으로 닫힙니다.

클라이언트 개선 사항:

  • 서버를 사용할 수 없는 경우 멈추는 것을 방지하기 위해 연결 시간 초과를 추가했습니다.
  • 다양한 네트워크 오류에 대한 특정 예외 처리를 통해 오류 처리를 개선했습니다.
  • 더 명확한 서식으로 더 설명적인 상태 메시지를 추가했습니다.

다중 클라이언트 서버 테스트

개선된 애플리케이션을 테스트해 보겠습니다.

  1. 서버를 시작합니다.
python3 ~/project/socket_lab/server.py

다음과 같은 출력을 볼 수 있습니다.

[STARTING] Server is listening on 127.0.0.1:12345
  1. 새 터미널에서 클라이언트를 시작합니다.
python3 ~/project/socket_lab/client.py
  1. 세 번째 터미널에서 다른 클라이언트를 시작합니다.
python3 ~/project/socket_lab/client.py
  1. 서버 터미널에서 두 연결을 모두 볼 수 있습니다.
[NEW CONNECTION] ('127.0.0.1', 59124) connected.
[ACTIVE CONNECTIONS] 1
[NEW CONNECTION] ('127.0.0.1', 59126) connected.
[ACTIVE CONNECTIONS] 2
  1. 두 클라이언트에서 메시지를 보내고 서버가 이를 수신하는 것을 관찰합니다.
[('127.0.0.1', 59124)] Hello from client 1
[('127.0.0.1', 59126)] Hello from client 2
  1. 완료되면 각 클라이언트에서 'quit'을 입력하여 연결을 끊거나 서버 터미널에서 Ctrl+C 를 눌러 서버를 종료합니다.

서버가 실행되지 않는 경우 처리

서버가 실행되지 않는 경우 어떻게 되는지 테스트해 보겠습니다.

  1. 서버가 중지되었는지 확인합니다 (실행 중인 경우 Ctrl+C 를 누릅니다).

  2. 클라이언트를 실행해 봅니다.

python3 ~/project/socket_lab/client.py

다음과 같은 출력을 볼 수 있습니다.

[CONNECTING] Connecting to server at 127.0.0.1:12345...
[REFUSED] Connection refused. Make sure the server is running.
[DISCONNECTED] Disconnected from server

이제 클라이언트는 오류를 적절하게 처리하여 사용자에게 서버가 실행되지 않을 수 있음을 알립니다.

이러한 개선 사항을 통해 여러 클라이언트와 다양한 오류 조건을 처리할 수 있는 강력한 클라이언트 - 서버 시스템을 만들었습니다. 이는 더 복잡한 네트워크 애플리케이션을 개발하기 위한 견고한 기반입니다.

요약

Python 소켓 프로그래밍에 대한 이 랩을 완료하신 것을 축하드립니다. 다음을 성공적으로 배웠습니다.

  1. 네트워크 통신을 위한 소켓 객체 생성
  2. 연결을 수신 대기하는 TCP 서버 구현
  3. 서버에 연결하는 클라이언트 애플리케이션 구축
  4. 클라이언트와 서버 간에 메시지 송수신
  5. 여러 클라이언트를 동시적으로 처리하도록 서버 개선
  6. 클라이언트와 서버 모두에서 강력한 오류 처리 구현

이러한 기술은 네트워크 프로그래밍의 기초를 형성하며, 간단한 채팅 프로그램에서 복잡한 분산 시스템에 이르기까지 광범위한 네트워크 애플리케이션을 구축하는 데 적용할 수 있습니다.

학습 여정을 계속하려면 다음을 고려해 보세요.

  • SSL/TLS를 사용한 보안 소켓 연결 구현
  • 구조화된 데이터를 교환하기 위한 더 복잡한 프로토콜 구축
  • 클라이언트 애플리케이션을 위한 GUI 생성
  • 많은 동시 연결에서 더 높은 성능을 위해 비동기 I/O 사용

Python 의 소켓 프로그래밍 기능은 네트워크 애플리케이션 개발에 매우 적합하며, 다른 언어에서는 찾아보기 힘든 단순성과 강력함의 균형을 제공합니다.