티스토리 뷰

다음의 간단한 클라이언트-서버 애플리케이션을 UDP와 TCP로 구현해보자.

 

  1. 클라이언트는 키보드로부터 한 줄의 문자(데이터)를 읽고 그 데이터를 서버로 보낸다.
  2. 서버는 그 데이터를 수신하고 문자를 대문자로 변환한다.
  3. 서버는 수정된 데이터를 클라이언트에게 보낸다.
  4. 클라이언트는 수정된 데이터를 수니하고 그 줄을 화면에 나타낸다.

 

2.7.1 UDP를 이용한 소켓 프로그래밍

  1. 송신 프로세스가 데이터의 패킷을 소켓 문 밖으로 밀어내기 전에, UDP를 사용할 때 먼저 패킷에 목적지 주소를 붙여 넣어야 한다.
  2. 이 패킷이 송신자의 소켓을 통과한 후 인터넷은 이 목적지 주소를 이용하여 그 패킷을 인터넷을 통해 수신 프로세스에 있는 소켓으로 라우트할 것이다.
  3. 패킷이 수신 소켓에 도착하면 수신 프로세스는 소켓을 통해 그 패킷을 추출하고 다음에 패킷의 콘텐츠를 조사하고 적절한 동작을 취한다.

 

UDP를 이용한 클라이언트-서버 애플리케이션

 

UDPClient와 UDPServer 파이썬으로 구현

// UDPClient.py
// socket 모듈은 파이썬에서 모든 네트워크 통신의 기본을 구성하므로
// 이를 통해 프로그램 내에 소켓을 생성할 수 있다.
from socket import *
serverName = ’hostname’
serverPort = 12000
// 클라이언트 소켓을 생성(IPv4, UDP 소켓)
clientSocket = socket(AF_INET, SOCK_DGRAM)
// 터미널 창에 키보드를 통해 입력하여 메시지를 생성
message = raw_input(’Input lowercase sentence:’)
// 패킷을 프로세스의 소켓으로 보냄(문자열->바이트, 목적지 주소)
clientSocket.sendto(message.encode(),(serverName, serverPort))
// 클라이언트는 패킷을 보낸 후에 서버로부터의 데이터 수신을 기다린다.
// modifiedMessage에 패킷 데이터가 할당되고 
// serverAddress에 패킷의 소스 주소가 할당된다.
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
// 메시지를 바이트에서 문자열로 변환하여 출력한다.
print(modifiedMessage.decode())
// 소켓을 닫으며 그 후에 프로세스가 종료된다.
clientSocket.close()

 

// UDPServer.py
from socket import *
// 포트 번호를 서버의 소켓에 할당
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind((’’, serverPort))
print(”The server is ready to receive”)
while True:
 // 패킷이 서버의 소켓에 도착하면, 
 // 패킷의 데이터는 변수 message에 할당되고 
 // 패킷의 소스 주소는 변수 clientAddress에 할당된다.(반송 주소로 사용됨.)
 message, clientAddress = serverSocket.recvfrom(2048)
 // 메소드 upper()를 이용하여 대문자로 변환한다.
 modifiedMessage = message.decode().upper()
 //클라이언트의 주소를 대문자로 변환된 메시지에 붙이고 만들어진 패킷을 서버의 소켓으로 보낸다.
 // 그 뒤 인터넷은 패킷을 클라이언트 주소로 전달한다.
 serverSocket.sendto(modifiedMessage.encode(), clientAddress)
 
// 서버는 패킷을 보낸 후 while 루프에 머무르며 다른 UDP 패킷이 도착하기를 기다린다.

 

 




2.7.2 TCP 소켓 프로그래밍

UDP와 다르게 데이터를 보내기 전에 먼저 TCP 연결을 생성한다.
TCP 연결이 설정된 후, 한쪽에서 다른 쪽으로 데이터를 보내려면 소켓을 통해 데이터를 TCP 연결로 보내면 된다.

 

 

  • 서버 프로세스가 실행되면, 클라이언트 프로세스는 서버로 TCP 연결을 시도한다.
    • 이러한 초기 접속을 "출입문을 두드리는 것"으로 표현한다.
    • 클라이언트 프로세스에서 TCP 소켓을 생성함으로써 가능하다.

  • 클라이언트는 TCP 소켓을 생성할 때
    • 서버에 있는 환영 소켓의 주소, 즉 서버의 IP 주소와 소켓의 포트 번호를 명시한다.
    • 소켓을 생성한 후 서버에게 3-way 핸드세이크를 시도한다. 이상이 없다면, 서버는 이 클라이언트에게 지정된 연결 소켓을 생성하다.  

✖️ 3-way 핸드세이크는 트랜스포트 계층에서 일어나서 클라이언트와 서버 프로그램은 전혀 인식하지 못한다.

TCPServer 프로세스는 2개의 소켓을 가진다

 

TCP를 이용한 클라이언트-서버 애플리케이션

 

TCPClient와 TCPServer 파이썬으로 구현

// TCPClient.py
from socket import *
serverName = ’servername’
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM)
// 데이터를 보내기 전에 TCP 연결이 먼저 클라이언트와 서버 사이에 설정되어야 한다.
// 클라이언트와 서버 간에 TCP 연결이 설정된다.
clientSocket.connect((serverName, serverPort))
// 사용자로부터 문장을 획득한다.
sentence = raw_input(’Input lowercase sentence:’)
// 문자열 sentence를 클라이언트 소켓을 통해 TCP 연결로 보낸다.
clientSocket.send(sentence.encode())
// 서버로부터 온 문자를 문자열 modifiedSentence에 모은다.
modifiedSentence = clientSocket.recv(1024)
print(’From Server: ’, modifiedSentence.decode())
// 소켓을 닫고 클라이언트와 서버 간의 TCP 연결을 닫는다.
clientSocket.close()

 

// TCPServer.py
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
// 서버 포트 번호 serverPort를 소켓과 연관시킨다.
// TCP에서는 serverSocket이 대기하는 소켓이 된다.
serverSocket.bind((’’, serverPort))
// TCP 연결 요청을 듣도록 한다.
serverSocket.listen(1)
print(’The server is ready to receive’)
while True:
 // 클라이언트가 이 문을 두드리면 프로그램은 serverSocket을 위한 accept() 메소드를 시작해서
 // 이 클라이언트에게 지정된 connectionSocket이라는 새로운 소켓을 서버에 생성한다.
 // 그 뒤 클라이언트와 서버는 핸드셰이킹을 완료해서
 // clientSocket과 서버의 connectionSocket 간에 TCP 연결을 생성한다.
 connectionSocket, addr = serverSocket.accept()
 sentence = connectionSocket.recv(1024).decode()
 capitalizedSentence = sentence.upper()
  // TCP 연결이 설정되었으므로 클라이언트와 서버는 이제 이 연결을 통해
  // 서로에게 바이트를 보낼 수 있다.
 connectionSocket.send(capitalizedSentence.encode())
 // 클라이언트에게 수정된 문장을 보낸 후 연결 소켓을 닫는다.
 // 그러나 serverSocket이 열려 있기 때문에 다른 클라이언트가 출입문을 두드릴 수 있고
 // 서버에게 수정할 문장을 보낼 수 있다.
 connectionSocket.close()

 

  • TCP의 경우 한쪽에서 보낸 모든 바이트가 다른 쪽에 도착하는 것을 보장할 뿐만 아니라 순서대로 도착하는 것을 보장한다.