호우동의 개발일지

Today :

article thumbnail

클라이언트-서버 애플리케이션 개발

  • 애플리케이션에 TCP, UDP 중 어떤 것을 사용할지 결정해야 함
    • TCP : 연결지향적 서비스
      • 신뢰적 바이트 스트림 채널 제공
        • 해당 채널을 통해 데이터가 두 종단 시스템 사이를 흐름

    • UDP : 비연결형 서비스
      • 한 종단 시스템에서 다른 곳으로 데이터를 독립적인 패킷으로 만들어 전송
      • 전송에 대한 보장을 하지 않는다.

 


RFC 표준 프로토콜 vs 개인 독점 프로토콜

  • RFC 표준 프로토콜
    • 오늘날 대부분의 네트워크 애플리케이션
    • 독립 개발자가 개발한 클라이언트와 서버 프로그램 간의 통신을 포함
    • RFC에 정의된 프로토콜을 구현할 때,
      해당 프로토콜과 잘 알려진 포트 번호를 사용

  • 개인 독점 프로토콜
    • 공개된 프로토콜을 구현하지 않음
      → 다른 독립 개발자는 이 애플리케이션과 상호작용하는 코드 개발 불가능

    • 애플리케이션을 개발할 때 잘 알려진 포트 번호를 사용하지 않도록 해야 함

 

 


TCP 소켓 프로그래밍

  • 클라이언트와 서버가 데이터를 보내기 전에 먼저 TCP 연결 설정 필요
    • TCP 연결의 한쪽은 클라이언트 소켓, 다른 쪽은 서버 소켓과 연결

  • TCP를 생성할 때 클라이언트 소켓 주소와 서버 소켓 주소를 연결과 연관시킨다.
    • 소켓 주소 = IP 주소 + 포트 번호
    • 데이터를 보낼 때 연결된 곳으로 보냄 → UDP 와의 차이점
      • UDP는 서버가 패킷을 소켓에 제공하기 전에 패킷에 목적지 주소를 붙여야 함

 


TCP 클라이언트 서버 상호작용

  1. 먼저 클라이언트는 서버로의 접속을 시도한다.
    → 서버는 클라이언트의 초기 접속에 응대할 수 있도록 준비해야 함
    • 여기에는 2가지 의미가 존재
      1. TCP 서버는 클라이언트가 접속하기 전에 프로세스를 먼저 수행하고 있어야 함
      2. 클라이언트로부터의 초기 접속을 처리하는 특별한 출입문(소켓)을 가져야 한다.

  2. 접속에 대한 시도로 클라이언트 프로그램에서 TCP 소켓을 생성한다.
    • TCP 소켓을 생성할 때, 서버에 있는 환영 주소를 명시한다.
      • 환영(welcome) 주소 : 서버의 IP 주소와 소켓의 포트 번호

  3. 소켓을 생성한 후, 클라이언트는 세 방향 핸드셰이크를 하고 서버와 TCP 연결을 설정
    • 세 방향 핸드셰이크는 트랜스포트 계층에서 일어남
      → 클라이언트와 서버 프로그램은 전혀 인식하지 못함

  4. 세 방향 핸드셰이크 동안, 클라이언트 프로세스는 서버 프로세스의 출입문을 두드림
  5. 서버가 노크를 들으면, 서버는 해당 클라이언트에게 지정된 새로운 소켓을 생성
    • 연결을 시도하는 클라이언트에게 새로 지정된 소켓 → connectionSocket

 


클라이언트 소켓과 서버의 연결

TCP 통신에서 서버는 2개의 소켓을 가진다.
TCP 통신에서는 서버 프로세스가 2개의 소켓을 사용

  • 애플리케이션 관점에서, 클라이언트 소켓과 서버 연결 소켓은 파이프에 의해 직접 연결됨

  • 클라이언트 프로세스는 자신의 소켓으로 임의의 바이트를 보낼 수 있고,
    보낸 순서대로 서버 프로세스가 바이트를 수신하도록 TCP가 보장한다.
    • 이 과정은 연결 소켓을 통해 일어난다.
    • → 따라서 TCP는 클라이언트와 서버 프로세스 간에 신뢰적 서비스를 제공한다.

  • 또한, 클라이언트 프로세스는 소켓으로부터 바이트를 수신할 수 있다.
  • 서버 프로세스도 소켓으로 바이트를 보낼 수도 있다.

 

 


TCP 프로그램 예시


TCP 전송 서비스 통신에서 클라이언트-서버 주요 소켓 동작

TCP 통신 과정
TCP 통신 과정

 


프로그램 코드

TCPClient.py

  • 애플리케이션 클라이언트 쪽 코드
