Computer >> 컴퓨터 >  >> 프로그래밍 >> Ruby

루비 네트워크 프로그래밍 완벽 가이드: 소켓의 기초부터 TCP 서버 구축까지

루비로 나만의 네트워크 클라이언트와 서버를 직접 만들고 싶으신가요? 아니면 그 동작 원리를 이해하고 싶으신가요?

그렇다면 반드시 소켓(Socket)이라는 개념을 다뤄야 합니다.

이 글에서는 루비 네트워크 프로그래밍의 기본기를 다지고, 루비를 사용해 다른 서버와 클라이언트와 실제로 통신하는 방법까지 함께 살펴보겠습니다.

소켓이란 무엇인가?

소켓은 통신 채널의 양 끝단(엔드포인트)을 의미하며, 클라이언트와 서버 모두 소켓을 통해 서로 데이터를 주고받습니다.

동작 방식은 매우 간단합니다:

연결이 한 번 수립되면 소켓에 데이터를 넣으면 됩니다. 그러면 데이터가 상대방 쪽으로 전달되고, 수신자는 소켓에서 데이터를 읽어 들여 처리하게 됩니다.

소켓의 종류

사용할 수 있는 소켓에는 여러 종류가 있습니다. 가장 흔히 쓰이는 TCP 소켓은 HTTP나 FTP처럼 TCP 기반 서비스에 접속할 때 사용합니다.

UDP 기반 프로토콜을 다뤄야 한다면 UDP 소켓을 사용하면 됩니다.

그 외의 소켓 유형은 조금 더 전문적인 용도로 쓰입니다. 예를 들어 유닉스 도메인 소켓(Unix Socket)은 유닉스 시스템에서 완전한 TCP 연결의 오버헤드 없이 프로세스 간 통신(IPC)을 가능하게 해줍니다.

루비에서 소켓 사용하기

소켓으로 무엇을 할 수 있는지 알았으니, 이제 실제로 사용해 볼 차례입니다.

먼저 프로그램에 소켓 라이브러리를 불러옵니다:

require 'socket'

TCP 소켓을 생성하려면 TCPSocket 클래스를 사용하면 됩니다. 인자로는 접속 대상의 IP 주소와 포트 번호가 필요합니다.

이 코드는 연결을 시도하며, 연결에 실패하면 Errno::ECONNREFUSED 에러가 발생합니다.

socket = TCPSocket.new('google.com', 80)

이제 소켓을 통해 메시지를 보낼 수 있습니다. 단, 상대방이 여러분의 요청을 이해할 수 있도록 통신 중인 프로토콜의 규격을 따라야 합니다.

socket.write "GET / HTTP/1.1"
socket.write "\r\n\r\n"

여기서 사용하는 많은 메서드는 사실 TCPSocket의 부모 클래스들에서 물려받은 것입니다.

서버의 응답을 읽으려면 recv 메서드를 사용합니다.

이때 소켓에서 읽어 올 바이트 수를 인자로 전달해야 합니다:

puts socket.recv(100)

그런데 여기에 작은 문제가 있습니다. 데이터가 제대로 돌아오지 않으면 애플리케이션이 아무 일도 하지 않는 것처럼 보일 수 있습니다.

그 이유는 읽을 데이터가 충분하지 않으면 프로그램이 '블로킹(blocking)' 상태에 빠지기 때문입니다.

즉, 데이터가 도착하거나 서버가 연결을 닫을 때까지 프로그램이 대기하게 되는 것입니다.

다루는 프로토콜에 따라 한 번에 읽는 데이터 크기를 늘리거나 줄여야 할 수도 있습니다.

만약 블로킹이 문제가 된다면 IO 클래스의 readpartialread_nonblock 메서드를 확인해 보세요.

TCP 서버 작성하기

이번에는 서버를 만들어 보겠습니다! 과정은 클라이언트를 작성하는 것과 비슷하지만, 소켓이 특정 인터페이스에 바인딩(bind)하도록 지정하고, 해당 포트에서 대기(listen)한 뒤, 마지막으로 들어오는 연결을 수락(accept)해야 합니다.

다행히 TCPServer 클래스가 앞의 두 단계를 자동으로 처리해 줍니다.

예제는 다음과 같습니다:

require 'socket'

socket = TCPServer.new('0.0.0.0', 8080)
client = socket.accept

puts "New client! #{client}"

client.write("Hello from server")
client.close

이 예제 서버는 8080 포트에서 대기하며, 접속하는 클라이언트에게 환영 메시지를 보냅니다.

주목할 점은 클라이언트 하나만 수락하고 프로그램이 종료된다는 것입니다.

여러 클라이언트 수락하기

여러 클라이언트를 동시에 수락하고 응답하려면 루프와 스레드가 필요합니다.

예제:

require 'socket'

PORT   = 8081
socket = TCPServer.new('0.0.0.0', PORT)

def handle_connection(client)
  puts "New client! #{client}"

  client.write("Hello from server")
  client.close
end

puts "Listening on #{PORT}. Press CTRL+C to cancel."

loop do
  client = socket.accept

  Thread.new { handle_connection(client) }
end

이 코드를 실행하면 강제로 중단하기 전까지 계속해서 클라이언트의 접속을 기다리는 서버가 시작됩니다.

여기서 한 단계 더 나아가 루비로 웹 서버를 직접 작성하는 방법을 배우고 싶다면 관련 심화 포스트를 참고해 보세요.

마무리

이번 글에서는 TCP 소켓이 무엇인지, 어떻게 동작하는지, 그리고 TCPServerTCPSocket 같은 루비 클래스를 활용해 인터넷상의 다른 머신과 통신하는 애플리케이션을 만드는 방법을 배웠습니다.

루비 네트워크 프로그래밍은 정말 재미있습니다!

이제 멋진 무언가를 만들어서 댓글로 여러분과 공유해 보세요 🙂

그리고 루비 실력을 꾸준히 키우고 싶다면 뉴스레터 구독도 잊지 마세요!