TCPServer
TCPServer (TCP/IP 서버 소켓)
TCPServer는 TCP/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에서는 socket을 bind로 바인딩해야만 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
}