Chapter 2. socket programming
socket programming
목표: 소켓을 사용해서 클라이언트/서버 애플리케이션을 만드는 방법을 배우는 것
socket(소켓)이란?
소켓은 애플리케이션 프로세스와 end-to-end 전송 프로토콜 사이의 문 역할을 한다.
즉, 네트워크를 통해 데이터를 보내고 받는 프로그래밍 인터페이스(API)라고 생각하면 된다.
소켓은 사용하는 전송 서비스에 따라 두 가지로 나뉜다.
1. UDP
unreliable datagram
빠르지만, 순서 보장이 없고 데이터 유실 가능성이 있다.
ex) 실시간 영상 스트리밍
| datagram 한 덩어리의 데이터를 한 번에 보내는 방식 메시지 단위로 보내기 때문에 패킷 하나하나가 독립적이다. 순서도 없고, 전달 여부도 확인하지 않는다. ex) 편지 봉투 여러 개를 우체통에 넣는 느낌 |
UDP는 메시지 단위로 끊겨 있기 때문에 송신 측이 10바이트를 보냈으면, 수신 측도 한 번에 10바이트를 받아야 한다. 즉, 메시지가 경계 단위로 유지돼서 나눠 받거나 합쳐 받기가 어렵다.
2. TCP
reliable, byte stream-oriented
데이터의 순서를 보장해주고 손실 시 재전송해준다.
ex) 파일 전송, 웹사이트 요청 등
| stream 연결된 흐름처럼 연속된 데이터를 보내는 방식 데이터를 한 줄의 바이트 흐름(byte stream)으로 다룬다. 전달 순서를 보장하고, 손실되면 재전송해준다. ex) 파이프처럼 데이터를 순서대로 밀어넣으면 상대가 순서대로 꺼내간다. |
TCP는 바이트 스트림 기반이라 메시지를 나누는 단위가 없다. 송신 측이 여러 번 나눠서 보냈더라도, 수신 측은 합쳐서 받을 수 있기 때문에, 전체 바이트 수만 맞춰서 잘 읽어오면 된다.
<클라이언트와 서버 간의 데이터 교환 예시>
클라이언트는 키보드로부터 문자열을 입력받아 서버에 전송
서버는 받은 문자열을 대문자로 변환
변환된 문자열을 클라이언트로 전송
클라이언트는 변환된 데이터를 화면에 출력
socket programming with UDP
UDP 기반(연결 없는 통신 방식)으로 클라이언트와 서버 간에 connection이 없다. 데이터를 보내기 전에 handshaking도 하지 않는다.
sender는 데이터를 보낼 때 목적지 IP주소와 포트 번호를 직접 붙여서 전송한다. 연결이 없기 때문에 매번 붙여야 한다.
receiver는 받은 패킷에서 송신자의 IP, 포트 정보를 추출해야 한다. 그래야 응답을 다시 보낼 수 있다.
데이터 손실 가능성이 있고 순서가 뒤바뀐 채로 도착할 수 있다는 단점이 있다.
애플리케이션 관점: unreliable 하게 byte 그룹(데이터그램)을 전송하는 방식
<UDP 방식으로 클라이언트와 서버가 소통하는 흐름도>