from socket import *
serverName = 'servername'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect((serverName,serverPort))
sentence = input('Input lowercase sentence:')
clientSocket.send(sentence.encode())
modifiedSentence = clientSocket.recv(2048)
clientSocket.close()
from socket import *
  • socket 모듈을 가져옴 → 프로그램 내에 소켓 생성 가능

serverName = 'servername'
serverPort = 12000 # 서버 포트 할당
  • 호스트 이름은 서버의 IP 주소 혹은 서버 이름을 포함하는 문자열을 제공해야 함
  • 호스트 이름을 사용하는 경우 IP 주소를 얻기 위해 DNS 검색이 자동으로 수행

# 클라이언트 소켓 생성
clientSocket = socket(AF_INET, SOCK_STREAM)
  • AF_INET : 하위 네트워크가 IPv4를 사용함
  • SOCK_STREAM : TCP 소켓임을 의미
  • 클라이언트 소켓을 생성할 때 해당 소켓의 포트 번호를 명시하지 않는다.
    • 운영체제가 하도록 내버려 둔다.

# 클라이언트와 서버 간에 TCP 연결 시작
clientSocket.connect((serverName,serverPort))
  • 클라이언트가 TCP 소켓을 이용하여 서버로 데이터를 보내기 전에 TCP 연결 설정 필요
  • (serverName, serverPort) → 연결의 서버 쪽 주소
  • 이후 세 방향 핸드셰이크가 수행되고, 클라이언트와 서버 간에 TCP 연결이 설정됨

# 사용자로부터 문장을 획득
sentence = input('Input lowercase sentence:')
  • 사용자가 리턴 키를 입력하여 문장을 마칠 때까지 문자를 계속해서 sentence에 모은다.

# 문자열 sentence를 클라이언트 소켓을 통해 TCP 연결로 보냄
clientSocket.send(sentence.encode())
  • 클라이언트 프로그램은 단순히 sentence에 있는 바이트를 TCP 연결에게 제공
    • cf ) UDP - 패킷을 생성하고 패킷에 목적지 주소를 붙인다.
      → TCP 방식에서는 전송할 때 패킷을 생성하지 않는다.

  • 이후, 클라이언트는 서버로부터 바이트를 수신하기를 기다린다.
# 서버로부터 온 문자를 문자열에 모은다.
modifiedSentence = clientSocket.recv(2048)

 

  • 라인이 리턴 키로 끝날 때까지 modifiedSentence에 문자가 계속해서 쌓임
# 소켓을 닫고 클라이언트와 서버 간의 TCP 연결을 닫는다.
clientSocket.close()
  • 이는 클라이언트 TCP가 서버 TCP에게 TCP 메시지를 보내게 한다.

TCPServer.py

from socket import * 
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)
print('The server is ready to receive')
while True:
        connectionSocket, addr = serverSocket.accept()
        sentence = connectionSocket.recv(1024).decode()
        capitalizedSentence = sentence.upper()
        connectionSocket.send(capitalizedSentence.encode())
        connectionSocket.close()
from socket import * 
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
  • TCPClient.py 와 같음 → 12000번 포트를 가진 TCP 소켓을 만든다.

serverSocket.bind(('', serverPort))
  • 서버 포트 번호 serverPort를 소켓과 연관시킨다.
    • TCP에서는 serverSocket(welcomeSocket)이 대기하는 소켓이 된다.
      → 출입문을 설정한 후, 임의의 클라이언트가 이 문을 두드리기를 기다린다.

serverSocket.listen(1)
  • 서버가 클라이언트로부터 TCP 연결 요청을 듣도록 한다.
  • 파라미터는 큐잉 연결의 최대 수(최소 1)

# 요청한 클라이언트에게 새로운 지정된 소켓을 서버에 생성
connectionSocket, addr = serverSocket.accept()
  • 클라이언트가 문을 두드리면 프로그램은 serverSocket을 위한 accept() 메서드 실행
    → 클라이언트에게 지정된 connectionSocket이라는 새로운 소켓 서버 생성

  • 이후, 클라이언트와 서버는 핸드셰이킹을 완료
    → 클라이언트 소켓과 서버 소켓 간의 TCP 연결 생성
    • clientSocket ↔ connectionSocket 간의 TCP 연결

  • TCP 연결이 설정되었으므로, 클라이언트와 서버는 서로에게 바이트를 보낼 수 있다.
# 모든 작업 이후, 연결 소켓을 닫는다.
connectionSocket.close()
  • 연결 소켓을 닫아도, serverSocket이 열려있기 때문에 다른 클라이언트가 두드릴 수 있음