TCPServer

TCPServer (TCP/IP 서버 소켓)

TCPServerTCP/IP 서버 소켓을 나타내요. 간단한 TCP 서버는 이렇게 생겼어요.

require 'socket'

server = TCPServer.new 2000 # 서버가 2000번 포트에 바인딩돼요
loop do
  client = server.accept    # 클라이언트가 접속하길 기다려요
  client.puts "Hello !"
  client.puts "Time is #{Time.now}"
  client.close
end

여러 클라이언트를 동시에 처리하려면 접속마다 스레드를 띄우면 돼요.

require 'socket'

server = TCPServer.new 2000
loop do
  Thread.start(server.accept) do |client|
    client.puts "Hello !"
    client.puts "Time is #{Time.now}"
    client.close
  end
end

출처: Ruby 4.0 API

본문

클래스 메서드 (Public Class Methods)

new([hostname,] port) → tcpserver

port에 바인딩된 새 서버 소켓을 만들어요. hostname을 주면 그 호스트에 바인딩돼요.

serv = TCPServer.new("127.0.0.1", 28561)
s = serv.accept
s.puts Time.now
s.close

내부적으로 TCPServer.new는 주소를 얻기 위해 getaddrinfo() 함수를 호출해요. getaddrinfo()가 여러 주소를 돌려주면 각 주소마다 서버 소켓 생성을 시도하고, 성공한 첫 번째 것을 돌려줘요.

인스턴스 메서드 (Public Instance Methods)

accept → tcpsocket

들어오는 연결을 수락해요. 새 TCPSocket 객체를 돌려줘요.

TCPServer.open("127.0.0.1", 14641) {|serv|
  s = serv.accept
  s.puts Time.now
  s.close
}

accept_nonblock([options]) → tcpsocket

기본 파일 디스크립터에 O_NONBLOCK을 설정한 뒤 accept(2)를 이용해 들어오는 연결을 수락해요. 수락된 TCPSocket을 돌려줘요.

require 'socket'
serv = TCPServer.new(2202)
begin # blocking accept 에뮬레이션
  sock = serv.accept_nonblock
rescue IO::WaitReadable, Errno::EINTR
  IO.select([serv])
  retry
end
# sock 은 수락된 소켓이에요.

TCPServer#accept_nonblock이 실패할 때 던질 수 있는 예외는 Socket#accept를 참고하세요. accept(2) 실패에 해당하는 오류(Errno::EWOULDBLOCK 포함)를 던질 수 있어요. 예외가 Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO라면 IO::WaitReadable로 확장돼요. 그래서 IO::WaitReadable로 rescue해서 accept_nonblock 재시도에 쓸 수 있어요.

키워드 인자 exception: false를 주면 IO::WaitReadable 예외 대신 :wait_readable 심볼을 돌려주게 할 수 있어요.

listen(int) → 0

주어진 int를 backlog(대기 큐 길이)로 해서 연결을 기다려요. listen 호출은 소켓이 SOCK_STREAM 또는 SOCK_SEQPACKET 타입일 때만 적용돼요.

backlog는 대기 중인 연결 큐의 최대 길이예요.

require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
sockaddr = Socket.pack_sockaddr_in( 2200, 'localhost' )
socket.bind( sockaddr )
socket.listen( 5 )

Unix 계열에서 임의의 포트로 리슨하는 예시예요.

require 'socket'
include Socket::Constants
socket = Socket.new( AF_INET, SOCK_STREAM, 0 )
socket.listen( 1 )

Unix에서는 새 sockaddr가 커널이 넘겨주는 임의 포트로 ADDR_ANY 주소에 생성되므로 위 코드가 동작해요. 하지만 Windows에서는 socketbind로 바인딩해야만 listen할 수 있으므로 동작하지 않아요.

backlog가 구현 의존적 최대 큐 길이를 넘으면 구현의 최대 큐 길이가 사용돼요.

Unix 계열에서 listen 실패 시 발생할 수 있는 시스템 예외로는 Errno::EBADF(잘못된 파일 디스크립터), Errno::EDESTADDRREQ(로컬 주소에 바인딩되지 않음), Errno::EINVAL(이미 연결됨/셧다운됨), Errno::ENOTSOCK(소켓 아님), Errno::EOPNOTSUPP(프로토콜이 listen 미지원), Errno::EACCES(권한 부족), Errno::ENOBUFS(리소스 부족) 등이 있어요.

Windows에서 listen 실패 시 발생할 수 있는 예외는 Errno::ENETDOWN(네트워크 다운), Errno::EADDRINUSE(로컬 주소 사용 중), Errno::EINPROGRESS(Windows Sockets 1.1 호출 진행 중), Errno::EINVAL(bind 안 됨/이미 연결됨), Errno::EISCONN(이미 연결됨), Errno::EMFILE(소켓 디스크립터 부족), Errno::ENOBUFS(버퍼 공간 부족), Errno::ENOTSOC(소켓 아님), Errno::EOPNOTSUPP(listen 미지원 타입) 등이 있어요.

sysaccept → file_descriptor

수락된 연결의 파일 디스크립터를 돌려줘요.

TCPServer.open("127.0.0.1", 28561) {|serv|
  fd = serv.sysaccept
  s = IO.for_fd(fd)
  s.puts Time.now
  s.close
}