(서버: S, 클라이언트: C)
S: socket(AF_INET, SOCK_DGRAM)-UDP 소켓 생성
C: socket(AF_INET, SOCK_DGRAM)-소켓 생성
C: create datagram-목적지 IP, port 붙여서 메시지 생성
C: send via clientSocket-서버로 전송
S: read datagram-클라이언트로부터 메시지 받기
S: 받은 IP/port를 기반으로 다시 응답 보내기(write reply)
C: read reply-서버 응답 수신
C: close-소켓 닫기
<UDP client>
사용자가 보낸 메시지를 서버에 전송하여 응답을 받고 출력
from socket import *
serverName = 'hostname' #데이터를 보낼 서버 주소
serverPort = 12000 #포트 번호
clientSocket = socket(AF_INET, SOCK_DGRAM) #소켓 생성
message = raw_input('Input lowercase sentence:') #사용자로부터 문자열 입력 받음
clientSocket.sendto(message.encode(), (serverName, serverPort)) #입력한 메시지를 인코딩해서 UDP 패킷으로 전송
modifiedMessage, serverAddress = clientSocket.recvfrom(2048) #서버로부터 최대 2048바이트 데이터 수신
print(modifiedMessage.decode()) #수신된 byte 데이터를 문자열로 디코딩해서 출력
clientSocket.close() #소켓 연결 종료
sendto(): 데이터를 목적지 주소로 전달
recvfrom(): 데이터를 수신하고, 발신자 주소(IP, 포트)까지 함께 리턴
<UDP server>
클라이언트 메시지를 받아서 대문자로 변환 후 응답
from socket import *
serverPort = 12000 #클라이언트와 통신할 서버 포트
serverSocket = socket(AF_INET, SOCK_DGRAM) #UDP용 소켓 생성
serverSocket.bind(('', serverPort)) #bind()를 통해 모든 IP 주소('')에 대해 포트 12000으로 서버 대기 상태 설정
print("The server is ready to receive") #서버가 준비되었음을 출력
while True: #서버는 무한 루프로 항상 클라이언트 요청을 기다림
message, clientAddress = serverSocket.recvfrom(2048) #클라이언트로부터 메시지 수신
modifiedMessage = message.decode().upper() #받은 메시지를 문자열로 변환 후 대문자로 변경
serverSocket.sendto(modifiedMessage.encode(), clientAddress) #대문자로 바꾼 문자열을 바이트로 인코딩하여 다시 클라이언트로 전송
socket programming with TCP
TCP는 신뢰성 있고 순서가 보장되며 연결지향적인 프로토콜이다. 따라서 클라이언트와 서버 간 연결을 먼저 수립해야 한다.
byte-stream transfer
클라이언트가 서버에 연결하려면,
서버 프로세스는 실행 중이어야 하고 서버 소켓을 먼저 만들어서 클라이언트의 연결을 기다리는 상태여야 한다.
즉, serverSocket = socket() 후 bind(), listen()등을 통해 대기한다.
클라이언트는 TCP 소켓을 만들어서 서버의 IP 주소와 포트 번호로 연결 요청을 보낸다.
clientSocket()=socket()
clientSocket.connect((serverIP, serverPort))
클라이언트가 접속하면 severSocket.accept()를 통해 새로운 커넥션 소켓이 생성된다.
<TCP 방식으로 클라이언트와 서버가 소통하는 흐름도>

bind(): IP 주소와 포트 설정
listen(): 클라이언트 연결 대기
accept(): 클라이언트 접속 수락, 새 소켓 반환
connect(): 서버로 연결 시도
<TCP client>
from socket import *
serverName = 'servername'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM) #TCP 소켓 생성
clientSocket.connect((serverName, serverPort)) #서버에 연결 요청
sentence = input('Input lowercase sentence:') #사용자로부터 메시지를 입력받음
clientSocket.send(sentence.encode()) #입력받은 문자열을 바이트 형태로 인코딩해서 전송
modifiedSentence = clientSocket.recv(1024) #서버로부터 응답(변환된 문장)을 받음. 최대 1024바이트
print('From Server:', modifiedSentence.decode()) #받은 바이트 데이터를 문자열로 디코딩해서 출력
clientSocket.close() #연결 종료
<TCP server>
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort)) #서버가 자신의 IP,port 바인딩
serverSocket.listen(1) #클라이언트 요청을 기다림. 최대 대기 요청 수: 1
print('The server is ready to receive') #서버 시작 메시지 출력
while True:
connectionSocket, addr = serverSocket.accept() #클라이언트 하나와 1:1 통신할 새 소켓 생성
sentence = connectionSocket.recv(1024).decode() #클라이언트가 보낸 메시지 수신하고 문자열로 디코딩
capitalizedSentence = sentence.upper() #수신된 문장을 대문자로 변환
connectionSocket.send(capitalizedSentence.encode()) #변환된 문자열을 바이트로 인코딩하여 클라이언트에게 전송
connectionSocket.close() #연결 종료(서버 소켓은 계속 열려있음)
'Computer Engineering > Computer networks' 카테고리의 다른 글
| comnet-06 (0) | 2025.04.30 |
|---|---|
| comnet-05 (1) | 2025.04.30 |
| comnet-04 security (2) | 2025.04.30 |
| comnet-02 (1) | 2025.04.30 |
| comnet-01 (0) | 2025.04.30 |